package com.capgemini.reports.definition.r23report;

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.workspace.bom.TcBomObject;
import com.capgemini.framework.teamcenter.object.pom.workspace.bom.TcBomObjectFactory;
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.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.reports.bomlineholder.BOMLineHolder;
import com.capgemini.reports.bomlineholder.BOMLineHolderFactory;
import com.capgemini.reports.bomlineholder.processor.DefaultAltOrDetBehaviour;
import com.capgemini.reports.definition.r23report.visitors.R23Visitor;
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.filter.AllIsGoodFilter;
import com.capgemini.reports.optimized.r23report.OptimizedR23Folder;
import com.capgemini.reports.printing.PrintParameters;
import com.capgemini.reports.reports.parameters.EffectivityDate;
import com.capgemini.reports.reports.parameters.MonoMulti;
import com.capgemini.reports.reports.parameters.ProductUnit;
import com.capgemini.reports.teamcenter.resolution.structureservice.IStructureServiceCaller;
import com.capgemini.reports.teamcenter.resolution.structureservice.MultiStructureServiceCaller;
import com.capgemini.reports.teamcenter.resolution.structureservice.TripleStructureServiceCaller;
import com.teamcenter.soa.client.model.ModelObject;
import com.teamcenter.soa.client.model.strong.Form;
import com.teamcenter.soa.client.model.strong.PSBOMView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;

import static com.capgemini.framework.teamcenter.object.pom.status.StatusEnum.*;

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

  private static final AllIsGoodFilter ACCEPT_ALL = new AllIsGoodFilter();

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

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

    if (!hasMoreThanOneEffectiveUnit(session, execution, productUnit, tcProduct, tcProductRevision, printParameters)) {
      throw new InvalidUserInputException(UserInputErrorEnum.NO_DATA_EXTRACTED_FOR_CRITERIA);
    }

    executionStrategy.print(printParameters, execution);

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

  private boolean hasMoreThanOneEffectiveUnit(final TcSession session, final ExecutionR23 execution, final ProductUnit productUnit, final TcProduct tcProduct, final TcProductRevision tcProductRevision, final PrintParameters printParameters) throws KurtException {
    boolean hasMoreThanOneEffectiveUnit = false;
    for (TcUnitRevision unitRevision : unitToProcess(tcProductRevision, execution.isWithFrozenUnit(),execution.getProductUnitForUnit(), execution.getProductUnitFromUnit(), execution.getNbDaysSinceFrozenStr())) {
      int unitNumber = unitRevision.unitNumber();
      // Get revision of the unit for usage in the report name
      String unitRev = unitRevision.getUnitRev();
      // Get unit frozen information for usage in the report name
      boolean isFrozen = false;
      if(unitRevision.hasStatus(J3A, J3B, FROZEN, FROZEN_IN_PROCESS)) {
        isFrozen = true;
      }
      LOGGER.debug("Unit [{}] Revision [{}] is Frozen ? = [{}]", unitNumber, unitRev, isFrozen);
      ProductUnit subProductUnit = new ProductUnit(productUnit.getProductID(), unitNumber);
      ExecutionR23 subExecution = new ExecutionR23(subProductUnit, execution.getGeneric(), execution.getMonoMulti(), execution.getEffectivityDate(), execution.isSuperModelBOM(),
              execution.isWithFrozenUnit(), execution.getProductUnitForUnit(), execution.getProductUnitFromUnit(), execution.getNbDaysSinceFrozenStr());
      subExecution.setProductLine(tcProductRevision.ligneProduit());
      subExecution.setUnitRevision(unitRev);
      subExecution.setFrozenUnit(isFrozen);
      try {
        printParameters.addSubReport("R23-" + unitNumber, prepareData(session, subExecution, tcProduct, unitNumber));
        hasMoreThanOneEffectiveUnit = true;
      }
      catch (KurtException e) {
        LOGGER.warn(e.getMessage(), e);
      }
    }
    return hasMoreThanOneEffectiveUnit;
  }

  private List<TcUnitRevision> unitToProcess(final TcProductRevision tcProductRevision, boolean isWithFrozenUnit, ProductUnit productUnitForUnit, ProductUnit productUnitFromUnit, String nbDaysSinceFrozenStr) throws KurtException {
    List<TcUnitRevision> unitsToProcess = tcProductRevision.r23UnitRevisions(isWithFrozenUnit, productUnitForUnit, productUnitFromUnit, nbDaysSinceFrozenStr);
    if (unitsToProcess.isEmpty()) {
      throw new InvalidUserInputException(UserInputErrorEnum.NO_DATA_COULD_BE_EXTRACTED_NO_FROZEN_OR_OPEN_UNIT_TO_THE_PRODUCT);
    }
    return unitsToProcess;
  }

  private PrintParameters prepareData(final TcSession session, final ExecutionR23 report, final TcProduct tcProduct, final int unitNumber) throws KurtException {
    LOGGER.debug("PrepareData started on: {}", report.getReportIdentifier().getReportTitle());

    TcBomObject topLineObject= new TcBomObjectFactory(session, report.getGeneric().getItem(session)).create();
    TcProduct productObject = new TcProduct(session, report.getProductUnit().getProduct(session));
    TcProductRevision productObjectRevision = productObject.revisions().get(0);

    EffectivityDate effectivityDate = report.getEffectivityDate();
    TcRevisionRuleProperties revisionRuleProperties = new TcRevisionRuleProperties(tcProduct, unitNumber, effectivityDate.isNow() ? null : effectivityDate.getDateTime());

    TcRevisionRule revisionRule = session.findRevRule(report.getRevisionRule());
    TcRevisionRulesConfiguration revRulesConfiguration = new TcRevisionRulesConfiguration(revisionRule, revisionRuleProperties);

    IStructureServiceCaller service;
    if (MonoMulti.MONO.equals(report.getMonoMulti())) {
      service = new TripleStructureServiceCaller(session);
    } else {
      service = new MultiStructureServiceCaller(session);
    }

    TcBomWindowConfiguration tcBomWindowConfiguration ;
    if (report.isSuperModelBOM()) {
      PSBOMView modelBom = productObjectRevision.bomViewRev();
      tcBomWindowConfiguration = new TcBomWindowConfiguration (topLineObject,modelBom,revRulesConfiguration);
    } else {
      tcBomWindowConfiguration = new TcBomWindowConfiguration(topLineObject, revRulesConfiguration);
    }


    try (TcBomWindow bomWindow = session.createBomWindow(tcBomWindowConfiguration)) {
      session.setObjectPropPolicy(report.getFullPolicy());

      BOMLineHolder unbuiltTopLine = service.resolve(bomWindow.topLine(), ACCEPT_ALL);
      List<Form> itemRevMaster = unbuiltTopLine.getAllItemRevMasters();
      session.loadProperties(itemRevMaster.toArray(new ModelObject[itemRevMaster.size()]));

      BOMLineHolderFactory.buildTopBom(session, unbuiltTopLine, ACCEPT_ALL);
      DefaultAltOrDetBehaviour<Object, Object> aBOMLineHolderProcessor = new DefaultAltOrDetBehaviour<>(session, effectivityDate.isNow() ? null : effectivityDate.getDateTime());
      unbuiltTopLine.process(aBOMLineHolderProcessor);
      PrintParameters printParameters = report.subReportParameters();
      unbuiltTopLine.freeze();

      new R23Visitor(unitNumber).constructDataMaps(session, printParameters, unbuiltTopLine);

      LOGGER.debug("PrepareData ended on: {}", report.getReportIdentifier().getReportTitle());
      return printParameters;
    }
    catch (EmptyNomenclatureException e) {
      switch (report.getGeneric().getType()) {
        case GENERIC:
        case GENERIC_ASSEMB:
          throw new KurtException("Empty Bom for Unit " + unitNumber, e);
        default:
          throw new KurtException("The TC Nomenclature Resolution returned null", e);
      }
    }
    finally {
      session.emptyPolicy();
    }
  }
}
