package com.capgemini.reports.definition.r19report;

import com.capgemini.framework.teamcenter.authent.TcSession;
import com.capgemini.framework.teamcenter.exceptions.KurtException;
import com.capgemini.framework.teamcenter.object.SnecmaObjectProperties;
import com.capgemini.framework.teamcenter.object.types.SnecmaObjectTypes;
import com.capgemini.reports.definition.r19report.ExecutionR19.ReportDataR19;
import com.capgemini.reports.documentationobjects.*;
import com.capgemini.reports.exceptions.InvalidInputException;
import com.capgemini.reports.exceptions.InvalidUserInputException;
import com.capgemini.reports.exceptions.NotFoundException;
import com.capgemini.reports.execution.IDataReportPreparator;
import com.capgemini.reports.execution.strategies.IExecutionStrategy;
import com.capgemini.reports.printing.PrintParameters;
import com.capgemini.reports.snecmaobjects.AbstractSnecmaObject;
import com.capgemini.reports.snecmaobjects.SnecmaObjectFactory;
import com.capgemini.reports.snecmaobjects.part.AbstractPart;
import com.capgemini.reports.teamcenter.ObjectInfoHelper;
import com.capgemini.reports.teamcenter.queries.DirectQuery;
import com.capgemini.reports.teamcenter.queries.TypeQuery;
import com.teamcenter.soa.client.model.ModelObject;
import com.teamcenter.soa.client.model.strong.Item;
import com.teamcenter.soa.client.model.strong.ItemRevision;
import com.teamcenter.soa.client.model.strong.RevisionRule;
import com.teamcenter.soa.client.model.strong.WorkspaceObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;


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

    private static final String OBJECT_NOT_FOUND_PREFIX = "Object not found with UID '";
    private static final String OBJECT_NOT_FOUND_SUFFIX = "'";
    private static final int LEVEL = 1;

    /**
     * Construct a map which contain folder use to extract parts referencing document. This function is specific for Report 19.
     */
    static Map<String, List<String>> buildMapRelationTypeFilter(final String documentType) throws KurtException {
        Map<String, List<String>> map = new HashMap<>();
        List<String> relation = new ArrayList<>();

        if (documentType.isEmpty()) {
            throw new KurtException("Document type musn't be emtpy to construct map relation type and filter");
        }

        if (SnecmaObjectTypes.DOC_DRAWING_REVISION.equals(documentType)) {
            relation.add(SnecmaObjectProperties.TC_IS_REPRESENTED_BY);
            map.put(documentType, relation);
        } else if (SnecmaObjectTypes.DOC_NORM.equals(documentType) || SnecmaObjectTypes.DOC_NORM_STANDARD.equals(documentType)) {
            relation.add(SnecmaObjectTypes.FOLDER_REFERENCED_DOCS);
            map.put(documentType, relation);
        } else if (SnecmaObjectTypes.DOC_TECHNIC_REVISION.equals(documentType)) {
            relation.add(SnecmaObjectProperties.FOLDER_PV_REACH_DOCS);
            relation.add(SnecmaObjectProperties.FOLDER_PV_ITAR_DOCS);
            relation.add(SnecmaObjectProperties.FOLDER_PV_DEFINITION_DOCS);
            map.put(documentType, relation);
        }
        return map;
    }

    /**
     * Construct a list containing autorized part type for a given document type.
     */
    static List<String> getAuthorizedPartTypes(final String documentType) throws InvalidInputException {
        List<String> authorizedTpes = new ArrayList<>();
        if (documentType == null || documentType.isEmpty()) {
            throw new InvalidInputException("Document type may not be emtpy to construct list authorized types");
        }
        if (SnecmaObjectTypes.DOC_DRAWING_REVISION.equals(documentType)) {
            authorizedTpes.add(SnecmaObjectTypes.DET_REVISION);
            authorizedTpes.add(SnecmaObjectTypes.PART_AERO_REVISION);
            authorizedTpes.add(SnecmaObjectTypes.PART_NO_AERO_REVISION);
        } else if (SnecmaObjectTypes.DOC_NORM.equals(documentType)) {
            authorizedTpes.add(SnecmaObjectTypes.DET_REVISION);
            authorizedTpes.add(SnecmaObjectTypes.PART_AERO_REVISION);
            authorizedTpes.add(SnecmaObjectTypes.PART_MAT_REVISION);
            authorizedTpes.add(SnecmaObjectTypes.PART_NO_AERO_REVISION);
        } else if (SnecmaObjectTypes.DOC_NORM_STANDARD.equals(documentType)) {
            authorizedTpes.add(SnecmaObjectTypes.STANDARD_REVISION);
        } else if (SnecmaObjectTypes.DOC_TECHNIC_REVISION.equals(documentType)) {
            authorizedTpes.add(SnecmaObjectTypes.ALTERNATE_REVISION);
            authorizedTpes.add(SnecmaObjectTypes.DET_REVISION);
            authorizedTpes.add(SnecmaObjectTypes.PART_AERO_REVISION);
            authorizedTpes.add(SnecmaObjectTypes.PART_NO_AERO_REVISION);
            authorizedTpes.add(SnecmaObjectTypes.PART_MAT_REVISION);
            authorizedTpes.add(SnecmaObjectTypes.STANDARD_REVISION);
        }
        return authorizedTpes;
    }

    /**
     * Construct a list containing descriptif revision type for document types DocNorm and DocTechnic only
     */
    static List<String> getAuthorizedDescriptifType(final String documentType) throws InvalidInputException {
        List<String> authorizedTypes = new ArrayList<>();
        if (documentType == null || documentType.isEmpty()) {
            throw new InvalidInputException("Document type may not be emtpy to construct list authorized types");
        }
        if (SnecmaObjectTypes.DOC_NORM.equals(documentType) || SnecmaObjectTypes.DOC_TECHNIC_REVISION.equals(documentType)) {
            authorizedTypes.add(SnecmaObjectTypes.DESCRIPTIF_REVISION);
        }
        return authorizedTypes;
    }

    @Override
    public String print(TcSession session, ExecutionR19 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 ExecutionR19 report) throws KurtException {
        LOGGER.info("PrepareData started for : " + report.getReportIdentifier().getReportCode());
        session.setObjectPropPolicy(report.getFullPolicy());

        // STEP 1 : Obtain document
        WorkspaceObject modelObject;
        try {
            modelObject = new DirectQuery<WorkspaceObject>(session).searchByUID(report.getDocumentUID());
        } catch (NotFoundException e) {
            throw new InvalidUserInputException(OBJECT_NOT_FOUND_PREFIX + report.getDocumentUID() + OBJECT_NOT_FOUND_SUFFIX, e);
        }

        // STEP 2 : Obtain the list of all ItemRevision where the document is referenced
        // Build filter map and relation for current document
        String documentType = modelObject.getTypeObject().getName();
        Map<String, List<String>> relationAndTypesFilter = buildMapRelationTypeFilter(documentType);
        List<ItemRevision> itemsRevisions = session.whereReferenced(modelObject, LEVEL, relationAndTypesFilter);
        LOGGER.debug("List of item revision where the doc is referenced contains {} objects", itemsRevisions.size());

        // STEP 3 : Filter only parts referencing the doc
        List<String> authorizedPartTypes = getAuthorizedPartTypes(documentType);
        List<ItemRevision> partFilteringResult = ObjectInfoHelper.filteringObjectsReferencer(session, itemsRevisions, authorizedPartTypes);
        LOGGER.debug("Number of parts filtered : {}", partFilteringResult.size());

        // Convert ItemRevision corresponding to Parts to AbstractPart then sort it by item_id and revision_id
        List<AbstractPart> abstractPartList = new ArrayList<>();
        for (ItemRevision itemRevision : partFilteringResult) {
            AbstractPart theSnecmaObject = (AbstractPart) SnecmaObjectFactory.build(session, itemRevision);
            abstractPartList.add(theSnecmaObject);
        }
        Collections.sort(abstractPartList, new PartIdRevComparator());

        // STEP 4 : For DocNorm and DocTechnic only we use of a specific revision rule to ascend from Descriptif referencing the doc and find the Generic and Generic Assemb using it
        // Filter Descriptif Revision referencing the doc
        List<String> authorizedDescriptifType = getAuthorizedDescriptifType(documentType);
        List<ItemRevision> descriptifFilteringResult = filteringObjectsDescription(session, itemsRevisions, authorizedDescriptifType);
        LOGGER.debug("Number Descriptif filtered : {}", descriptifFilteringResult.size());

        // Get the R19 specific revision rule to get Generic and Generic Assemb using the Descriptif as input
        List<RevisionRule> revisionRuleList = TypeQuery.getRevisionRule(session, report.getRevisionRule());
        List<ItemRevision> generics = new ArrayList<>();
        if (revisionRuleList == null || revisionRuleList.size() == 0) {
            LOGGER.warn("The revision rules \"{}\" has not been found in Teamcenter! The Generic and GenericAssemb object will not be managed.", report.getRevisionRule());
        } else {
            LOGGER.debug("The revision rules \"{}\" will be used.", report.getRevisionRule());
            RevisionRule reportRevisionRule = revisionRuleList.get(0);

            for (ItemRevision descriptifRevision : descriptifFilteringResult) {
                // With level 2 whereUsed we get RF Generic Revision, Generic Revision and Generic Assemb Revision objects
                List<ItemRevision> genericList = session.whereUsed(descriptifRevision, 2, reportRevisionRule);
                if (genericList != null && genericList.size() != 0) {
                    List<String> idList = new ArrayList<>();
                    // We keep only one revision by Generic or Generic Assemb. RF Generic Revision are ignored.
                    for (ItemRevision generic : genericList) {
                        session.loadProperties(generic, "item_id", "object_name", "object_type");
                        String objectType = generic.get_object_type();
                        String itemId = generic.get_item_id();
                        LOGGER.debug("Object item_id : {}, object_name : {}, object_type : {}", itemId, generic.get_object_name(), generic.get_object_type());
                        if ("Generic Revision".equals(objectType) || "Generic Assemb Revision".equals(objectType)) {
                            if (!idList.contains(itemId)) {
                                idList.add(itemId);
                                generics.add(generic);
                                LOGGER.debug("Object item_id : {} selected.", itemId);
                            }
                        }
                    }
                }
            }
            LOGGER.debug("Total number of Generic/Generic Assemb found : {}", generics.size());
        }

        // STEP 5 : Prepare data for ReportDataR19 and R19Printer
        // Convert ItemRevision corresponding to Generic and Generic Assemb to AbstractSnecmaObject
        List<AbstractSnecmaObject> abstractGenericRevisionList = new ArrayList<>();
        for (ItemRevision itemRevision : generics) {
            AbstractSnecmaObject abstractGenericRevision = SnecmaObjectFactory.build(session, itemRevision);
            abstractGenericRevisionList.add(abstractGenericRevision);
        }

        // Document
        AbstractDoc theDoc = buildDocument(session, modelObject);

        // Convert all objects (Parts and Generic/GenericAssemb to a list of AbstractSnecmaObject to include in ReportDataR19
        List <AbstractSnecmaObject> abstractSnecmaObjectList = new ArrayList<>();
        abstractSnecmaObjectList.addAll(abstractPartList);
        abstractSnecmaObjectList.addAll(abstractGenericRevisionList);

        ReportDataR19 dataR19 = new ReportDataR19(theDoc, abstractSnecmaObjectList);

        R19Printer printer = new R19Printer();
        PrintParameters printParameters = printer.makeMasterTemplate(report, dataR19);
        printer.constructDataMaps(session, printParameters, dataR19);

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

        return printParameters;
    }

    private AbstractDoc buildDocument(final TcSession session, final ModelObject modelObject) {
        AbstractDoc doc = null;
        if (SnecmaObjectTypes.DOC_DRAWING_REVISION.equals(modelObject.getTypeObject().getName())) {
            doc = new DocDrawing(session, (ItemRevision) modelObject);
        } else if (SnecmaObjectTypes.DOC_NORM.equals(modelObject.getTypeObject().getName())) {
            doc = new DocNorm(session, (Item) modelObject);
        } else if (SnecmaObjectTypes.DOC_NORM_STANDARD.equals(modelObject.getTypeObject().getName())) {
            doc = new DocNormStandard(session, (Item) modelObject);
        } else if (SnecmaObjectTypes.DOC_TECHNIC_REVISION.equals(modelObject.getTypeObject().getName())) {
            doc = new DocTechnic(session, (ItemRevision) modelObject);
        }
        return doc;
    }

    /**
     * Returns a list of object referencer answering to the specification <br>
     * Has statuts Def Approved And is the the highest sequence between object have the same item_id and revision_id .<br>
     * You must give a list objects referencing a document, a list object revision authorized and a list status authorized
     */

    public static List<ItemRevision> filteringObjectsDescription(final TcSession session, final List<ItemRevision> objectsToFilter, final List<String> authorizedTypes) {
        List<ItemRevision> results = new ArrayList<>();
        // getAuthorized types
        for (ItemRevision object : objectsToFilter) {
            LOGGER.debug("Object description type={} item_id={}", object.getTypeObject().getName(), object.get_item_id());
            if (authorizedTypes.contains(object.getTypeObject().getName())) {
                results.add(object);
            } else {
                LOGGER.info("Object description Rejected unauthorized type={} item_id={}", object.getTypeObject().getName(), object.get_item_id());
            }
        }
        return results;
    }
}
