package com.capgemini.reports.definition.r10report;

import com.capgemini.framework.common.LanguageHelper;
import com.capgemini.framework.common.StringHelper;
import com.capgemini.framework.common.TeamcenterDateFormatter;
import com.capgemini.framework.teamcenter.object.pom.status.TcReleaseStatus;
import com.capgemini.framework.teamcenter.object.types.SnecmaObjectTypes;
import com.capgemini.framework.teamcenter.resolution.filtering.TcTypeInclusionFilter;
import com.capgemini.reports.bomlineholder.BOMLineHolder;
import com.capgemini.reports.bomlineholder.BOMLineHolderHelper;
import com.capgemini.reports.definition.r10report.lines.*;
import com.capgemini.reports.documentationobjects.*;
import com.capgemini.reports.exceptions.InvalidInputException;
import com.capgemini.reports.exceptions.MalformedNomenclatureException;
import com.capgemini.reports.exceptions.NotFoundException;
import com.capgemini.reports.releasestatus.DefaultRevisionSelector;
import com.capgemini.reports.snecmaobjects.ISnecmaObject;
import com.capgemini.reports.snecmaobjects.descriptif.DescriptifRevision;
import com.capgemini.reports.snecmaobjects.part.AbstractPart;
import com.capgemini.reports.snecmaobjects.product.ProductRevision;
import lombok.extern.slf4j.Slf4j;

import java.util.*;

import static com.capgemini.framework.teamcenter.object.pom.status.StatusEnum.FROZEN;
import static com.capgemini.framework.teamcenter.object.pom.status.StatusEnum.PUBLISHED;
import static com.capgemini.framework.teamcenter.object.types.SnecmaObjectTypes.*;

/**
 * Extracts documents' Revision from their Master, stores their status in a Map.
 */
@Slf4j
public class DocumentExtractor implements IDocVisitor<Map<R10Keys, String>> {

    private static final String TYPE_DEFINITION_DOCUMENTS = "Definition documents";
    private static final String TYPE_REFERENCE_DOCUMENTS = "Reference documents";
    private final Calendar effectivityDate;
    private final Map<String, AbstractDoc> docCache;

    public DocumentExtractor(final Calendar effectivityDate, final Map<String, AbstractDoc> docCache) {
        this.effectivityDate = effectivityDate;
        this.docCache = docCache;
    }

    private static List<Map<R10Keys, String>> noDocuments() {
        List<Map<R10Keys, String>> list = new ArrayList<>();
        Map<R10Keys, String> emptyDoc = new EnumMap<>(R10Keys.class);
        emptyDoc.put(R10Keys.DOC_CREATION_DATE, StringHelper.EMPTY);
        emptyDoc.put(R10Keys.DOC_ID, StringHelper.EMPTY);
        emptyDoc.put(R10Keys.DOC_INDEX, StringHelper.EMPTY);
        emptyDoc.put(R10Keys.DOC_LINK_TYPE, StringHelper.EMPTY);
        emptyDoc.put(R10Keys.DOC_NAME, StringHelper.EMPTY);
        emptyDoc.put(R10Keys.DOC_NAME_ENG, StringHelper.EMPTY);
        emptyDoc.put(R10Keys.DOC_REVISION, StringHelper.EMPTY);
        emptyDoc.put(R10Keys.DOC_STATUS, StringHelper.EMPTY);
        emptyDoc.put(R10Keys.DOC_TYPE, StringHelper.EMPTY);
        list.add(emptyDoc);
        return list;
    }

    /**
     * Extract the content of each Document of a {@link R10ProductGeneric}.
     *
     * @param r10ProductGeneric the product/Generic line (the Generic holds the Documents)
     * @return a {@link List} of {@link Map}s
     * @throws MalformedNomenclatureException if nomenclature is not well-defined
     * @throws NotFoundException              if no descriptif is found below the RfGeneric of the Generic
     * @throws InvalidInputException          if generic bomLine is null
     */
    public List<Map<R10Keys, String>> extract(final R10ProductGeneric r10ProductGeneric) throws MalformedNomenclatureException, NotFoundException, InvalidInputException {
        // For generics, a Descriptif can be found as a bom Child, which owns some pseudo-folders containing the documents
        BOMLineHolder genericComponent = r10ProductGeneric.getGenericComponentBL();
        return extractDocsFromGeneric(genericComponent);
    }

    /**
     * Extract the content of each Document of a {@link R10PartRfPart}.
     *
     * @param partRfPart the Part/Rf Part/Part (the Part component holds the documents)
     * @return a {@link List} of {@link Map}s
     */
    public List<Map<R10Keys, String>> extract(final R10PartRfPart partRfPart) {
        // For Parts, the documents can be found directly in their pseudo-fodlers
        BOMLineHolder component = partRfPart.getPartComponentBL();
        AbstractPart part = (AbstractPart) component.getSnecmaObject();
        return extractDocsFromPart(part);
    }

    /**
     * Extract the content of each Document of a {@link R10Quad}.
     */
    public List<Map<R10Keys, String>> extract(final R10Quad quad) throws InvalidInputException, MalformedNomenclatureException, NotFoundException {
        ISnecmaObject component = quad.getComposant();
        if (component.isPart()) {
            AbstractPart part = (AbstractPart) component;
            return extractDocsFromPart(part);
        } else {
            BOMLineHolder genericComponent = quad.getComposantBL();
            return extractDocsFromGeneric(genericComponent);
        }
    }

    /**
     * Extract the content of each Document of a {@link R10ReceptorLine}.
     */
    public List<Map<R10Keys, String>> extract(final R10ReceptorLine receptorLine) throws InvalidInputException, MalformedNomenclatureException, NotFoundException {
        ISnecmaObject receptor = receptorLine.getRecepteur();
        if (receptor.isGeneric()) {
            return extractDocsFromGeneric(receptorLine.getRecepteurBL());
        } else {
            return noDocuments();
        }
    }

    /**
     * Extract the content of each Document of a {@link R10TopLine}.
     */
    public List<Map<R10Keys, String>> extract(final R10TopLine r10TopLine) throws InvalidInputException, MalformedNomenclatureException, NotFoundException {
        ISnecmaObject topComponent = r10TopLine.getBomLineObject();
        if (topComponent.isGeneric()) {
            return extractDocsFromGeneric(r10TopLine.getBomLine());
        } else if (topComponent.isProduct()) {
            return extractDocsFromProduct((ProductRevision) topComponent);
        } else {
            log.warn("Unknown behaviour when top object is neither a Generic nor a Product, but is a :" + topComponent.getType());
            return noDocuments();
        }
    }

    /**
     * Extract the content of each Document of a {@link R10Quad}.
     *
     * @param r10AltPart The Alt/PArt (the part holds the Documents)
     * @return a {@link List} of {@link Map}s
     */
    public List<Map<R10Keys, String>> extract(final R10AltPart r10AltPart) {
        AbstractPart component = r10AltPart.getChild();
        return extractDocsFromPart(component);
    }

    private List<Map<R10Keys, String>> extractDocsFromGeneric(final BOMLineHolder gen)
            throws InvalidInputException, MalformedNomenclatureException, NotFoundException {
        // For generics, a Descriptif can be found as a bom Child, which owns some pseudo-folders containing the documents
        BOMLineHolder rfGeneric = BOMLineHolderHelper.findRfGenericBL(gen);
        BOMLineHolder descriptifBL = BOMLineHolderHelper.findFirst(rfGeneric, SnecmaObjectTypes.DESCRIPTIF_REVISION);
        DescriptifRevision descriptif = (DescriptifRevision) descriptifBL.getSnecmaObject();

        // Extract the documents from the Descriptif
        List<AbstractDoc> docs = new ArrayList<>();
        docs.addAll(descriptif.getReferenceDocumentsOfTypes(
                new TcTypeInclusionFilter(DOC_NORM, DOC_NORM_STANDARD),
                new DefaultRevisionSelector(effectivityDate, FROZEN, PUBLISHED), docCache));
        docs.addAll(descriptif.getDefinitionDocumentsOfTypes(new TcTypeInclusionFilter(DOC_TECHNIC_REVISION), docCache));
        return extractDocContent(docs);
    }

    private List<Map<R10Keys, String>> extractDocsFromPart(final AbstractPart part) {
        List<AbstractDoc> docs = new ArrayList<>();
        docs.addAll(part.getReferenceDocumentsOfTypes(
                new TcTypeInclusionFilter(DOC_NORM, DOC_NORM_STANDARD),
                new DefaultRevisionSelector(effectivityDate, FROZEN, PUBLISHED), docCache));
        docs.addAll(part.getDefinitionDocumentsOfTypes(new TcTypeInclusionFilter(DOC_TECHNIC_REVISION), docCache));
        docs.addAll(part.getRepresentedDocumentsOfTypes(new TcTypeInclusionFilter(DOC_DRAWING_REVISION), docCache));
        return extractDocContent(docs);
    }

    /* ************** This Applies the Visitor on each documents. ******************/

    private List<Map<R10Keys, String>> extractDocsFromProduct(final ProductRevision product) {
        List<AbstractDoc> docs = new ArrayList<>(product.getDefinitionDocumentsOfTypes(
                new TcTypeInclusionFilter(DOC_TECHNIC_REVISION), docCache));
        return extractDocContent(docs);
    }

    /* ************** This section defines the Visitor behaviour on each documents. ******************/

    private List<Map<R10Keys, String>> extractDocContent(final List<AbstractDoc> docs) {
        if (docs.isEmpty()) {
            return noDocuments();
        }
        List<Map<R10Keys, String>> docContentList = new ArrayList<>();
        for (AbstractDoc doc : docs) {
            docContentList.add(doc.accept(this));
        }
        return docContentList;
    }

    @Override
    public Map<R10Keys, String> visit(final DocDrawing doc) {
        Map<R10Keys, String> map = new EnumMap<>(R10Keys.class);
        map.put(R10Keys.DOC_LINK_TYPE, TYPE_DEFINITION_DOCUMENTS);
        map.put(R10Keys.DOC_ID, doc.getDocId());
        map.put(R10Keys.DOC_REVISION, doc.getRevision());
        map.put(R10Keys.DOC_INDEX, doc.getIndicePlan());
        map.put(R10Keys.DOC_TYPE, "Représentation 2D"); // Devrait être "Représentation 2D / Drawing" mais on demande la partie FR...
        map.put(R10Keys.DOC_NAME, doc.getName());
        map.put(R10Keys.DOC_NAME_ENG, doc.getDesignationEn());
        List<TcReleaseStatus> status = doc.getStatus();
        map.put(R10Keys.DOC_STATUS, status.isEmpty() ? "" : status.get(0).getDisplayableName());
        map.put(R10Keys.DOC_CREATION_DATE, TeamcenterDateFormatter.formatCalendarToFrench(doc.getDateCreation()));
        return map;
    }

    @Override
    public Map<R10Keys, String> visit(final DocGeometry doc) {
        return null; // not used in R10
    }

    @Override
    public Map<R10Keys, String> visit(final DocNorm doc) {
        Map<R10Keys, String> map = new EnumMap<>(R10Keys.class);
        map.put(R10Keys.DOC_LINK_TYPE, TYPE_REFERENCE_DOCUMENTS);
        map.put(R10Keys.DOC_ID, doc.getDocId());
        map.put(R10Keys.DOC_REVISION, doc.getRevision());
        map.put(R10Keys.DOC_INDEX, "-");
        map.put(R10Keys.DOC_TYPE, LanguageHelper.french(doc.getTypeDocNorm()));
        map.put(R10Keys.DOC_NAME, doc.getItemName());
        map.put(R10Keys.DOC_NAME_ENG, doc.getDesignationEn());
        List<TcReleaseStatus> status = doc.getStatus();
        map.put(R10Keys.DOC_STATUS, status.isEmpty() ? "" : status.get(0).getDisplayableName());
        map.put(R10Keys.DOC_CREATION_DATE, TeamcenterDateFormatter.formatCalendarToFrench(doc.getDateCreation()));
        return map;
    }

    @Override
    public Map<R10Keys, String> visit(final DocNormStandard doc) {
        Map<R10Keys, String> map = new EnumMap<>(R10Keys.class);
        map.put(R10Keys.DOC_LINK_TYPE, TYPE_REFERENCE_DOCUMENTS);
        map.put(R10Keys.DOC_ID, doc.getDocId());
        map.put(R10Keys.DOC_REVISION, doc.getRevision());
        map.put(R10Keys.DOC_INDEX, "-");
        map.put(R10Keys.DOC_TYPE, "Norme pour acticles standards"); // Specification says to put "Norme pour acticles standards / Norm for Standard ItemsStringHelper.EMPTY, but also says to extract the french part ^^
        map.put(R10Keys.DOC_NAME, doc.getItemName());
        map.put(R10Keys.DOC_NAME_ENG, doc.getDesignationEn());
        List<TcReleaseStatus> status = doc.getStatus();
        map.put(R10Keys.DOC_STATUS, status.isEmpty() ? "" : status.get(0).getDisplayableName());
        map.put(R10Keys.DOC_CREATION_DATE, TeamcenterDateFormatter.formatCalendarToFrench(doc.getDateCreation()));
        return map;
    }

    @Override
    public Map<R10Keys, String> visit(final DocTechnic doc) {
        String docId = (doc.getIdOrigine() == null || doc.getIdOrigine().isEmpty()) ? doc.getDocId() : doc.getIdOrigine();
        Map<R10Keys, String> map = new EnumMap<>(R10Keys.class);
        map.put(R10Keys.DOC_LINK_TYPE, TYPE_DEFINITION_DOCUMENTS);
        map.put(R10Keys.DOC_ID, docId);
        map.put(R10Keys.DOC_REVISION, doc.getRevision());
        map.put(R10Keys.DOC_INDEX, doc.getIndice());
        map.put(R10Keys.DOC_TYPE, LanguageHelper.french(doc.getTypeDocTechnic1()) + " / " + LanguageHelper.french(doc.getTypeDocTechnic2()));
        map.put(R10Keys.DOC_NAME, doc.getName());
        map.put(R10Keys.DOC_NAME_ENG, doc.getDesignationEn());
        List<TcReleaseStatus> status = doc.getStatus();
        map.put(R10Keys.DOC_STATUS, status.isEmpty() ? "" : status.get(0).getDisplayableName());
        map.put(R10Keys.DOC_CREATION_DATE, TeamcenterDateFormatter.formatCalendarToFrench(doc.getDateCreation()));
        return map;
    }
}
