package com.capgemini.reports.snecmaobjects.part;

import com.capgemini.framework.teamcenter.authent.TcSession;
import com.capgemini.framework.teamcenter.object.pom.status.StatusEnum;
import com.capgemini.framework.teamcenter.object.types.SnecmaObjectTypes;
import com.capgemini.reports.releasestatus.RevisionStatusAndDate;
import com.capgemini.reports.snecmaobjects.AbstractSnecmaObject;
import com.capgemini.reports.teamcenter.ObjectInfoHelper;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.teamcenter.soa.client.model.ModelObject;
import com.teamcenter.soa.client.model.strong.Item;
import com.teamcenter.soa.client.model.strong.ItemRevision;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import static com.capgemini.framework.teamcenter.object.SnecmaObjectProperties.*;

/**
 * Abstract Class representing a Part.
 */
@Slf4j
public abstract class AbstractPart extends AbstractSnecmaObject {
    private static final long serialVersionUID = 3469538869012990375L;

    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractPart.class);

    public AbstractPart(final TcSession session, final ItemRevision itemRevision) {
        super(session, itemRevision);
    }

    /**
     * Return the list of the definition documents of a Part.<br/>
     * Via the pseudofolder &quot;Documents de d&eacute;finition&quot;.
     */
    public static List<ItemRevision> returnDefinitionDocuments(final TcSession session, final ItemRevision partRev) {
        List<ItemRevision> listDefinitionDoc = new ArrayList<>();
        session.loadProperties(partRev, PV_DEFINITION_DOCS);
        ModelObject[] definitionDocObjects = partRev.getPropertyObject(PV_DEFINITION_DOCS).getModelObjectArrayValue();
        session.loadProperties(definitionDocObjects);
        for (ModelObject definitionDocObject : definitionDocObjects) {
            if (definitionDocObject == null) {
                log.warn("A Null ModelObject was obtained from the pseudo-folder '_PV_definition_docs' of a Part.");
                continue;
            }
            listDefinitionDoc.add((ItemRevision) definitionDocObject);
        }
        return listDefinitionDoc;
    }

    /**
     * Return the list of the reference documents of a Part.<br/>
     * Via the pseudofolder &quot;Documents de r&eacute;f&eacute;rence&quot;.
     */
    public static List<Item> returnReferenceDocuments(final TcSession session, final ItemRevision partRev) {
        List<Item> listReferenceDoc = new ArrayList<>();
        session.loadProperties(partRev, PV_REFERENCE_DOCS);
        ModelObject[] referenceDocObjects = partRev.getPropertyObject(PV_REFERENCE_DOCS).getModelObjectArrayValue();
        session.loadProperties(referenceDocObjects);
        for (ModelObject referenceDocObject : referenceDocObjects) {
            if (referenceDocObject == null) {
                log.warn("A Null ModelObject was obtained from the pseudo-folder '_PV_reference_docs' of a Part.");
                continue;
            }
            listReferenceDoc.add((Item) referenceDocObject);
        }
        return listReferenceDoc;
    }

    /**
     * Return the list of the represented documents of a Part.<br/>
     * Via the pseudofolder &quot;Represented by&quot;.
     */
    public static List<ItemRevision> returnRepresentedDocuments(final TcSession session, final ItemRevision partRev) {
        List<ItemRevision> listRepresentedDoc = new ArrayList<>();
        session.loadProperties(partRev, TC_IS_REPRESENTED_BY);
        ModelObject[] representedDocObjects = partRev.getPropertyObject(TC_IS_REPRESENTED_BY).getModelObjectArrayValue();
        session.loadProperties(representedDocObjects);
        for (ModelObject representedDocObject : representedDocObjects) {
            if (representedDocObject == null) {
                log.warn("A Null ModelObject was obtained from the pseudo-folder 'TC_Is_Represented_By' of a Part.");
                continue;
            }
            listRepresentedDoc.add((ItemRevision) representedDocObject);
        }
        return listRepresentedDoc;
    }

    public String getDesignationEN() {
        return getMasterFormRev().getPropertyObject(PV_DESIGNATION_EN).getStringValue();
    }

    public String getRefCompleteConcepteur() {
        return getMasterFormRev().getPropertyObject(PV_REF_COMPLETE_CONCEPTEUR).getStringValue();
    }

    public String getRefTransposeeSnecma() {
        return getMasterFormRev().getPropertyObject(PV_REF_TRANSPO_SNECMA).getStringValue();
    }

    public String getResponsableConception() {
        return getMasterFormRev().getPropertyObject(PV_RESPONSABLE_CONCEPTION).getStringValue();
    }

    public String getClassement() {
        return getMasterFormRev().getPropertyObject(PV_CLASSEMENT).getStringValue();
    }

    public String getSuiviUtilisation() {
        return getMasterFormRev().getPropertyObject(PV_SUIVI_UTILISATION).getStringValue();
    }

    public String getSuiviDureeVie() {
        return getMasterFormRev().getPropertyObject(PV_SUIVI_DUREE_VIE).getStringValue();
    }

    public double getDureeVie() {
        return getMasterFormRev().getPropertyObject(PV_DUREE_VIE).getIntValue();
    }

    public String getUniteDureeVie() {
        return getMasterFormRev().getPropertyObject(PV_UNITE_DUREE_VIE).getStringValue();
    }

    public Double getMass() {
        return getMasterFormRev().getPropertyObject(PV_MASSE_INDICATIVE).getDoubleValue();
    }

    public String getMassUnit() {
        return getMasterFormRev().getPropertyObject(PV_UNITE_MASSE).getStringValue();
    }

    public String getDocumentControle() {
        return getMasterFormRev().getPropertyObject(PV_DOCUMENT_CONTROLE).getStringValue();
    }

    public String getRefAssociee() {
        return getMasterFormRev().getPropertyObject(PV_REF_ASSOCIEE).getStringValue();
    }

    public String getSuiviDeveloppement() {
        return getMasterFormRev().getPropertyObject(PV_SUIVI_DEVELOPPEMENT).getStringValue();
    }

    public String getTypeProduit() {
        return getMasterFormRev().getPropertyObject(PV_TYPE_PRODUIT).getStringValue();
    }

    public String getExpirationDate() {
        session.loadProperties(getMasterFormRev(), PV4_EXPIRATION_DATE);
        return getMasterFormRev().getPropertyObject(PV4_EXPIRATION_DATE).getStringValue();
    }

    public String getMotClefEN() {
        String value = "";
        if(getSparePartsForm() != null) {
            session.loadProperties(getSparePartsForm(), PV_MOT_CLEF_EN);
            value = getSparePartsForm().getPropertyObject(PV_MOT_CLEF_EN).getStringValue();
        }
        return value;
    }

    public String getMotClefFR() {
        String value = "";
        if(getSparePartsForm() != null) {
            session.loadProperties(getSparePartsForm(), PV_MOT_CLEF_FR);
            value = getSparePartsForm().getPropertyObject(PV_MOT_CLEF_FR).getStringValue();
        }
        return value;
    }

    @Override
    public boolean isPart() {
        return true;
    }

    /**
     * This method is specific to R19 report
     * It gets DocNorms from "Documents de référence" pseudo-folder of the part.
     * It checks the value of pv_type_doc_norm properties and returns the pv_id_origine (or item_id if not found)
     * of all DMD type docs separated by a coma
     **/
    public String getDMD() {
        LOGGER.debug("Entering getDMD method");
        List<String> DMDList = new ArrayList<>();
        // Get content of "Documents de référence" pseudo-folder
        List<Item> documents = returnReferenceDocuments(session, itemRevision);
        LOGGER.debug("Number of reference documents : {}", documents.size());
        if (!documents.isEmpty()) {
            for (Item docItem : documents) {
                if (docItem == null) {
                    continue;
                }
                String docType = docItem.getTypeObject().getName();
                String docId = docItem.get_item_id();
                LOGGER.debug("Processing document : {} of type {}", docId, docType);
                if (SnecmaObjectTypes.DOC_NORM.equals(docType)) {
                    // Get the last revision of the DocNorm and check content of pv_type_doc_norm attribute from his master form rev
                    ItemRevision docNormRevision = getDocNormLastRevision(session, docItem);
                    if(docNormRevision != null) {
                        session.loadProperties(docNormRevision, "IMAN_master_form_rev");
                        ModelObject[] masterForm = docNormRevision.getPropertyObject("IMAN_master_form_rev").getModelObjectArrayValue();
                        session.loadProperties(masterForm, PV_ID_ORIGINE, PV_TYPE_DOC_NORM);
                        if (masterForm == null || masterForm.length == 0) {
                            LOGGER.warn("Error while retrieving master form on document {} this document will be ignored.", docId);
                            continue;
                        }
                        String typeDocNorm = masterForm[0].getPropertyObject(PV_TYPE_DOC_NORM).getStringValue();
                        LOGGER.debug("pv_type_doc_norm attribute value of doc {} : {}", docId, typeDocNorm);
                        if (typeDocNorm.contains(DMD_VALUE_OF_TYPE_DOCNORM)) {
                            LOGGER.debug("PV_ID_ORIGINE attribute value of doc {} : {}", docId, masterForm[0].getPropertyObject(PV_ID_ORIGINE).getStringValue());
                            if (masterForm[0].getPropertyObject(PV_ID_ORIGINE) == null || masterForm[0].getPropertyObject(PV_ID_ORIGINE).getStringValue().isEmpty() || masterForm[0].getPropertyObject(PV_ID_ORIGINE).getStringValue() == "") {
                                LOGGER.debug("docId added to list");
                                DMDList.add(docId);
                            } else {
                                LOGGER.debug("PV_ID_ORIGINE added to list");
                                DMDList.add(masterForm[0].getPropertyObject(PV_ID_ORIGINE).getStringValue());
                            }
                        }
                    }
                }
            }
        }
        LOGGER.debug("DMDList : {}", DMDList);
        return Joiner.on(",").join(DMDList);
    }

    /**
     * This method is specific to R19 report
     * It gets DocNorms from "Documents de référence" pseudo-folder of the part.
     * It checks the value of pv_type_doc_norm properties and returns the pv_id_origine (or item_id if not found)
     * of all DMF type docs separated by a coma
     **/
    public String getDMF() {
        LOGGER.debug("Entering getDMF method");
        List<String> DMFList = new ArrayList<>();
        // Get content of "Documents de référence" pseudo-folder
        List<Item> documents = returnReferenceDocuments(session, itemRevision);
        LOGGER.debug("Number of reference documents : {}", documents.size());
        if (!documents.isEmpty()) {
            for (Item docItem : documents) {
                if (docItem == null) {
                    continue;
                }
                String docType = docItem.getTypeObject().getName();
                String docId = docItem.get_item_id();
                LOGGER.debug("Processing document : {} of type {}", docId, docType);
                if (SnecmaObjectTypes.DOC_NORM.equals(docType)) {
                    // Get the last revision of the DocNorm and check content of pv_type_doc_norm attribute from his master form rev
                    ItemRevision docNormRevision = getDocNormLastRevision(session, docItem);
                    if(docNormRevision != null) {
                        session.loadProperties(docNormRevision, "IMAN_master_form_rev");
                        ModelObject[] masterForm = docNormRevision.getPropertyObject("IMAN_master_form_rev").getModelObjectArrayValue();
                        session.loadProperties(masterForm, PV_ID_ORIGINE, PV_TYPE_DOC_NORM);
                        if (masterForm == null || masterForm.length == 0) {
                            LOGGER.warn("Error while retrieving master form on document {} this document will be ignored.", docId);
                            continue;
                        }
                        String typeDocNorm = masterForm[0].getPropertyObject(PV_TYPE_DOC_NORM).getStringValue();
                        LOGGER.debug("pv_type_doc_norm attribute value of doc {} : {}", docId, typeDocNorm);
                        if (typeDocNorm.contains(DMF_VALUE_OF_TYPE_DOCNORM)) {
                            LOGGER.debug("PV_ID_ORIGINE attribute value of doc {} : {}", docId, masterForm[0].getPropertyObject(PV_ID_ORIGINE).getStringValue());
                            if (masterForm[0].getPropertyObject(PV_ID_ORIGINE) == null || masterForm[0].getPropertyObject(PV_ID_ORIGINE).getStringValue().isEmpty() || masterForm[0].getPropertyObject(PV_ID_ORIGINE).getStringValue() == "") {
                                LOGGER.debug("docId added to list");
                                DMFList.add(docId);
                            } else {
                                LOGGER.debug("PV_ID_ORIGINE added to list");
                                DMFList.add(masterForm[0].getPropertyObject(PV_ID_ORIGINE).getStringValue());
                            }
                        }
                    }
                }
            }
        }
        LOGGER.debug("DMFList : {}", DMFList);
        return Joiner.on(",").join(DMFList);
    }

    /**
     * This method determine the last revision of a docNorm/docNormStandard.
     *
     * @param session A TcSession instance
     * @param docItem   The id of the document
     * @return ItemRevision object representing the revision of the document
     **/
    private ItemRevision getDocNormLastRevision(final TcSession session, final Item docItem) {
        // Retrieve the revisions
        ModelObject[] revisions = docItem.get_revision_list();
        session.loadProperties(revisions);
        LOGGER.debug("Doc {} : number of rev in revision_list attribute = {}", docItem.get_item_id(), revisions.length);
        // Convert the List of ModelObject to a List of ItemRevision
        List<ItemRevision> listOfDocNormRev = new ArrayList<>();
        for(ModelObject rev : revisions) {
            if(rev != null) {
                listOfDocNormRev.add((ItemRevision) rev);
            } else {
                LOGGER.warn("The revision object returned by TC for Doc {} is null. It will be ignored in DMD and DMF columns.", docItem.get_item_id());
            }
        }

        // Try to get the revision with the latest release date
        if(listOfDocNormRev.size() > 0) {
            Optional<ItemRevision> selectedDocRev = ObjectInfoHelper.getRevisionFromStatusWithMostRecentReleaseDate(session, listOfDocNormRev, Calendar.getInstance(), StatusEnum.PUBLISHED, StatusEnum.FROZEN);
            if (selectedDocRev.isPresent()) {
                return selectedDocRev.get();
            } else {
                // If no release date found, get the revision with latest modification date
                RevisionStatusAndDate revAndStatus = ObjectInfoHelper.getRevisionAndStatusWithMostRecentModificationDate(listOfDocNormRev, Calendar.getInstance());
                return revAndStatus.getItemRevision();
            }
        } else {
            return null;
        }
    }
}
