package com.capgemini.reports.definition.r20report.printing;

import com.capgemini.framework.common.LanguageHelper;
import com.capgemini.framework.common.ResourceHelper;
import com.capgemini.framework.common.StringHelper;
import com.capgemini.framework.common.TeamcenterDateFormatter;
import com.capgemini.framework.teamcenter.authent.TcSession;
import com.capgemini.framework.teamcenter.exceptions.KurtException;
import com.capgemini.reports.changeobjects.UnitRevision;
import com.capgemini.reports.changeobjects.change.TcChangeRequestRevision;
import com.capgemini.reports.definition.r20report.ExecutionR20;
import com.capgemini.reports.definition.r20report.ExecutionR20.ReportDataR20;
import com.capgemini.reports.definition.r20report.visitors.R20Keys;
import com.capgemini.reports.definition.r20report.visitors.R20Keys.Page1;
import com.capgemini.reports.definition.r20report.visitors.R20Keys.Page4;
import com.capgemini.reports.documentationobjects.DocTechnic;
import com.capgemini.reports.exceptions.FileLoadingException;
import com.capgemini.reports.exceptions.InvalidInputException;
import com.capgemini.reports.exceptions.ReportPrintException;
import com.capgemini.reports.execution.mapconstructor.AbstractStaticTemplateMapConstructor;
import com.capgemini.reports.printing.PrintParameters;
import com.capgemini.reports.snecmaobjects.generic.AbstractGeneric;
import com.capgemini.reports.snecmaobjects.part.PartAeroRevision;
import com.capgemini.reports.snecmaobjects.product.ProductRevision;
import com.capgemini.reports.users.SnecmaUser;
import com.capgemini.reports.workflows.Task;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.Map.Entry;

/**
 * Printer of the R20 report.
 */
@Slf4j
@EqualsAndHashCode(callSuper = true)
public final class R20Printer extends AbstractStaticTemplateMapConstructor<ExecutionR20, ReportDataR20> {

    private static final long serialVersionUID = -5470510531792553291L;
    private static final String NONE = "(Aucun)";
    private static final String NO_GENERIC_FOUND = "(Aucun générique n'a pu être trouvé)";
    private static final String NOT_APPLICABLE = "(N/A)";
    private static final String WKF_VALIDATION_SO_UNIT = "Validation SO UNIT";
    private static final String COMMA_DELIMITER = ", ";

    private static final String SANS = "Sans";
    private static final String AVEC = "Avec";
    private static final String EMPTY_STRING = "";
    private static final String EXCEPTIONNEL = "Exceptionnel";
    private static final String NON_APPLICATION = "Non Application ";
    private static final String APPLICATION = "Application ";

    private static final String SUBREPORT_NOIMPACTS = "SUBREPORT_NOIMPACTS";
    private static final String SUBREPORT_IMPACTS = "SUBREPORT_IMPACTS";
    private static final String SUBREPORT_GENERIC = "SUBREPORT_GENERIC";
    private static final String SUBREPORT_P1 = "SUBREPORT_P1";
    private static final String SUBREPORT_P2 = "SUBREPORT_P2";
    private static final String SUBREPORT_P3 = "SUBREPORT_P3";
    private static final String SUBREPORT_P4 = "SUBREPORT_P4";

    private static final String JASPER_TEMPLATE_PAGE1 = "/Jasper/template/report_r20/report_r20_p1.jrxml";
    private static final String JASPER_TEMPLATE_PAGE2 = "/Jasper/template/report_r20/report_r20_p2.jrxml";
    private static final String JASPER_TEMPLATE_PAGE4 = "/Jasper/template/report_r20/report_r20_p4.jrxml";
    private static final String JASPER_TEMPLATE_PAGE2_GENERIC = "/Jasper/template/report_r20/page2/r20_p2_generics.jrxml";
    private static final String JASPER_TEMPLATE_PAGE2_IMPACTS = "/Jasper/template/report_r20/page2/r20_p2_impacts.jrxml";
    private static final String JASPER_TEMPLATE_PAGE2_NOIMPACTS = "/Jasper/template/report_r20/page2/r20_p2_noimpacts.jrxml";

    /**
     * Print the R20 report.
     *
     * @param mainReport the {@link PrintParameters} of the main report, to which the data and the sub-reports will be added
     * @param data       will allow to prepare data for the report
     * @throws ReportPrintException  if unable to setup the report's page templates
     * @throws FileLoadingException  if unable to read the report's page templates
     * @throws InvalidInputException if date could not be printed properly (page 3)
     */
    public static void print(final ReportDataR20 data, final PrintParameters mainReport)
            throws ReportPrintException, FileLoadingException, InvalidInputException {
        setupPage1(data, mainReport);
        setupPage2(data, mainReport);
        setupPage3(data, mainReport);
        setupPage4(data, mainReport);

        // Setup main report
        mainReport.addReportData(setupEmptyData());
        mainReport.addReportParameters(setupMainParameters(data));
        mainReport.addReportParameter(R20Keys.IS_CR_LIST_EMPTY, data.getTotalChangeRequest().isEmpty());
        mainReport.setReportTitle("Titre rapport principal");
    }

    private static void setupPage1(final ReportDataR20 data, final PrintParameters mainReport) throws FileLoadingException, ReportPrintException {
        // Setup page 1
        try (InputStream page1Stream = ResourceHelper.getResourceAsStream(JASPER_TEMPLATE_PAGE1)) {
            Collection<Map<Page1, ?>> page1Data = setupPage1Data(data);
            Map<Page1, ?> page1Params = new EnumMap<>(R20Keys.Page1.class);
            mainReport.addSubReport(SUBREPORT_P1, page1Stream, page1Data, page1Params);
        } catch (IOException e) {
            log.error("Unable to close template for Page 1 of the R20 report", e);
        }

    }

    private static void setupPage2(final ReportDataR20 data, final PrintParameters mainReport) throws ReportPrintException, FileLoadingException {
        // Setup template streams
        try (InputStream page2stream = ResourceHelper.getResourceAsStream(JASPER_TEMPLATE_PAGE2);
             InputStream page2GenericStream = ResourceHelper.getResourceAsStream(JASPER_TEMPLATE_PAGE2_GENERIC);
             InputStream page2ImpactStream = ResourceHelper.getResourceAsStream(JASPER_TEMPLATE_PAGE2_IMPACTS);
             InputStream pageNoImactStream = ResourceHelper.getResourceAsStream(JASPER_TEMPLATE_PAGE2_NOIMPACTS)) {
            // Setup print parameters
            PrintParameters page2 = PrintParameters.createSubReport(page2stream);
            page2.addSubReport(SUBREPORT_GENERIC, page2GenericStream, setupPage2GenericData(data), new EnumMap<>(R20Keys.Page2.Generics.class));
            page2.addSubReport(SUBREPORT_IMPACTS, page2ImpactStream, setupPage2ImpactsData(data), new EnumMap<>(R20Keys.Page2.Generics.class));
            page2.addSubReport(SUBREPORT_NOIMPACTS, pageNoImactStream, setupPage2NoImpactsData(data), new EnumMap<>(R20Keys.Page2.NoImpacts.class));
            page2.addReportData(setupEmptyData());
            page2.addReportParameters(new EnumMap<>(R20Keys.Page2.class));
            mainReport.addSubReport(SUBREPORT_P2, page2);
        } catch (IOException e) {
            log.warn("Exception encountered while closing input stream for R20 report", e);
        }
    }

    private static void setupPage3(final ReportDataR20 data, final PrintParameters mainReport)
            throws FileLoadingException, ReportPrintException, InvalidInputException {
        Page3TemplateGenerator page3TemplateGenerator = new Page3TemplateGenerator(data);
        PrintParameters page3 = page3TemplateGenerator.generatePrintParameters();
        mainReport.addSubReport(SUBREPORT_P3, page3);
    }

    private static void setupPage4(final ReportDataR20 data, final PrintParameters mainReport) throws FileLoadingException, ReportPrintException {
        List<Map<Page4, ?>> page4Data = setPage4Data(data);
        Map<Page4, ?> page4Params = new EnumMap<>(R20Keys.Page4.class);
        try (InputStream page4Stream = ResourceHelper.getResourceAsStream(JASPER_TEMPLATE_PAGE4)) {
            mainReport.addSubReport(SUBREPORT_P4, page4Stream, page4Data, page4Params);
        } catch (IOException e) {
            log.warn("Unable to close template for Page 4 of the R20 report", e);
        }
    }

    private static List<Map<String, ?>> setupEmptyData() {
        List<Map<String, ?>> list = new ArrayList<>();
        list.add(new HashMap<String, Object>());
        return list;
    }

    private static Map<R20Keys, Object> setupMainParameters(final ReportDataR20 data) throws FileLoadingException {
        Map<R20Keys, Object> parametersMap = new EnumMap<>(R20Keys.class);
        BufferedImage image = ResourceHelper.loadImage("/Jasper/images/Snecma_logo.png");
        ProductRevision product = data.getProduct();
        UnitRevision unitBefore = data.getUnitBefore();
        UnitRevision unitAfter = data.getUnitAfter();
        String productLine = product.getProductLine();
        String productdomain = LanguageHelper.french(product.getDomain());

        printDocGelData(parametersMap, unitAfter);

        printWorkflowData(parametersMap, unitAfter);

        // Put it all in the map
        parametersMap.put(R20Keys.SNECMA_LOGO, image);
        parametersMap.put(R20Keys.PRODUIT, product.getName());
        parametersMap.put(R20Keys.UNIT_AVANT, unitBefore.getName());
        parametersMap.put(R20Keys.UNIT_APRES, unitAfter.getName());
        parametersMap.put(R20Keys.EXCEPTIONNEL_AVANT, unitBefore.isExceptionnel() ? EXCEPTIONNEL : EMPTY_STRING);
        parametersMap.put(R20Keys.EXCEPTIONNEL_APRES, unitAfter.isExceptionnel() ? EXCEPTIONNEL : EMPTY_STRING);
        parametersMap.put(R20Keys.DOMAINE_PRODUIT_AVANT, productdomain);
        parametersMap.put(R20Keys.DOMAINE_PRODUIT_APRES, productdomain);
        parametersMap.put(R20Keys.LIGNE_PRODUIT_AVANT, productLine);
        parametersMap.put(R20Keys.LIGNE_PRODUIT_APRES, productLine);
        parametersMap.put(R20Keys.MATRICULE_MOTEUR, data.getMatriculeMoteur());
        parametersMap.put(R20Keys.CLASSE, data.getClasse().toString());
        parametersMap.put(R20Keys.MAJEUR_MINEUR, data.getMajorMinor().toString());
        parametersMap.put(R20Keys.CONJOINT_UNIQUE, data.getConjointUnique().toString());

        return parametersMap;
    }

    private static void printDocGelData(final Map<R20Keys, Object> parametersMap, final UnitRevision unitAfter) {
        // Obtain data from Doc Gel
        String docGelReference;
        String docGelIndice;
        DocTechnic docGel = unitAfter.getTechnicalDocGel();
        if (docGel != null && docGel.getTypeDocTechnic1() != null && docGel.getTypeDocTechnic1().contains("Document de gel")) {
            docGelReference = docGel.getReference();
            docGelIndice = docGel.getIndice();
        } else {
            docGelReference = NONE;
            docGelIndice = NONE;
        }
        parametersMap.put(R20Keys.IDENTIFIANT_DOC_GEL_UNIT_APRES, docGelReference);
        parametersMap.put(R20Keys.INDICE_DOCUMENT_GEL_UNIT_APRES, docGelIndice);
    }

    private static void printWorkflowData(final Map<R20Keys, Object> parametersMap, final UnitRevision unitAfter) {
        // Obtain data from workflow
        String wkfOwner = "";
        Calendar wkfCreationDate = null;
        String wkfApprobator = "";
        Calendar wkfValidationDate = null;
        if (!unitAfter.getWorkflowsValidationUnitParSO().isEmpty()) {
            Task validationUnit = unitAfter.getWorkflowsValidationUnitParSO().get(0);
            wkfOwner = makeNameAndId(validationUnit.getOwningUser());
            wkfCreationDate = validationUnit.getCreationDate();
            for (Task childTask : validationUnit.getCompletedChildTasks()) {
                if (WKF_VALIDATION_SO_UNIT.equals(childTask.getName())) {
                    wkfApprobator = makeNameAndId(childTask.getLastModUser());
                    wkfValidationDate = childTask.getLastModDate();
                    break;
                }
            }
        }
        // Handle null wkf (none found)
        String wkfcreationDateStr = wkfCreationDate == null ? "" : TeamcenterDateFormatter.formatCalendarToFrench(wkfCreationDate);
        String wkfValidationDateStr = wkfValidationDate == null ? "" : TeamcenterDateFormatter.formatCalendarToFrench(wkfValidationDate);

        parametersMap.put(R20Keys.DATE_CREATION, wkfcreationDateStr);
        parametersMap.put(R20Keys.DATE_VALIDATION, wkfValidationDateStr);
        parametersMap.put(R20Keys.NOM_CREATEUR_WORKFLOW, wkfOwner);
        parametersMap.put(R20Keys.NOM_APPROBATEUR_TACHE, wkfApprobator);
    }

    private static String makeNameAndId(final SnecmaUser owner) {
        return owner.getName() + " (" + owner.getUserId() + ")";
    }

    /**
     * Sets up the a table containing all the Modifications applied to form the unit After.<br>
     * The table is fist sorted by Application (LMR before regular mods) then by Change ID
     */
    private static List<Map<Page1, ?>> setupPage1Data(final ReportDataR20 data) {
        List<Map<R20Keys.Page1, ?>> changeRequestList = new ArrayList<>();
        for (TcChangeRequestRevision changeRequest : data.getTotalChangeRequestLMR()) {
            String approbationSO = changeRequest.getApprobationSO();
            if (approbationSO == null) {
                approbationSO = EMPTY_STRING;
            }
            Map<R20Keys.Page1, Object> lmrMap = new EnumMap<>(R20Keys.Page1.class);
            lmrMap.put(R20Keys.Page1.CHANGE_REQUEST_APPLI, NON_APPLICATION);
            lmrMap.put(R20Keys.Page1.CHANGE_REQUEST_ID, changeRequest.getItemId());
            lmrMap.put(R20Keys.Page1.CHANGE_REQUEST_NOM, changeRequest.getChangeName());
            lmrMap.put(R20Keys.Page1.CHANGE_REQUEST_NO_APPRO_SO, approbationSO);
            changeRequestList.add(lmrMap);
        }
        for (TcChangeRequestRevision changeRequest : data.getTotalChangeRequestNotLMR()) {
            String approbationSO = changeRequest.getApprobationSO();
            if (approbationSO == null) {
                approbationSO = EMPTY_STRING;
            }
            Map<R20Keys.Page1, Object> lmrMap = new EnumMap<>(R20Keys.Page1.class);
            lmrMap.put(R20Keys.Page1.CHANGE_REQUEST_APPLI, APPLICATION);
            lmrMap.put(R20Keys.Page1.CHANGE_REQUEST_ID, changeRequest.getItemId());
            lmrMap.put(R20Keys.Page1.CHANGE_REQUEST_NOM, changeRequest.getChangeName());
            lmrMap.put(R20Keys.Page1.CHANGE_REQUEST_NO_APPRO_SO, approbationSO);
            changeRequestList.add(lmrMap);
        }
        if (changeRequestList.isEmpty()) {
            changeRequestList.add(new EnumMap<R20Keys.Page1, String>(R20Keys.Page1.class));
        }
        return changeRequestList;
    }

    /**
     * Sets up the a table containing all the generics taking part of a Modification with rupture (from unit After or LMR) with their PartAero Before (if any) and After (if any).
     *
     * @param data the executionR20 to findAll the data
     * @return the table data as a List of Map
     */
    private static List<Map<R20Keys.Page2.Generics, ?>> setupPage2GenericData(final ReportDataR20 data) {
        List<Map<R20Keys.Page2.Generics, ?>> tableContent = new ArrayList<>();
        for (AbstractGeneric generic : data.getTotalGenericsBeforeLMRWithRupture().keySet()) {
            putOneLineInPage2GenericsData(data, tableContent, generic);
        }
        for (AbstractGeneric generic : data.getTotalGenericsAfterWithRupture().keySet()) {
            putOneLineInPage2GenericsData(data, tableContent, generic);
        }
        return tableContent;
    }

    private static void putOneLineInPage2GenericsData(final ReportDataR20 data, final List<Map<R20Keys.Page2.Generics, ?>> tableContent,
                                                      final AbstractGeneric generic) {
        PartAeroRevision partBefore = data.getGenericsAndPartNumbersBefore().get(generic);
        PartAeroRevision partAfter = data.getGenericsAndPartNumbersAfter().get(generic);
        String idPartAvant = partBefore == null ? EMPTY_STRING : partBefore.getItemId();
        String idPartApres = partAfter == null ? EMPTY_STRING : partAfter.getItemId();
        Map<R20Keys.Page2.Generics, String> line = new EnumMap<>(R20Keys.Page2.Generics.class);
        line.put(R20Keys.Page2.Generics.GENERIC_AVEC_RUPTURE, generic.getName());
        line.put(R20Keys.Page2.Generics.ID_PART_AVANT, idPartAvant);
        line.put(R20Keys.Page2.Generics.ID_PART_APRES, idPartApres);
        tableContent.add(line);
    }

    /**
     * Sets up the a table containing all the generics which take part in at least one Modification with Rupture (from unit After or LMR), regardless of if they also take part in a modif without rupture.
     *
     * @param data the executionR20 to findAll the data
     * @return the table data as a List of Map
     */
    private static List<Map<R20Keys.Page2.Impacts, ?>> setupPage2ImpactsData(final ReportDataR20 data) {
        List<Map<R20Keys.Page2.Impacts, ?>> tableContent = new ArrayList<>();
        for (Entry<AbstractGeneric, Set<TcChangeRequestRevision>> genericEntry : data.getTotalGenericsBeforeLMRWithRupture().entrySet()) {
            List<String> listOfModifs = new ArrayList<>();
            for (TcChangeRequestRevision changeRequest : genericEntry.getValue()) {
                listOfModifs.add(changeRequest.getItemId());
                if (changeRequest.getApprobationSO() != null) {
                    listOfModifs.add(changeRequest.getApprobationSO());
                }
            }
            Map<R20Keys.Page2.Impacts, String> line = new EnumMap<>(R20Keys.Page2.Impacts.class);
            line.put(R20Keys.Page2.Impacts.NOM_GENERIC, genericEntry.getKey().getName());
            line.put(R20Keys.Page2.Impacts.LIST_MODIFICATIONS_GENERIC, StringHelper.implodeList(COMMA_DELIMITER, listOfModifs));
            tableContent.add(line);
        }
        for (Entry<AbstractGeneric, Set<TcChangeRequestRevision>> genericEntry : data.getTotalGenericsAfterWithRupture().entrySet()) {
            List<String> listOfModifs = new ArrayList<>();
            for (TcChangeRequestRevision changeRequest : genericEntry.getValue()) {
                listOfModifs.add(changeRequest.getItemId());
                if (changeRequest.getApprobationSO() != null) {
                    listOfModifs.add(changeRequest.getApprobationSO());
                }
            }
            Map<R20Keys.Page2.Impacts, String> line = new EnumMap<>(R20Keys.Page2.Impacts.class);
            line.put(R20Keys.Page2.Impacts.NOM_GENERIC, genericEntry.getKey().getName());
            line.put(R20Keys.Page2.Impacts.LIST_MODIFICATIONS_GENERIC, StringHelper.implodeList(COMMA_DELIMITER, listOfModifs));
            tableContent.add(line);
        }
        return tableContent;
    }

    /**
     * Sets up the a table containing all the generics which take part in at least one Modification without Rupture (from unit After or LMR), regardless of if they also take part in a modif with rupture.
     *
     * @param data the executionR20 to findAll the data
     * @return the table data as a List of Map
     */
    private static List<Map<R20Keys.Page2.NoImpacts, ?>> setupPage2NoImpactsData(final ReportDataR20 data) {
        List<Map<R20Keys.Page2.NoImpacts, ?>> tableContent = new ArrayList<>();
        for (Entry<AbstractGeneric, Set<TcChangeRequestRevision>> genericEntry : data.getTotalGenericsBeforeLMRWithoutRupture().entrySet()) {
            List<String> listOfModifs = new ArrayList<>();
            for (TcChangeRequestRevision changeRequest : genericEntry.getValue()) {
                listOfModifs.add(changeRequest.getItemId());
                if (changeRequest.getApprobationSO() != null) {
                    listOfModifs.add(changeRequest.getApprobationSO());
                }
            }
            Map<R20Keys.Page2.NoImpacts, String> line = new EnumMap<>(R20Keys.Page2.NoImpacts.class);
            line.put(R20Keys.Page2.NoImpacts.NOM_GENERIC, genericEntry.getKey().getName());
            line.put(R20Keys.Page2.NoImpacts.LIST_MODIFICATIONS_GENERIC, StringHelper.implodeList(COMMA_DELIMITER, listOfModifs));
            tableContent.add(line);
        }
        for (Entry<AbstractGeneric, Set<TcChangeRequestRevision>> genericEntry : data.getTotalGenericsAfterWithoutRupture().entrySet()) {
            List<String> listOfModifs = new ArrayList<>();
            for (TcChangeRequestRevision changeRequest : genericEntry.getValue()) {
                listOfModifs.add(changeRequest.getItemId());
                if (changeRequest.getApprobationSO() != null) {
                    listOfModifs.add(changeRequest.getApprobationSO());
                }
            }
            Map<R20Keys.Page2.NoImpacts, String> line = new EnumMap<>(R20Keys.Page2.NoImpacts.class);
            line.put(R20Keys.Page2.NoImpacts.NOM_GENERIC, genericEntry.getKey().getName());
            line.put(R20Keys.Page2.NoImpacts.LIST_MODIFICATIONS_GENERIC, StringHelper.implodeList(COMMA_DELIMITER, listOfModifs));
            tableContent.add(line);
        }
        return tableContent;
    }

    private static List<Map<Page4, ?>> setPage4Data(final ReportDataR20 data) {
        List<Map<Page4, ?>> tableContent = new ArrayList<>();
        for (AbstractGeneric generic : data.getTotalGenerics()) {
            Map<R20Keys.Page4, Object> lmrMap = new EnumMap<>(R20Keys.Page4.class);
            PartAeroRevision partBefore = data.getGenericsAndPartNumbersBefore().get(generic);
            PartAeroRevision partAfter = data.getGenericsAndPartNumbersAfter().get(generic);
            String idPartAvant = partBefore == null ? EMPTY_STRING : partBefore.getItemId();
            String idPartApres = partAfter == null ? EMPTY_STRING : partAfter.getItemId();
            String impact = evaluateImpact(data, generic);

            // Put the data in the table
            lmrMap.put(R20Keys.Page4.NOM_GENERIQUE, generic.getName());
            lmrMap.put(R20Keys.Page4.IMPACT_PN, impact);
            lmrMap.put(R20Keys.Page4.ID_PART_AVANT, idPartAvant);
            lmrMap.put(R20Keys.Page4.ID_PART_APRES, idPartApres);
            tableContent.add(lmrMap);
        }
        if (tableContent.isEmpty()) {
            Map<R20Keys.Page4, Object> line = new EnumMap<>(R20Keys.Page4.class);
            line.put(R20Keys.Page4.NOM_GENERIQUE, NO_GENERIC_FOUND);
            line.put(R20Keys.Page4.IMPACT_PN, NOT_APPLICABLE);
            line.put(R20Keys.Page4.ID_PART_AVANT, NOT_APPLICABLE);
            line.put(R20Keys.Page4.ID_PART_APRES, NOT_APPLICABLE);
            tableContent.add(line);
        }
        return tableContent;
    }

    private static String evaluateImpact(final ReportDataR20 data, final AbstractGeneric generic) {
        String impact;
        if (data.getTotalGenericsAfterWithRupture().containsKey(generic) || data.getTotalGenericsBeforeLMRWithRupture().containsKey(generic)) {
            impact = AVEC;
        } else if (data.getTotalGenericsAfterWithoutRupture().containsKey(generic)
                || data.getTotalGenericsBeforeLMRWithoutRupture().containsKey(generic)) {
            impact = SANS;
        } else {
            impact = EMPTY_STRING;
        }
        return impact;
    }

    @Override
    public void constructDataMaps(TcSession session, final PrintParameters printParameters, final ReportDataR20 data) throws KurtException {
        long start = System.currentTimeMillis();
        print(data, printParameters);
        log.trace(EXECUTION_TIME_XXX_MS_FOR_YYY, System.currentTimeMillis() - start, "data filtering");
    }
}
