DecisionFlowChartManagerImpl.java

/*
 * Copyright 2010-2024 James Pether Sörling
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *	$Id$
 *  $HeadURL$
*/
package com.hack23.cia.web.impl.ui.application.views.common.chartfactory.impl;

import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.hack23.cia.model.internal.application.data.committee.impl.ViewRiksdagenCommittee;
import com.hack23.cia.web.impl.ui.application.views.common.chartfactory.api.DecisionFlowChartManager;
import com.hack23.cia.web.impl.ui.application.views.common.dataseriesfactory.api.DecisionDataFactory;
import com.hack23.cia.web.impl.ui.application.views.common.dataseriesfactory.api.ProposalCommitteeeSummary;
import com.hack23.cia.web.widgets.charts.SankeyChart;
import com.vaadin.ui.TextArea;

/**
 * The Class DecisionFlowChartManagerImpl.
 */
@Service
public final class DecisionFlowChartManagerImpl implements DecisionFlowChartManager {

    @Autowired
    private DecisionDataFactory decisionDataFactory;

    /**
     * Instantiates a new decision flow chart manager impl.
     */
    public DecisionFlowChartManagerImpl() {
        super();
    }

    @Override
    public SankeyChart createAllDecisionFlow(final Map<String, List<ViewRiksdagenCommittee>> committeeMap,
                                             final String rm) {
        // Retrieve all proposals for the given 'rm'
        final List<ProposalCommitteeeSummary> committeeSummaries = decisionDataFactory.createCommitteeSummary(rm);

        // Prepare the sankey chart
        final SankeyChart chart = new SankeyChart();

        // Group by organization
        final Map<String, List<ProposalCommitteeeSummary>> orgProposalMap =
            committeeSummaries.stream()
                              .collect(Collectors.groupingBy(ProposalCommitteeeSummary::getOrg));

        // For each org, if we have a matching committee in 'committeeMap', add data rows
        for (final Entry<String, List<ProposalCommitteeeSummary>> orgEntry : orgProposalMap.entrySet()) {
            final String orgKey = orgEntry.getKey();
            final List<ProposalCommitteeeSummary> orgSummaries = orgEntry.getValue();
            if (committeeMap.containsKey(orgKey)) {
                final Optional<ViewRiksdagenCommittee> committeeOpt =
                    committeeMap.get(orgKey).stream().findFirst();
                if (committeeOpt.isPresent()) {
                    final ViewRiksdagenCommittee committee = committeeOpt.get();
                    addDocTypeDataRows(chart, orgSummaries, committee);
                    addDecisionDataRows(chart, orgSummaries, committee);
                }
            }
        }

        chart.drawChart();
        return chart;
    }

    @Override
    public SankeyChart createCommitteeDecisionFlow(final ViewRiksdagenCommittee viewRiksdagenCommittee,
                                                   final Map<String, List<ViewRiksdagenCommittee>> committeeMap,
                                                   final String rm) {
        final List<ProposalCommitteeeSummary> committeeSummaries = decisionDataFactory.createCommitteeSummary(rm);

        final SankeyChart chart = new SankeyChart();
        // Group by organization
        final Map<String, List<ProposalCommitteeeSummary>> orgProposalMap =
            committeeSummaries.stream()
                              .collect(Collectors.groupingBy(ProposalCommitteeeSummary::getOrg));

        final String targetOrg = viewRiksdagenCommittee.getEmbeddedId().getOrgCode().toUpperCase(Locale.ENGLISH);
        for (final Entry<String, List<ProposalCommitteeeSummary>> orgEntry : orgProposalMap.entrySet()) {
            final String orgKeyUpper = orgEntry.getKey().toUpperCase(Locale.ENGLISH);
            if (committeeMap.containsKey(orgKeyUpper) && targetOrg.equals(orgKeyUpper)) {
                addDocTypeDecisionDataRows(chart, orgEntry.getValue());
            }
        }

        chart.drawChart();
        return chart;
    }

    @Override
    public TextArea createCommitteeeDecisionSummary(final Map<String, List<ViewRiksdagenCommittee>> committeeMap,
                                                    final String rm) {
        final TextArea area = new TextArea("Summary");
        final StringBuilder builder = new StringBuilder();

        final List<ProposalCommitteeeSummary> committeeSummaries = decisionDataFactory.createCommitteeSummary(rm);

        // Group by organization
        final Map<String, List<ProposalCommitteeeSummary>> orgProposalMap =
            committeeSummaries.stream()
                              .collect(Collectors.groupingBy(ProposalCommitteeeSummary::getOrg));

        for (final Entry<String, List<ProposalCommitteeeSummary>> orgEntry : orgProposalMap.entrySet()) {
            final String orgKeyUpper = orgEntry.getKey().toUpperCase(Locale.ENGLISH);
            if (committeeMap.containsKey(orgKeyUpper)) {
                // Grab the first committee from the list
                final ViewRiksdagenCommittee committee =
                    committeeMap.get(orgKeyUpper).stream().findFirst().orElse(null);
                addCommiteeSummary(builder, orgEntry.getValue(), committee);
            }
        }
        area.setValue(builder.toString());
        return area;
    }

    @Override
    public TextArea createCommitteeeDecisionSummary(final ViewRiksdagenCommittee viewRiksdagenCommittee,
                                                    final String rm) {
        final TextArea area = new TextArea("Summary");
        final StringBuilder builder = new StringBuilder();

        final List<ProposalCommitteeeSummary> committeeSummaries = decisionDataFactory.createCommitteeSummary(rm);

        // Group by organization
        final Map<String, List<ProposalCommitteeeSummary>> orgProposalMap =
            committeeSummaries.stream()
                              .collect(Collectors.groupingBy(ProposalCommitteeeSummary::getOrg));

        final String targetOrg = viewRiksdagenCommittee.getEmbeddedId().getOrgCode().toUpperCase(Locale.ENGLISH);

        // Retrieve the relevant summaries for the single committee
        final List<ProposalCommitteeeSummary> list = orgProposalMap.get(targetOrg);
        addCommiteeSummary(builder, list, viewRiksdagenCommittee);

        area.setValue(builder.toString());
        return area;
    }

    /**
     * Helper to add doc type data rows to sankey.
     *
     * @param chart    the chart
     * @param proposals the summary list for the org
     * @param committee the committee
     */
    private static void addDocTypeDataRows(final SankeyChart chart,
                                           final List<ProposalCommitteeeSummary> proposals,
                                           final ViewRiksdagenCommittee committee) {
        // Group by doc type
        final Map<String, List<ProposalCommitteeeSummary>> docTypeMap =
            proposals.stream().collect(Collectors.groupingBy(ProposalCommitteeeSummary::getDocType));

        final String committeeName = committee.getEmbeddedId().getDetail();
        for (final Entry<String, List<ProposalCommitteeeSummary>> docEntry : docTypeMap.entrySet()) {
            final String docType = docEntry.getKey();
            if (!docType.isEmpty()) {
                chart.addDataRow(docType, committeeName, docEntry.getValue().size());
            }
        }
    }

    /**
     * Helper to add decision data rows to sankey.
     *
     * @param chart    the chart
     * @param proposals the summary list for the org
     * @param committee the committee
     */
    private static void addDecisionDataRows(final SankeyChart chart,
                                            final List<ProposalCommitteeeSummary> proposals,
                                            final ViewRiksdagenCommittee committee) {
        // Group by decision
        final Map<String, List<ProposalCommitteeeSummary>> decisionMap =
            proposals.stream().collect(Collectors.groupingBy(ProposalCommitteeeSummary::getDecision));

        final String committeeName = committee.getEmbeddedId().getDetail();
        for (final Entry<String, List<ProposalCommitteeeSummary>> decisionEntry : decisionMap.entrySet()) {
            final String decision = decisionEntry.getKey();
            if (!decision.isEmpty()) {
                chart.addDataRow(committeeName, decision, decisionEntry.getValue().size());
            }
        }
    }

    /**
     * Helper to add doc type -> decision data rows to sankey.
     *
     * @param chart   the chart
     * @param proposals the summary list for the org
     */
    private static void addDocTypeDecisionDataRows(final SankeyChart chart,
                                                   final List<ProposalCommitteeeSummary> proposals) {
        // Group by doc type
        final Map<String, List<ProposalCommitteeeSummary>> docTypeMap =
            proposals.stream().collect(Collectors.groupingBy(ProposalCommitteeeSummary::getDocType));

        for (final Entry<String, List<ProposalCommitteeeSummary>> docEntry : docTypeMap.entrySet()) {
            final String docType = docEntry.getKey();
            if (!docType.isEmpty()) {
                // Now group by decision
                final Map<String, List<ProposalCommitteeeSummary>> decisionMap =
                    docEntry.getValue().stream()
                            .collect(Collectors.groupingBy(ProposalCommitteeeSummary::getDecision));
                for (final Entry<String, List<ProposalCommitteeeSummary>> decEntry : decisionMap.entrySet()) {
                    final String decision = decEntry.getKey();
                    if (!decision.isEmpty()) {
                        chart.addDataRow(docType, decision, decEntry.getValue().size());
                    }
                }
            }
        }
    }

    /**
     * Helper to create a textual committee summary in a StringBuilder.
     *
     * @param builder     the StringBuilder
     * @param proposals   the proposal summaries for this committee
     * @param committee   the committee
     */
    private static void addCommiteeSummary(final StringBuilder builder,
                                           final List<ProposalCommitteeeSummary> proposals,
                                           final ViewRiksdagenCommittee committee) {
        // If no committee or proposals, bail out
        if (committee == null || proposals == null || proposals.isEmpty()) {
            return;
        }

        final String detail = committee.getEmbeddedId().getDetail();
        builder.append('\n').append(detail);

        // Group by doc type
        final Map<String, List<ProposalCommitteeeSummary>> docTypeMap =
            proposals.stream().collect(Collectors.groupingBy(ProposalCommitteeeSummary::getDocType));

        for (final Entry<String, List<ProposalCommitteeeSummary>> docEntry : docTypeMap.entrySet()) {
            addEntry(builder, docEntry);
        }
    }

    /**
     * Build a textual representation of doc type -> decision -> proposals.
     *
     * @param builder the builder
     * @param docEntry the doc entry
     */
    private static void addEntry(final StringBuilder builder,
                                 final Entry<String, List<ProposalCommitteeeSummary>> docEntry) {
        final String docType = docEntry.getKey();
        final List<ProposalCommitteeeSummary> docTypeList = docEntry.getValue();

        builder.append("\n ( ")
               .append(docTypeList.size())
               .append(' ')
               .append(docType)
               .append(" -> ");

        // Group by decision
        final Map<String, List<ProposalCommitteeeSummary>> decisionMap =
            docTypeList.stream()
                       .collect(Collectors.groupingBy(ProposalCommitteeeSummary::getDecision));

        for (final Entry<String, List<ProposalCommitteeeSummary>> decisionEntry : decisionMap.entrySet()) {
            final String decision = decisionEntry.getKey();
            if (!decision.isEmpty()) {
                builder.append("\n   ")
                       .append(decisionEntry.getValue().size())
                       .append(' ')
                       .append(decision)
                       .append(' ');

                for (final ProposalCommitteeeSummary summary : decisionEntry.getValue()) {
                    builder.append("\n    ")
                           .append(summary.getDecision())
                           .append(':')
                           .append(summary.getWording())
                           .append(' ')
                           .append(summary.getWording2())
                           .append(' ');
                }
            }
        }
        builder.append(')');
    }
}