package com.capgemini.reports.definition.r15r16r17reports;

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.framework.teamcenter.queries.saved.modelobject.ItemRevisionQuery;
import com.capgemini.reports.documentationobjects.DocNorm;
import com.capgemini.reports.documentationobjects.DocNormStandard;
import com.capgemini.reports.documentationobjects.DocTechnic;
import com.capgemini.reports.execution.IDataReportPreparator;
import com.capgemini.reports.releasestatus.RevisionStatusAndDate;
import com.capgemini.reports.snecmaobjects.part.AbstractPart;
import com.capgemini.reports.teamcenter.ObjectInfoHelper;
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.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;

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

/**
 * Abstract Class whose goal is to prepare common data to R15 R16 R17 reports.
 *
 * @param <T> the report {@link AbstractExecutionR15R16R17}
 */
@Slf4j
@EqualsAndHashCode
public abstract class AbstractDataReportPreparatorR15R16R17<T extends AbstractExecutionR15R16R17<T>> implements IDataReportPreparator<T> {
    private static final long serialVersionUID = 4533353562306975227L;

    private static final String REC_DEFINITION = "REC-Définition de récupération / Recovery design";
    private static final String MCI_MASSE_CENTRE_DE_GRAVITE = "MCI-Masse, Centre de gravité et  inertie / Mass, gravity center and inertia";
    private static final int MINIMUM_SECURITY_LEVEL_FOR_WATERMARK = 3;
    private static final String PROP_PS_PARENTS = "ps_parents";
    private static final String REALISATION_ACHIEVEMENT = "Réalisation / Achievement";

    /**
     * Get the date in a specific format for Reports.
     *
     * @param calendar {@link Calendar} - a calendar
     * @return the {@link String} value
     */
    protected String getDateFormatDayMonthYear(final Calendar calendar) {
        SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy");
        return sdf.format(calendar.getTime());
    }

    /**
     * Returns true if the watermark should be present in the report.<br>
     * It should be present if any of the DocNorm or DocTechnic is considered as "sensible".
     *
     * @param report - the execution report which provide all informations for the report
     * @return true if the watermark should be present in the report
     */
    protected boolean isConfidential(final T report) {
        for (DocNorm docNorm : report.docNormList) {
            String niveauSensibilite = docNorm.getNiveauSensibilite();
            String sensibiliteSecurite = docNorm.getSensibiliteSecurite();
            log.debug("DocNorm " + docNorm.getDocId() + ": niveauSensibilite = " + niveauSensibilite + ", sensibiliteSecurite = " + sensibiliteSecurite);
            if (sensibiliteAttributesAreConfidential(niveauSensibilite, sensibiliteSecurite)) {
                return true;
            }
        }
        for (DocTechnic docTechnic : report.docTechnicList) {
            String niveauSensibilite = docTechnic.getNiveauSensibilite();
            String sensibiliteSecurite = docTechnic.getSensibiliteSecurite();
            log.debug("DocTechnic " + docTechnic.getDocId() + ": niveauSensibilite = " + niveauSensibilite + ", sensibiliteSecurite = " + sensibiliteSecurite);
            if (sensibiliteAttributesAreConfidential(niveauSensibilite, sensibiliteSecurite)) {
                return true;
            }
        }
        return false;
    }

    /**
     * This method check values of attributes ip_classification and pv_sensibilite_securite of a DocNorm or a DocTechnic.
     *
     * @param niveauSensibilite value of ip_classification attribute
     * @param sensibiliteSecurite value of pv_sensibilite_securite attribute
     * @return true if 'niveau de sensibilite' value contains 'C3' or C4' string
     * or if 'niveau de sensibilite' is empty or doesn't contain 'C3' or 'C4' but 'sensibilité securité' has a value of 3 or 4.
     * */
    protected boolean sensibiliteAttributesAreConfidential(String niveauSensibilite, String sensibiliteSecurite) {
        if (niveauSensibilite != null && (niveauSensibilite.contains("C3") || niveauSensibilite.contains("C4"))) {
            return true;
        } else {
            if (sensibiliteSecurite != null && !sensibiliteSecurite.isEmpty()
                    && Integer.valueOf(sensibiliteSecurite) >= MINIMUM_SECURITY_LEVEL_FOR_WATERMARK) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns true if this report execution generated an empty table of documents (no doc found).
     *
     * @param report - the execution report which provide all informations for the report
     * @return true if the table of documents is empty
     */
    public boolean isEmptyTable(final T report) {
        return report.getDocNormList().isEmpty() && report.getDocNormStandardList().isEmpty() && report.getDocTechnicList().isEmpty();
    }

    /**
     * Get the parents with the ps_parents property.<br>
     * Filter with a wanted type.
     *
     * @param session    {@link TcSession} - the tc session
     * @param refObject  {@link ItemRevision} - the reference object
     * @param typeWanted {@link SnecmaObjectTypes} the selected type we need
     * @return List<ItemRevision> the parents of the reference object
     */
    protected List<ItemRevision> getPsParentsOfPart(final TcSession session, final ItemRevision refObject, final String typeWanted) {
        List<ItemRevision> results = new ArrayList<>();
        session.loadProperties(refObject, PROP_PS_PARENTS);
        ModelObject[] parents = refObject.get_ps_parents();
        session.loadProperties(parents);
        for (ModelObject object : parents) {
            ItemRevision itemRev = (ItemRevision) object;
            if (typeWanted.equals(itemRev.getTypeObject().getName())) {
                results.add(itemRev);
            }
        }
        return results;
    }

    /**
     * Retrieve the {@link DocNorm} and the {@link DocNormStandard} in their respective {@link List}s.
     *
     * @param session      {@link TcSession} - the tc session
     * @param itemRevision {@link ItemRevision} - the object which possess all the documents
     * @param report       - the execution report which provide all informations for the report
     */
    protected void retrieveDocNormAndDocNormStandardLists(final TcSession session, final ItemRevision itemRevision, final T report) {
        List<Item> referenceDoclist = AbstractPart.returnReferenceDocuments(session, itemRevision);
        if (!referenceDoclist.isEmpty()) {
            for (Item docItem : referenceDoclist) {
                if (docItem == null) {
                    continue;
                }
                String docType = docItem.getTypeObject().getName();
                if (SnecmaObjectTypes.DOC_NORM.equals(docType)) {
                    ItemRevision selectedDocRev = getDocNormAndDocNormStandardRevisions(session, docItem.get_item_id());
                    report.docNormList.add(new DocNorm(session, selectedDocRev));
                } else if (SnecmaObjectTypes.DOC_NORM_STANDARD.equals(docType)) {
                    ItemRevision selectedDocRev = getDocNormAndDocNormStandardRevisions(session, docItem.get_item_id());
                    report.docNormStandardList.add(new DocNormStandard(session, selectedDocRev));
                }
            }
        }
    }

    private ItemRevision getDocNormAndDocNormStandardRevisions(final TcSession session, final String docId) {
        // Retrieve the revisions
        List<ItemRevision> listOfDocNormRev = new ItemRevisionQuery(session).searchID(docId).findAll();
        // Try to obtain the revision with the latest release date...
        Optional<ItemRevision> selectedDocRev = ObjectInfoHelper.getRevisionFromStatusWithMostRecentReleaseDate(session, listOfDocNormRev, Calendar.getInstance(), StatusEnum.PUBLISHED, StatusEnum.FROZEN);
        if (selectedDocRev.isPresent()) {
            return selectedDocRev.get();
        } else {
            // No release date found, now obtain the revision with latest modification date
            RevisionStatusAndDate revAndStatus = ObjectInfoHelper.getRevisionAndStatusWithMostRecentModificationDate(listOfDocNormRev, Calendar.getInstance());
            return revAndStatus.getItemRevision();
        }
    }

    /**
     * Retrieve the {@link DocTechnic} in a {@link List}.
     */
    protected void retrieveDocTechnicList(final TcSession session, final ItemRevision partItemRevision, final T report) {
        List<ItemRevision> technicDocObjects = AbstractPart.returnDefinitionDocuments(session, partItemRevision);
        if (!technicDocObjects.isEmpty()) {
            for (ItemRevision docTechnicRev : technicDocObjects) {
                if (docTechnicRev != null) {
                    report.docTechnicList.add(new DocTechnic(session, docTechnicRev));
                }
            }
        }
    }

    /**
     * Filter the DocTechnic with specific values on the type_doc_technic_1 and type_doc_technic_2 properties.
     *
     * @param report - the execution report which provide all informations for the report
     */
    protected void filterDocTechnic(final T report) {
        List<DocTechnic> transitList = new ArrayList<>();
        for (DocTechnic docTechnic : report.docTechnicList) {
            // These values are forbidden
            String typeDocTechnic1 = docTechnic.getTypeDocTechnic1();
            String typeDocTechnic2 = docTechnic.getTypeDocTechnic2();
            if (!(REALISATION_ACHIEVEMENT.equals(typeDocTechnic1)
                    && (MCI_MASSE_CENTRE_DE_GRAVITE.equals(typeDocTechnic2) || REC_DEFINITION.equals(typeDocTechnic2)))) {
                transitList.add(docTechnic);
            }
        }
        report.docTechnicList.clear();
        report.docTechnicList.addAll(transitList);
    }
}
