package com.capgemini.reports.definition.r07report;

import com.capgemini.framework.common.LanguageHelper;
import com.capgemini.framework.common.ReportFolderOutputHelper;
import com.capgemini.framework.common.StringHelper;
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.data.DataReportPreparatorR07.ReportDataLineR07;
import com.capgemini.reports.definition.r07report.printing.*;
import com.capgemini.reports.exceptions.FileLoadingException;
import com.capgemini.reports.exceptions.InvalidInputException;
import com.capgemini.reports.exceptions.ReportPrintException;
import com.capgemini.reports.execution.mapconstructor.IMapConstructor;
import com.capgemini.reports.printing.PrintParameters;
import com.capgemini.reports.printing.PrintReportHelper;
import com.capgemini.reports.reports.parameters.Generics;
import com.google.common.collect.SetMultimap;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;

/**
 * Prints a R07 report using data carried in the {@link ExecutionR07}.
 */
@Getter
@Setter(value = AccessLevel.PROTECTED)
public class R07Printer implements IMapConstructor<ExecutionR07, ReportDataR07> {

    private static final String EMPTY_STRING = "";
    private static final long serialVersionUID = 8191686574681618653L;
    private int nbMaxChangeNotice;
    private String conf1Name;
    private String conf2Name;

    private static String getConfName(final Generics genericParam) throws ReportPrintException {
        switch (genericParam.getType()) {
            case GENERIC:
                return StringHelper.implode(" - ", genericParam.getProductUnit().getProductID(), genericParam.getGeneric(), genericParam.getProductUnit().getUnitStr(), today());
            case GENERIC_ASSEMB:
                return StringHelper.implode(" - ", genericParam.getProductUnit().getProductID(), genericParam.getGenericAssemb(), genericParam.getProductUnit().getUnitStr(), today());
            case ONLY_PRODUCT:
                return StringHelper.implode(" - ", genericParam.getProductUnit().getProductID(), genericParam.getProductUnit().getUnitStr(), today());
            default:
                throw new ReportPrintException("Unsupported Configuration type: " + genericParam.toString());
        }
    }

    private static String makeTitleContent(final int confNum, final Generics generics, final int nbConfLine) throws ReportPrintException {
        return "Nombre de triplets ou couples dans la configuration " + confNum + " ( " + getConfName(generics) + ") : " + nbConfLine;
    }

    private static String today() {
        Date dateActual = new Date();
        DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
        return dateFormat.format(dateActual);
    }

    @Override
    public PrintParameters makeMasterTemplate(final ExecutionR07 report, final ReportDataR07 data) throws ReportPrintException, FileLoadingException,
            InvalidInputException {
        conf1Name = getConfName(report.getGenericParam1());
        conf2Name = getConfName(report.getGenericParam2());
        nbMaxChangeNotice = data.getMaxCN();
        BuilderTemplateR07 templateBuilder = new BuilderTemplateR07(collectParamDifference(), nbMaxChangeNotice, conf1Name, conf2Name, report.getNameSheet());
        String template = templateBuilder.createDynamicTemplate();
        String storageFolder = ReportFolderOutputHelper.getStorageFolderLocation(report.getLaunchType());
        String fileName = report.getFileNameOnDisc();
        return PrintReportHelper.setupPrintParameters(template, storageFolder, fileName, report);
    }

    @Override
    public void constructDataMaps(final TcSession session, final PrintParameters printParameters, final ReportDataR07 reportData) throws KurtException {
        // On recupere toutes les donnees dans la map et apres on etudie chaque map de la liste pour voir les CR et CN
        List<Map<String, ?>> lstMapData = new ArrayList<>();
        for (ReportDataLineR07 elem : reportData.getReportDataList()) {
            SnecmaLine<?, ?> line = elem.getLine();
            Map<String, Object> structureMap = makeMap(line); // Map representing the structural elements (receptor/repere/component)
            SetMultimap<TcChangeRequestRevision, TcChangeNoticeRevision> bundles = elem.getBundles();

            // If no CR nor CN were found, add a line anyway
            if (bundles.isEmpty()) {
                fillerMapDataEffectivity(structureMap, elem.isConf1());
                fillerMapDataDifference(structureMap, elem.isExclusive(), elem.getHasMod());
                fillEmptyCrColumns(structureMap); // Fill unused CR column with blanks
                fillEmptyCnColumns(structureMap, 0); // Fill all CN columns with blanks
                lstMapData.add(structureMap);
                continue;
            }

            Set<TcChangeNoticeRevision> orphans = bundles.removeAll(ReportDataLineR07.FAKE); // Get orphans apart

            /*
             * 4 configurations for change objects acting on this component:
             * 1) a CR mentions the component, but has zero CNs ------> (put a single line, no CNs on it)
             * 2) a CR mentions the component, has some children CNs -> (put a single line, all CNs that also mention the component on the line)
             * 3) a CN mentions the component, has a parent CR -------> (DO NOTHING this is handled in 2) because CR<->CN link is reflexive)
             * 4) a CN mentions the component, has no parent CR ------> (gather all those CNs -> single line)
             */

            // Put the CR lines with REAL CRs first
            for (Entry<TcChangeRequestRevision, Collection<TcChangeNoticeRevision>> entry : bundles.asMap().entrySet()) {
                TcChangeRequestRevision changeRequestRevision = entry.getKey();
                // Copy the cnList as we need to remove any eventual null value, but we don't want to update the underlying multimap!
                Collection<TcChangeNoticeRevision> childrenCN = new ArrayList<>(entry.getValue());
                childrenCN.remove(null);
                Map<String, Object> mapTemporary = new HashMap<>(structureMap); // Structural map is copied to keep the content the same throughout all the lines
                addChangeRequestAndChangeNotice(mapTemporary, changeRequestRevision, childrenCN);
                fillerMapDataEffectivity(mapTemporary, elem.isConf1());
                fillerMapDataDifference(mapTemporary, elem.isExclusive(), elem.getHasMod());
                // Now fill with blanks any unused CN columns
                fillEmptyCnColumns(mapTemporary, childrenCN.size());
                // finally add the map
                lstMapData.add(mapTemporary);
            }

            // Finally print the CNs that have no CR parent all in the same line
            if (!orphans.isEmpty()) { // SetMultimap.findAll never returns null, but at least an empty map
                Map<String, Object> mapTemporary = addChangeNoticeWithoutCR(orphans, structureMap);
                fillerMapDataEffectivity(mapTemporary, elem.isConf1());
                fillerMapDataDifference(mapTemporary, elem.isExclusive(), elem.getHasMod());
                lstMapData.add(mapTemporary);
            }
        }

        // FIX CONFIGURATION WITH NUMBER OF RELATION (recepteur-repere-composant)
        Map<EnumChangeR07, Object> parameters = new EnumMap<>(EnumChangeR07.class);
        parameters.put(EnumChangeR07.TITLE_FIRST_CONFIGURATION, makeTitleContent(1, reportData.getGenericParam1(), reportData.getNumberOfLinesInConf1()));
        parameters.put(EnumChangeR07.TITLE_SECOND_CONFIGURATION, makeTitleContent(2, reportData.getGenericParam2(), reportData.getNumberOfLinesInConf2()));
        printParameters.addReportParameters(parameters);
        if (lstMapData.isEmpty()) {
            lstMapData.add(createEmptyMap());
        }
        printParameters.addReportData(lstMapData);
    }

    private Map<String, ?> createEmptyMap() {
        Map<String, Object> mapEmpty = new HashMap<>();
        mapEmpty.put(EnumRecepteurR07.EFFECTIVITE.toString(), EMPTY_STRING);
        mapEmpty.put(EnumRecepteurR07.ID_RECEPTEUR.toString(), EMPTY_STRING);
        mapEmpty.put(EnumRecepteurR07.REVISION_RECEPTEUR.toString(), EMPTY_STRING);
        mapEmpty.put(EnumRecepteurR07.NOM_RECEPTEUR.toString(), EMPTY_STRING);
        mapEmpty.put(EnumRepereR07.REPERE_FONCTIONNEL.toString(), EMPTY_STRING);
        mapEmpty.put(EnumRepereR07.REVISION_REPERE_FONCTIONNEL.toString(), EMPTY_STRING);
        mapEmpty.put(EnumRepereR07.RESPONSABLE_REPERE.toString(), EMPTY_STRING);
        mapEmpty.put(EnumRepereR07.SUIVI_MONTAGE.toString(), EMPTY_STRING);
        mapEmpty.put(EnumComposantR07.REF_FABRICANT.toString(), EMPTY_STRING);
        mapEmpty.put(EnumComposantR07.REVISION_REFERENCE.toString(), EMPTY_STRING);
        mapEmpty.put(EnumComposantR07.TYPE_REFERENCE.toString(), EMPTY_STRING);
        mapEmpty.put(EnumComposantR07.NOM_REFERENCE.toString(), EMPTY_STRING);
        mapEmpty.put(EnumComposantR07.REFERENCE_CONCEPTEUR.toString(), EMPTY_STRING);
        mapEmpty.put(EnumComposantR07.REFERENCE_TRANSPOSEE.toString(), EMPTY_STRING);
        mapEmpty.put(EnumComposantR07.REFERENCE_ASSOCIEE.toString(), EMPTY_STRING);
        mapEmpty.put(EnumComposantR07.RESPONSABLLE_CONCEPTION.toString(), EMPTY_STRING);
        mapEmpty.put(EnumComposantR07.ARTICLE_GESTION.toString(), EMPTY_STRING);
        mapEmpty.put(EnumComposantR07.CLASSEMENT.toString(), EMPTY_STRING);
        mapEmpty.put(EnumComposantR07.SUIVI_UTILISATION.toString(), EMPTY_STRING);
        mapEmpty.put(EnumComposantR07.SUIVI_DUREE_VIE.toString(), EMPTY_STRING);
        mapEmpty.put(EnumComposantR07.LISTE_FOURNISSEUR.toString(), EMPTY_STRING);
        mapEmpty.put(EnumLienNomenclatureR07.ALT.toString(), EMPTY_STRING);
        mapEmpty.put(EnumLienNomenclatureR07.SUIVANT_BESOIN.toString(), EMPTY_STRING);
        mapEmpty.put(EnumLienNomenclatureR07.QUANTITE_CUMULEE.toString(), EMPTY_STRING);
        mapEmpty.put(EnumLienNomenclatureR07.PANACHAGE.toString(), EMPTY_STRING);
        mapEmpty.put(EnumLienNomenclatureR07.QUANTITE.toString(), EMPTY_STRING);
        mapEmpty.put(EnumChangeR07.ID_CR.toString(), EMPTY_STRING);
        mapEmpty.put(EnumChangeR07.REVISION_CR.toString(), EMPTY_STRING);
        mapEmpty.put(EnumChangeR07.TYPE_CR.toString(), EMPTY_STRING);
        mapEmpty.put(EnumChangeR07.NOM_CR.toString(), EMPTY_STRING);
        return mapEmpty;
    }

    private void fillEmptyCrColumns(final Map<String, Object> mapTemporary) {
        mapTemporary.put(EnumChangeR07.ID_CR.toString(), EMPTY_STRING);
        mapTemporary.put(EnumChangeR07.REVISION_CR.toString(), EMPTY_STRING);
        mapTemporary.put(EnumChangeR07.TYPE_CR.toString(), EMPTY_STRING);
        mapTemporary.put(EnumChangeR07.NOM_CR.toString(), EMPTY_STRING);
    }

    /**
     * Fills the remaining CN columns with empty cells.<br>
     * If there are [0..cnMax[ columns, but on this line there are 'n' CNs (n&lt;cnMax) then provide colNum=n=cnList.size().<br>
     * As a result, all [n..cnMax[ columns will be filled with empty cells
     *
     * @param mapTemporary the map to fill
     * @param colStart     the last non-empty CN column
     */
    private void fillEmptyCnColumns(final Map<String, Object> mapTemporary, final int colStart) {
        for (int colNum = colStart + 1; colNum <= nbMaxChangeNotice; colNum++) {
            // For some reason, the printer expects [1..n] instead of [0..n[
            // So here since all [1..n] columns contain some real CNs, we need to fill all ]n..cnMax] columns with blanks !
            mapTemporary.put(EnumChangeR07.ID_CN.toString() + "_" + colNum, EMPTY_STRING);
            mapTemporary.put(EnumChangeR07.REVISION_CN.toString() + "_" + colNum, EMPTY_STRING);
            mapTemporary.put(EnumChangeR07.TYPE_CN.toString() + "_" + colNum, EMPTY_STRING);
            mapTemporary.put(EnumChangeR07.NOM_CN.toString() + "_" + colNum, EMPTY_STRING);
        }
    }

    private Map<String, Object> makeMap(final SnecmaLine<?, ?> line) {
        Map<String, Object> map = new HashMap<>();
        mapReceptor(map, line);
        mapRepere(map, line);
        mapComponent(map, line);
        return map;
    }

    private void mapReceptor(final Map<String, Object> map, final SnecmaLine<?, ?> line) {
        map.put(EnumRecepteurR07.ID_RECEPTEUR.toString(), line.getIDReceptor());
        map.put(EnumRecepteurR07.REVISION_RECEPTEUR.toString(), line.getRevisionReceptor());
        map.put(EnumRecepteurR07.NOM_RECEPTEUR.toString(), line.getNameReceptor());
    }

    private void mapRepere(final Map<String, Object> map, final SnecmaLine<?, ?> line) {
        map.put(EnumRepereR07.REPERE_FONCTIONNEL.toString(), line.getRepereName());
        map.put(EnumRepereR07.REVISION_REPERE_FONCTIONNEL.toString(), line.getRepereRevision());
        map.put(EnumRepereR07.RESPONSABLE_REPERE.toString(), line.getResponsableRepere());
        map.put(EnumRepereR07.SUIVI_MONTAGE.toString(), LanguageHelper.french(line.getSuiviMontage())); // French
    }

    private void mapComponent(final Map<String, Object> map, final SnecmaLine<?, ?> line) {
        map.put(EnumComposantR07.REF_FABRICANT.toString(), line.getIDComponent());
        map.put(EnumComposantR07.REVISION_REFERENCE.toString(), line.getIDRevisionComponent());
        map.put(EnumComposantR07.TYPE_REFERENCE.toString(), line.getTypeReference()); // French
        map.put(EnumComposantR07.NOM_REFERENCE.toString(), line.getNameReference());
        map.put(EnumComposantR07.REFERENCE_CONCEPTEUR.toString(), line.getReferenceConceptor());
        map.put(EnumComposantR07.REFERENCE_TRANSPOSEE.toString(), line.getReferenceTransposed());
        map.put(EnumComposantR07.REFERENCE_ASSOCIEE.toString(), line.getReferenceAssociated());
        map.put(EnumComposantR07.RESPONSABLLE_CONCEPTION.toString(), line.getResponsableConception());
        map.put(EnumComposantR07.ARTICLE_GESTION.toString(), line.getArticleGestion());
        map.put(EnumComposantR07.CLASSEMENT.toString(), line.getClassement());
        map.put(EnumComposantR07.SUIVI_UTILISATION.toString(), LanguageHelper.french(line.getSuiviUtilisation())); // French
        map.put(EnumComposantR07.SUIVI_DUREE_VIE.toString(), LanguageHelper.french(line.getSuiviDureeVie())); // French
        map.put(EnumComposantR07.LISTE_FOURNISSEUR.toString(), StringHelper.implode(",  ", line.getSuppliersList()));
        map.put(EnumLienNomenclatureR07.ALT.toString(), line.getALT());
        map.put(EnumLienNomenclatureR07.SUIVANT_BESOIN.toString(), line.getSB());
        map.put(EnumLienNomenclatureR07.QUANTITE_CUMULEE.toString(), line.getQuantiteCumulee());
        map.put(EnumLienNomenclatureR07.PANACHAGE.toString(), LanguageHelper.french(line.getPanachage()));
        map.put(EnumLienNomenclatureR07.QUANTITE.toString(), line.getQuantite());
    }

    private Map<String, Object> addChangeNoticeWithoutCR(final Collection<TcChangeNoticeRevision> changeNoticeList,
                                                         final Map<? extends String, ?> structureMap) {
        Map<String, Object> mapTemporary = new HashMap<>(structureMap); // Structural map is copied to keep the content the same throughout all the lines
        int changeNum = 0;
        for (TcChangeNoticeRevision changeNoticeRevision : changeNoticeList) {
            addChangeNoticeAtPosition(mapTemporary, changeNoticeRevision, changeNum++);
        }
        fillEmptyCrColumns(mapTemporary); // Fill unused CR column with blanks
        fillEmptyCnColumns(mapTemporary, changeNoticeList.size()); // Fill all remaining CN columns with blanks
        return mapTemporary;
    }

    private void addChangeRequestAndChangeNotice(final Map<String, Object> map, final TcChangeRequestRevision changeRequesteRevision,
                                                 final Collection<TcChangeNoticeRevision> cnList) {
        map.put(EnumChangeR07.ID_CR.toString(), changeRequesteRevision.getItemId());
        map.put(EnumChangeR07.REVISION_CR.toString(), changeRequesteRevision.getRevisionId());
        map.put(EnumChangeR07.TYPE_CR.toString(), LanguageHelper.french(changeRequesteRevision.getTypeModSupportProcess()));
        map.put(EnumChangeR07.NOM_CR.toString(), changeRequesteRevision.getChangeName());
        int posCn = 0;
        for (TcChangeNoticeRevision changeNoticeRevision : cnList) {
            addChangeNoticeAtPosition(map, changeNoticeRevision, posCn++);
        }
    }

    /**
     * Add a CN to the CN columns.<br>
     * If there are [0..cnMax[ columns, puts the CN in the nth column (n<cnMax).<br>
     */
    private void addChangeNoticeAtPosition(final Map<String, Object> mapTemporary, final TcChangeNoticeRevision changeNoticeRevision,
                                           final int changeNum) {
        int realPos = changeNum + 1; // For some reason, the printer expects [1..n] instead of [0..n[
        mapTemporary.put(EnumChangeR07.ID_CN.toString() + "_" + realPos, changeNoticeRevision.getItemId());
        mapTemporary.put(EnumChangeR07.REVISION_CN.toString() + "_" + realPos, changeNoticeRevision.getRevisionId());
        mapTemporary.put(EnumChangeR07.TYPE_CN.toString() + "_" + realPos, LanguageHelper.french(changeNoticeRevision.getTypeModification()));
        mapTemporary.put(EnumChangeR07.NOM_CN.toString() + "_" + realPos, changeNoticeRevision.getChangeName());
    }

    private void fillerMapDataEffectivity(final Map<String, Object> mapTemporary, final boolean isConf1) {
        mapTemporary.put(EnumRecepteurR07.EFFECTIVITE.toString(), isConf1 ? conf1Name : conf2Name);
        mapTemporary.put(EnumChangeR07.CONFIGURATION_CONCERNED.toString(), isConf1);
    }

    private void fillerMapDataDifference(final Map<String, Object> mapTemporary, final boolean isExclusif, final Map<EnumMapDelta, Boolean> mapMod) {
        if (isExclusif) {
            mapTemporary.put(EnumMapDelta.ALL_LINE_DIFFERENCE.toString(), true);
        } else {
            for (Entry<EnumMapDelta, Boolean> elem : mapMod.entrySet()) {
                mapTemporary.put(elem.getKey().toString(), elem.getValue());
            }
            mapTemporary.put(EnumMapDelta.ALL_LINE_DIFFERENCE.toString(), false);
        }
    }

    private List<String> collectParamDifference() {
        List<String> lstParamDiff = new ArrayList<>();
        lstParamDiff.add(EnumMapDelta.ID_RECEPTEUR_ECART.toString());
        lstParamDiff.add(EnumMapDelta.NOM_RECEPTEUR_ECART.toString());
        lstParamDiff.add(EnumMapDelta.REPERE_FONCTIONNEL_ECART.toString());
        lstParamDiff.add(EnumMapDelta.RESPONSABLE_REPERE_ECART.toString());
        lstParamDiff.add(EnumMapDelta.SUIVI_MONTAGE_ECART.toString());
        lstParamDiff.add(EnumMapDelta.REF_FABRICANT_ECART.toString());
        lstParamDiff.add(EnumMapDelta.TYPE_REFERENCE_ECART.toString());
        lstParamDiff.add(EnumMapDelta.NOM_REFERENCE_ECART.toString());
        lstParamDiff.add(EnumMapDelta.REFERENCE_CONCEPTEUR_ECART.toString());
        lstParamDiff.add(EnumMapDelta.REFERENCE_TRANSPOSEE_ECART.toString());
        lstParamDiff.add(EnumMapDelta.REFERENCE_ASSOCIEE_ECART.toString());
        lstParamDiff.add(EnumMapDelta.RESPONSABLE_CONCEPTION_ECART.toString());
        lstParamDiff.add(EnumMapDelta.ARTICLE_GESTION_ECART.toString());
        lstParamDiff.add(EnumMapDelta.CLASSEMENT_ECART.toString());
        lstParamDiff.add(EnumMapDelta.SUIVI_UTILISATION_ECART.toString());
        lstParamDiff.add(EnumMapDelta.SUIVI_DUREE_VIE_ECART.toString());
        lstParamDiff.add(EnumMapDelta.LISTE_FOURNISSEUR_ECART.toString());
        lstParamDiff.add(EnumMapDelta.ALT_ECART.toString());
        lstParamDiff.add(EnumMapDelta.SUIVI_BESOIN_ECART.toString());
        lstParamDiff.add(EnumMapDelta.QUANTITE_CUMULEE_ECART.toString());
        lstParamDiff.add(EnumMapDelta.PANACHAGE_ECART.toString());
        lstParamDiff.add(EnumMapDelta.QUANTITE_ECART.toString());
        lstParamDiff.add(EnumMapDelta.ALL_LINE_DIFFERENCE.toString());
        return lstParamDiff;
    }
}
