package com.capgemini.reports.definition.r07report.data;

import com.capgemini.framework.teamcenter.authent.TcSession;
import com.capgemini.framework.teamcenter.exceptions.KurtException;
import com.capgemini.reports.bomlineholder.flattener.line.SnecmaLine;
import com.capgemini.reports.changeobjects.change.TcChangeNoticeRevision;
import com.capgemini.reports.changeobjects.change.TcChangeRequestRevision;
import com.capgemini.reports.definition.r07report.CRExtractorHelper;
import com.capgemini.reports.definition.r07report.ExecutionR07;
import com.capgemini.reports.definition.r07report.R07Printer;
import com.capgemini.reports.definition.r07report.ReportDataR07;
import com.capgemini.reports.definition.r07report.data.extractor.BomFlattenerR07;
import com.capgemini.reports.definition.r07report.data.extractor.DataExtractorR07;
import com.capgemini.reports.definition.r07report.data.filter.ConfigurationFilterR07;
import com.capgemini.reports.definition.r07report.data.sorter.R07DataSorter;
import com.capgemini.reports.definition.r07report.data.sorter.R07DataSorter.ConfLine;
import com.capgemini.reports.definition.r07report.printing.EnumMapDelta;
import com.capgemini.reports.execution.IDataReportPreparator;
import com.capgemini.reports.execution.strategies.IExecutionStrategy;
import com.capgemini.reports.printing.PrintParameters;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.SetMultimap;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * This class will allow to prepare data and findAll back data for making report R07.
 */
public class DataReportPreparatorR07 implements IDataReportPreparator<ExecutionR07> {
    private static final long serialVersionUID = 6255355550254874091L;
    private static final Logger LOGGER = LoggerFactory.getLogger(DataReportPreparatorR07.class);

    @Override
    public String print(TcSession session, ExecutionR07 execution, IExecutionStrategy executionStrategy) throws KurtException {
        PrintParameters printParameters = prepareData(session, execution);
        executionStrategy.print(printParameters, execution);
        return executionStrategy.getReportFileName(execution) + printParameters.getOuputFileExtension();
    }

    private PrintParameters prepareData(final TcSession session, final ExecutionR07 report) throws KurtException {
        LOGGER.debug("PrepareData started on: {}", report.getReportIdentifier().getReportTitle());

        // Extract the Tc data
        DataExtractorR07 extractor = new DataExtractorR07(session, report);
        extractor.extractData();

        // Flatten the bomLines to extract all links in a List
        BomFlattenerR07 flatConf = new BomFlattenerR07();
        flatConf.visitTopReceptorBom(session, extractor.getTopBomConf1(), extractor.getTopBomConf2());

        // Define number of triplet for each configuration
        int sizeTripletConf1 = flatConf.getConfig1().size();
        int sizeTripletConf2 = flatConf.getConfig2().size();

        // Filter the data to remove objects that are identical in both configuration. Also isolates exclusive items in each onf.
        ConfigurationFilterR07 filter = new ConfigurationFilterR07();
        filter.filter(flatConf);

        // Sort the data according to the original bomLine browsing order
        R07DataSorter dataSorter = new R07DataSorter();
        Collection<ConfLine> reportLines = dataSorter.sort(filter);

        // Obtain the Change Requests and Change Notices
        List<ReportDataLineR07> dataList = extractCrCn(session, reportLines);

        // Count the maximum number of CNs
        int maxCN = countMaxCN(dataList);
        ReportDataR07 dataR07 = new ReportDataR07(report.getGenericParam1(), report.getGenericParam2(), sizeTripletConf1, sizeTripletConf2, maxCN, dataList);
        R07Printer printer = new R07Printer();
        PrintParameters printParameters = printer.makeMasterTemplate(report, dataR07);
        printer.constructDataMaps(session, printParameters, dataR07);

        LOGGER.debug("PrepareData ended on: {}", report.getReportIdentifier().getReportTitle());
        return printParameters;
    }

    int countMaxCN(final List<ReportDataLineR07> dataList) {
        int maxCN = 1; // Minimum 1 column
        for (ReportDataLineR07 line : dataList) {
            // There can be CNs bundled behind a CR. Amongst these lines, the FAKE CR points to the CNs that have no father, so the line length is correct
            for (Collection<TcChangeNoticeRevision> cnInThisLine : line.getBundles().asMap().values()) {
                maxCN = Math.max(maxCN, cnInThisLine.size());
            }
        }
        return maxCN;
    }

    List<ReportDataLineR07> extractCrCn(final TcSession session, final Collection<ConfLine> reportLines) {
        // Extract CRs and CNs
        CRExtractorHelper crExtractor = new CRExtractorHelper(session);
        List<ReportDataLineR07> reportDataList = new ArrayList<>();
        for (ConfLine line : reportLines) {
            // Obtain all related CRs and CNs
            SnecmaLine<?, ?> snecmaLine = line.getLine();
            String componentId = snecmaLine.getIDComponent();
            String componentRevision = snecmaLine.getIDRevisionComponent();
            List<TcChangeRequestRevision> crList = crExtractor.getCrOnPart(componentId, componentRevision);
            List<TcChangeNoticeRevision> cnList = crExtractor.getCnOnPart(componentId, componentRevision);

            // Build map: CR -> CN
            SetMultimap<TcChangeRequestRevision, TcChangeNoticeRevision> bundles = makeBundles(crList, cnList);
            reportDataList.add(new ReportDataLineR07(snecmaLine, bundles, line.isConf1(), line.isExclusive(), line.getHasMod()));
        }
        return reportDataList;
    }

    /**
     * Remove and group in bundles the CNs that have a common ancestor CR, and leave only the CNs that have no ancestor in the input list.
     *
     * @param lstCN a {@link List} of {@link TcChangeNoticeRevision}s. Those CNs all mention the part. This list will be altered, only the CNs without a CR 'father' will be left.
     * @return a {@link Map} grouping a {@link List} of {@link TcChangeNoticeRevision} sharing a common {@link TcChangeRequestRevision} 'father'.
     */
    private SetMultimap<TcChangeRequestRevision, TcChangeNoticeRevision> makeBundles(final List<TcChangeRequestRevision> lstCR,
                                                                                     final List<TcChangeNoticeRevision> lstCN) {
        SetMultimap<TcChangeRequestRevision, TcChangeNoticeRevision> bundles = HashMultimap.create();
        // Put all the CR first. Link them to their Children CNs IF those children are in the CNlist (they mention the Part in their pseudo-folder 'after')
        for (TcChangeRequestRevision changeRequestRevision : lstCR) {
            for (TcChangeNoticeRevision child : changeRequestRevision.getChangeNoticesList()) {
                if (lstCN.contains(child)) {
                    bundles.put(changeRequestRevision, child);
                }
            }
            if (!bundles.containsKey(changeRequestRevision)) {
                bundles.put(changeRequestRevision, null);
            }
        }
        // Then put all CNs in the map. If they have one or more father CRs, add the CN to the CR's children list. Otherwise, put them in FAKE's list
        for (TcChangeNoticeRevision changeNoticeRevision : lstCN) {
            if (changeNoticeRevision.getImplementsCR() != null && !changeNoticeRevision.getImplementsCR().isEmpty()) {
                for (TcChangeRequestRevision father : changeNoticeRevision.getImplementsCR()) {
                    bundles.put(father, changeNoticeRevision);
                    bundles.remove(father, null); // Try to remove a null from the father's sons. May have happened if the father had no known CN. It now has at least one.
                }
            } else {
                bundles.put(ReportDataLineR07.FAKE, changeNoticeRevision);
            }
        }
        return bundles;
    }

    /**
     * Regroups all the data that is necessary to produce a line in the final report: the written date, the configuration it belongs to, which cells to highlight.
     */
    @Getter
    @AllArgsConstructor
    public static class ReportDataLineR07 {

        /**
         * This is a placeholder.
         */
        public static final TcChangeRequestRevision FAKE = new TcChangeRequestRevision(null, null) {
            private static final long serialVersionUID = -3143088096659343939L;

        };

        private final SnecmaLine<?, ?> line;
        /**
         * These CRs keys do not mention the component. However, the CNs in the value List do, and the CR is their 'father'.
         */
        private final SetMultimap<TcChangeRequestRevision, TcChangeNoticeRevision> bundles;
        private final boolean conf1;
        private final boolean exclusive;
        Map<EnumMapDelta, Boolean> hasMod;

        @Override
        public String toString() {
            return "Data: " + (exclusive ? "E" : "") + (conf1 ? "1" : "2") + line.toString() + " with " + bundles.size() + " CR(s)";
        }
    }
}
