package com.capgemini.reports.definition.r24report;

import com.capgemini.framework.teamcenter.authent.TcSession;
import com.capgemini.framework.teamcenter.exceptions.EmptyNomenclatureException;
import com.capgemini.framework.teamcenter.exceptions.KurtException;
import com.capgemini.framework.teamcenter.object.pom.status.StatusEnum;
import com.capgemini.framework.teamcenter.object.pom.workspace.bom.TcRevisionRule;
import com.capgemini.framework.teamcenter.object.pom.workspace.bom.TcRevisionRuleProperties;
import com.capgemini.framework.teamcenter.object.pom.workspace.bom.generic.TcGeneric;
import com.capgemini.framework.teamcenter.object.pom.workspace.bom.generic.TcGenericPv;
import com.capgemini.framework.teamcenter.object.pom.workspace.bom.product.TcProduct;
import com.capgemini.framework.teamcenter.object.pom.workspace.bom.product.TcProductRevision;
import com.capgemini.framework.teamcenter.object.pom.workspace.bom.unit.TcUnitRevision;
import com.capgemini.framework.teamcenter.object.runtime.TcBomWindow;
import com.capgemini.framework.teamcenter.object.runtime.TcBomWindowConfiguration;
import com.capgemini.framework.teamcenter.object.runtime.TcRevisionRulesConfiguration;
import com.capgemini.framework.teamcenter.object.types.SnecmaObjectTypes;
import com.capgemini.framework.teamcenter.queries.saved.modelobject.ItemRevisionQuery;
import com.capgemini.reports.bomlineholder.BOMLineHolder;
import com.capgemini.reports.documentationobjects.DocNorm;
import com.capgemini.reports.documentationobjects.DocNormStandard;
import com.capgemini.reports.documentationobjects.DocTechnic;
import com.capgemini.reports.exceptions.InvalidUserInputException;
import com.capgemini.reports.exceptions.NoConfChangesException;
import com.capgemini.reports.exceptions.UserInputErrorEnum;
import com.capgemini.reports.execution.IDataReportPreparator;
import com.capgemini.reports.execution.strategies.IExecutionStrategy;
import com.capgemini.reports.optimized.r24report.OptimizedR24Folder;
import com.capgemini.reports.printing.PrintParameters;
import com.capgemini.reports.releasestatus.RevisionStatusAndDate;
import com.capgemini.reports.reports.parameters.ProductUnit;
import com.capgemini.reports.snecmaobjects.admin.RFAdminRevision;
import com.capgemini.reports.snecmaobjects.generic.GenericRevision;
import com.capgemini.reports.snecmaobjects.part.AbstractPart;
import com.capgemini.reports.snecmaobjects.part.PartAeroRevision;
import com.capgemini.reports.snecmaobjects.rf.RFGenericRevision;
import com.capgemini.reports.teamcenter.ObjectInfoHelper;
import com.capgemini.reports.teamcenter.ObjectInfoLoader;
import com.capgemini.reports.teamcenter.resolution.structureservice.SingleStructureServiceCaller;
import com.google.common.base.Optional;
import com.google.common.collect.FluentIterable;
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 net.lingala.zip4j.exception.ZipException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 * This class will allow to prepare data and findAll back data for making report R24.
 */
@Slf4j
@EqualsAndHashCode
public class DataReportPreparatorR24 implements IDataReportPreparator<ExecutionR24> {
  private static final long serialVersionUID = 3714231249482402544L;

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

  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 String REALISATION_ACHIEVEMENT = "Réalisation / Achievement";

  @Override
  public String print(TcSession session, ExecutionR24 execution, IExecutionStrategy executionStrategy) throws KurtException, ZipException {
    ProductUnit productUnit = execution.getProductUnit();
    TcProduct tcProduct = new TcProduct(session, productUnit.getProduct(session));
    TcProductRevision tcProductRevision = tcProduct.revisions().get(0);
    execution.setProductLine(tcProductRevision.ligneProduit());
    String typeProduit = tcProductRevision.productType();
    PrintParameters printParameters = execution.reportParameters();

    if (new OptimizedR24Folder().configurationHasNotChanged(productUnit)) {
      throw new NoConfChangesException(UserInputErrorEnum.NO_CONFIGURATION_CHANGES.getMsgEn());
    }

    int unitNumber = productUnit.getUnit();
    Optional<TcUnitRevision> tcUnitRevision = tcProductRevision.unitRevision(unitNumber);
    if (!tcUnitRevision.isPresent()) {
      throw new InvalidUserInputException(UserInputErrorEnum.PRODUCT_AND_UNIT_DOES_NOT_EXIST);
    }
    TcRevisionRulesConfiguration revisionRulesConfiguration = revisionRulesConfiguration(session, execution, tcProduct, unitNumber);

    session.setObjectPropPolicy(execution.getFullPolicy());

    List<TcGenericPv> tcGenericPvs = tcUnitRevision.get().genericPvs();
    List<TcGeneric> tcGenerics = FluentIterable.from(tcGenericPvs).filter(TcGeneric.class).toList();
    int genericsDontExistForPU = 0;
    for (TcGeneric generic : tcGenerics) {
      try {
        ExecutionR24 subExecution = new ExecutionR24(execution.getProductUnit(), generic.getItem(), execution.getTypeImpression(), execution.getFrenchInput());
        subExecution.setProductLine(tcProductRevision.ligneProduit());
        subExecution.setTypeProduit(typeProduit);
        subExecution.setClausePropriete(ObjectInfoLoader.getClausePropriete(typeProduit, session));
        printParameters.addSubReport("R24-" + generic.getItem().get_item_id(), prepareData(session, subExecution, generic, revisionRulesConfiguration));
      }
      catch (EmptyNomenclatureException e) {
        genericsDontExistForPU++;
        log.warn("No Bom for generic:" + generic.getItem().get_item_id() + " for PU:" + productUnit.getProductID() + "/" + productUnit.getUnitStr());
      }
    }
    if (genericsDontExistForPU == tcGenerics.size()) {
      throw new InvalidUserInputException(UserInputErrorEnum.NO_GENERIC_FOUND_FOR_THIS_PU);
    }

    executionStrategy.print(printParameters, execution);
    return executionStrategy.getReportFileName(execution) + printParameters.getOuputFileExtension();
  }

  private TcRevisionRulesConfiguration revisionRulesConfiguration(final TcSession session, final ExecutionR24 report, final TcProduct productObject, int unit) throws KurtException {
    TcRevisionRuleProperties revisionRuleProperties = new TcRevisionRuleProperties(productObject, unit);
    TcRevisionRule revisionRule = session.findRevRule(report.getRevisionRule());
    return new TcRevisionRulesConfiguration(revisionRule, revisionRuleProperties);
  }

  private PrintParameters prepareData(final TcSession session, final ExecutionR24 report, final TcGeneric tcGeneric, final TcRevisionRulesConfiguration revisionRulesConfiguration) throws KurtException {
    LOGGER.debug("PrepareData started on: {}", report.getReportIdentifier().getReportTitle());
    TcBomWindowConfiguration bomWindowConfiguration = new TcBomWindowConfiguration(tcGeneric, revisionRulesConfiguration);
    try (TcBomWindow bomWindow = session.createBomWindow(bomWindowConfiguration)) {
      BOMLineHolder rfGenericBom = new SingleStructureServiceCaller(session).resolve(bomWindow.topLine(), new R24BOMLineExplorer());

      // Treatment of the RFGenericBom
      ItemRevision rfGeneric = rfGenericBom.getItemRevision();
      report.setRfGenericRev(new RFGenericRevision(session, rfGeneric));

      for (BOMLineHolder currentChild : rfGenericBom.getBomChilds()) {
        ItemRevision itemRevChild = currentChild.getItemRevision();
        String childType = itemRevChild.getTypeObject().getName();
        if (SnecmaObjectTypes.PART_AERO_REVISION.equals(childType)) {
          report.getPartNumberRevList().add(new PartAeroRevision(session, itemRevChild));
        }
        else if (SnecmaObjectTypes.DESCRIPTIF_REVISION.equals(childType)) {
          retrieveDocNormAndDocNormStandardLists(session, itemRevChild, report);
          retrieveDocTechnicList(session, itemRevChild, report);
        }
        else if (SnecmaObjectTypes.RF_ADMIN_REVISION.equals(childType)) {
          report.setRfAdminRev(new RFAdminRevision(session, itemRevChild));
          report.setFrontiereAvion(report.getRfAdminRev().getFrontiereAvion());
          report.setFrontiereCooperant(report.getRfAdminRev().getFrontiereCooperant());
        }
      }

      ItemRevision generivRev = (ItemRevision) bomWindow.topLine().get_bl_line_object();
      String typeGeneric = generivRev.getTypeObject().getName();
      if (SnecmaObjectTypes.GENERIC_REVISION.equals(typeGeneric)) {
        report.setGenericRev(new GenericRevision(session, generivRev));
      }
      else {
        throw new KurtException("Object should be a Generic but is a: " + typeGeneric);
      }

      // Specific treatment of DocTechnic
      filterDocTechnic(report);
      filterForDefinitionCertifiee(report);
      PrintParameters printParameters = report.subReportParameters();
      new R24Printer().constructDataMaps(session, printParameters, report);
      LOGGER.debug("PrepareData ended on: {}", report.getReportIdentifier().getReportTitle());
      return printParameters;
    }
  }

  private void filterForDefinitionCertifiee(final ExecutionR24 report) {
    if (report.isDefCertifiee()) {
      List<DocTechnic> transitList = new ArrayList<>();
      for (DocTechnic docTechnic : report.getDocTechnicList()) {
        // These values are forbidden
        if ("Définition / Design".equals(docTechnic.getTypeDocTechnic1())) {
          transitList.add(docTechnic);
        }
      }
      report.getDocTechnicList().clear();
      report.getDocTechnicList().addAll(transitList);
    }
  }

  /**
   * 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 ExecutionR24 report) {
    return report.getDocNormList().isEmpty() && report.getDocNormStandardList().isEmpty() && report.getDocTechnicList().isEmpty();
  }

  /**
   * 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
   */
  private void retrieveDocNormAndDocNormStandardLists(final TcSession session,
                                                      final ItemRevision itemRevision, final ExecutionR24 report) {
    List<Item> referenceDoclist = AbstractPart.returnReferenceDocuments(session, itemRevision);
    if (!referenceDoclist.isEmpty()) {
      for (Item docItem : referenceDoclist) {
        if (docItem == null) {
          continue;
        }
        String docType = docItem.getTypeObject().getName();
        String docId = docItem.get_item_id();
        if (SnecmaObjectTypes.DOC_NORM.equals(docType)) {
          ItemRevision selectedDocRev = getDocNormAndDocNormStandardRevisions(session, docId);
          report.docNormList.add(new DocNorm(session, selectedDocRev));
        }
        else if (SnecmaObjectTypes.DOC_NORM_STANDARD.equals(docType)) {
          ItemRevision selectedDocRev = getDocNormAndDocNormStandardRevisions(session, docId);
          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;
    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}.
   */
  private void retrieveDocTechnicList(final TcSession session, final ItemRevision partItemRevision, final ExecutionR24 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
   */
  private void filterDocTechnic(final ExecutionR24 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);
  }
}
