package com.capgemini.reports.definition.r13report.printer;

import com.capgemini.framework.common.LanguageHelper;
import com.capgemini.framework.common.StringHelper;
import com.capgemini.framework.teamcenter.object.pom.status.StatusEnum;
import com.capgemini.framework.teamcenter.object.pom.status.TcEffectivity;
import com.capgemini.framework.teamcenter.object.pom.status.TcReleaseStatus;
import com.capgemini.framework.teamcenter.object.types.SnecmaObjectTypes;
import com.capgemini.framework.teamcenter.resolution.filtering.TcTypeExclusionFilter;
import com.capgemini.reports.changeobjects.change.AbstractChangeObject;
import com.capgemini.reports.changeobjects.change.FPPRevision;
import com.capgemini.reports.changeobjects.change.TcChangeNoticeRevision;
import com.capgemini.reports.changeobjects.change.TcChangeRequestRevision;
import com.capgemini.reports.definition.r13report.R13Keys;
import com.capgemini.reports.definition.r13report.execution.ExecutionR13CR;
import com.capgemini.reports.execution.mapconstructor.IMapConstructor;
import com.capgemini.reports.reports.parameters.EffectivityDate;
import com.capgemini.reports.snecmaobjects.generic.AbstractGeneric;
import com.google.common.base.Joiner;
import lombok.EqualsAndHashCode;

import java.util.*;

import static com.capgemini.reports.changeobjects.change.AbstractChangeObject.getItemIdFromModelObjectSetToString;

/**
 * Printer of a R13 Report for CR objects.
 */
@EqualsAndHashCode(callSuper = true)
public class R13PrinterCR extends AbstractR13Printer<ExecutionR13CR, TcChangeRequestRevision> implements IMapConstructor<ExecutionR13CR, List<TcChangeRequestRevision>> {
  private static final long serialVersionUID = 7886687130935534458L;

  private static final String CN_DEPRECIE_REVISION = "CN Déprécié Revision";
  private static final String PV4_CN_DEPREC_REVISION = "PV4_CN_DEPRECRevision";
  private static final String EMPTY_STRING = "";
  private static final String SAUT_LIGNE = "\n";
  private static final String COMMA = " , ";

  @Override
  protected LinkedHashMap<AbstractChangeObject, Map<String, String>> getChangeObjectData(final TcChangeRequestRevision change, final Map<String, String> typeObjectParam) {
    Map<String, String> prefix = new HashMap<>(typeObjectParam);

    // Add FPP cell (optional)
    decoratePrefixWithFpp(change, prefix);

    // CR properties
    prefix.put(R13Keys.TYPE.toString(), LanguageHelper.french(change.getTypeModSupportProcess()));
    prefix.put(R13Keys.CR.EFFECTIVITES.toString(), getEffectivityValue(change));
    prefix.put(R13Keys.CR.PARTS_CHANGE_REQUEST.toString(),getItemIdFromModelObjectSetToString(change.getPartsFromApresPseudoFolder(null)));

    // Discover CNs (1 CN -> 1 additional line) and extract their prefixes
    return discoverCN(change, prefix);
  }

  private void decoratePrefixWithFpp(final TcChangeRequestRevision change, final Map<String, String> param) {
    List<FPPRevision> fppList = change.getFppList();
    if (fppList == null || fppList.isEmpty()) {
      emptyFPP(param); // No FPP, empty cell
    }
    else {
      StringBuilder builderId = new StringBuilder();
      StringBuilder builderRev = new StringBuilder();
      boolean firstPass = true;
      for (FPPRevision fpp : fppList) {
        if (firstPass) {
          builderId.append(fpp.getItemId());
          builderRev.append(fpp.getRevisionId());
          firstPass = false;
        }
        else {
          builderId.append(SAUT_LIGNE).append(fpp.getItemId()); // If several FPPs are found, add them all in the same cell with a new line
          builderRev.append(SAUT_LIGNE).append(fpp.getRevisionId());
        }
      }
      param.put(R13Keys.CR.ID_PROBLEM_REPORT.toString(), builderId.toString());
      param.put(R13Keys.CR.REVISION_PROBLEM_REPORT.toString(), builderRev.toString());
    }
  }

  /**
   * Concatenates all effectivities of the ChangeRequest in a String.
   *
   * @param change the change
   * @return a String containing all effectivities, comma-separated
   */
  private String getEffectivityValue(final TcChangeRequestRevision change) {
    StringBuilder builder = new StringBuilder();
    List<TcReleaseStatus> statuses = change.getReleaseStatusContainingEffectivity();
    if (statuses != null && !statuses.isEmpty()) {
      boolean firstPass = true;
      for (TcReleaseStatus status : statuses) {
        // Must check because only the TcEffectivity are wanted (and not Set_Effectivity, Old effectivity, Interface todo)
        if (SnecmaObjectTypes.EFFECTIVITY.equals(status.getStatusEnum().getIdentifier())) {
          for (TcEffectivity effectivity : status.getEffectivities()) {
            if (firstPass) {
              firstPass = false;
            }
            else {
              builder.append(COMMA);
            }
            effectivitySentence(builder, effectivity);
          }
        }
      }
    }
    return builder.toString();
  }

  /**
   * Appends the {@link TcEffectivity} description to the {@link StringBuilder}.
   */
  private void effectivitySentence(final StringBuilder builder, final TcEffectivity effectivityBuilt) {
    String articleFinalId = effectivityBuilt.endItemId();
    builder.append(articleFinalId).append('[');
    String unitRange = effectivityBuilt.unitRange();
    if (unitRange.isEmpty()) {
      builder.append(effectivityBuilt.dateRange());
    }
    else {
      builder.append(unitRange);
    }
    builder.append(']');
  }

  /**
   * Complete the CN columns
   * @param change
   * @param fppCrPrefix
   * @return LinkedHashMap<AbstractChangeObject, Map<String, String>>
   */
  private LinkedHashMap<AbstractChangeObject, Map<String, String>> discoverCN(final TcChangeRequestRevision change,
                                                                              final Map<String, String> fppCrPrefix) {
    LinkedHashMap<AbstractChangeObject, Map<String, String>> result = new LinkedHashMap<>();
    // Put a line with the CR alone
    Map<String, String> emptyCnData = makeEmptyCN(fppCrPrefix);
    // Override module with CR modules
    Set<String> crModuleValues = getModuleValues(change);
    emptyCnData.put(R13Keys.CR.MODULES.toString(), StringHelper.implodeList(COMMA, crModuleValues));
    result.put(change, emptyCnData);

    // Now put an extra line for every CN found
    for (TcChangeNoticeRevision changeNotice : change.getChangeNoticesList(new TcTypeExclusionFilter(CN_DEPRECIE_REVISION, PV4_CN_DEPREC_REVISION), StatusEnum.forbid(StatusEnum.FINISHED, StatusEnum.CANCELLED))) {
      Map<String, String> cnParam = new HashMap<>(fppCrPrefix); // Copy prefix without modifying it

      // Users parameters of the CN
      cnParam.putAll(makeUserParameters(changeNotice));

      // ChangeNotice parameters
      Set<String> cnModuleValues = getModuleValues(changeNotice);
      cnParam.put(R13Keys.CR.MODULES.toString(), StringHelper.implodeList(COMMA, cnModuleValues));
      cnParam.put(R13Keys.CR.ITEM_ID_CN.toString(), changeNotice.getItemId());
      cnParam.put(R13Keys.CR.ITEM_REVISION_CN.toString(), changeNotice.getRevisionId());
      cnParam.put(R13Keys.CR.DATE_CREATION_CN.toString(), new EffectivityDate(changeNotice.getDateCreation()).getFormattedDateForFrenchUser());
      cnParam.put(R13Keys.CR.LIGNE_PRODUIT_CN.toString(), Joiner.on(StringHelper.LINE_FEED).join(changeNotice.getLignesProduit()));
      cnParam.put(R13Keys.CR.DESIGNATION_CN.toString(), changeNotice.getChangeName());
      cnParam.put(R13Keys.CR.TYPE_CN.toString(), LanguageHelper.french(changeNotice.getTypeModification()));
      cnParam.put(R13Keys.CR.PARTS_CHANGE_NOTICE.toString(), getItemIdFromModelObjectSetToString(changeNotice.getPartsFromApresPseudoFolder(null)));
      // Validated status of object parameters
      cnParam.putAll(makeReleaseStatusParameters(changeNotice));

      // Register this ChangeNotice for further processing (wkf, tasks etc.)
      result.put(changeNotice, cnParam);
    }
    return result;
  }

  /**
   * Complete CN columns with empty strings
   * @param param
   * @return Map<String, String>
   */
  private Map<String, String> makeEmptyCN(final Map<String, String> param) {
    Map<String, String> result = new HashMap<>(param);
    result.put(R13Keys.CR.ATTACHE_PILOTE_CN.toString(), EMPTY_STRING);
    result.put(R13Keys.CR.NOM_PILOTE_CN.toString(), EMPTY_STRING);
    result.put(R13Keys.CR.STATUT_PILOTE_CN.toString(), EMPTY_STRING);
    result.put(R13Keys.CR.MODULES.toString(), EMPTY_STRING);
    result.put(R13Keys.CR.ITEM_ID_CN.toString(), EMPTY_STRING);
    result.put(R13Keys.CR.ITEM_REVISION_CN.toString(), EMPTY_STRING);
    result.put(R13Keys.CR.DATE_CREATION_CN.toString(), EMPTY_STRING);
    result.put(R13Keys.CR.LIGNE_PRODUIT_CN.toString(), EMPTY_STRING);
    result.put(R13Keys.CR.DESIGNATION_CN.toString(), EMPTY_STRING);
    result.put(R13Keys.CR.TYPE_CN.toString(), EMPTY_STRING);
    result.put(R13Keys.CR.STATUT_CN.toString(), EMPTY_STRING);
    result.put(R13Keys.CR.DATE_STATUT_CN.toString(), EMPTY_STRING);
    result.put(R13Keys.CR.PARTS_CHANGE_NOTICE.toString(), EMPTY_STRING);
    return result;
  }

  /**
   * Compiles the content required for the 'Module' column.
   *
   * @param changeRequest the {@link TcChangeRequestRevision} of the line
   * @return the module column's content
   */
  private Set<String> getModuleValues(final TcChangeRequestRevision changeRequest) {
    Set<String> moduleNames = new TreeSet<>();
    // Obtain modules in gens with or without rupture of the CR
    for (AbstractGeneric gen : changeRequest.getListOfGenericRevWithOrWithoutRupture()) {
      moduleNames.add(getModuleName(gen));
    }
    // Append all Generics in gens with/without rupture of all child CNs
    for (TcChangeNoticeRevision cn : changeRequest.getChangeNoticesList(new TcTypeExclusionFilter(CN_DEPRECIE_REVISION, PV4_CN_DEPREC_REVISION), StatusEnum.forbid(StatusEnum.FINISHED, StatusEnum.CANCELLED))) {
      moduleNames.addAll(getModuleValues(cn));
    }
    return moduleNames;
  }

  /**
   * Compiles the content required for the 'Module' column.
   *
   * @param changeNotice the {@link TcChangeRequestRevision} of the line
   * @return the module column's content
   */
  private Set<String> getModuleValues(final TcChangeNoticeRevision changeNotice) {
    Set<String> moduleNames = new TreeSet<>();
    for (AbstractGeneric gen : changeNotice.getListOfGenericRevWithOrWithoutRupture()) {
      String moduleName = getModuleName(gen);
      moduleNames.add(moduleName);
    }
    return moduleNames;
  }

  private String getModuleName(final AbstractGeneric gen) {
    return gen.getItemId();
  }

  @Override
  protected boolean isValid(final TcReleaseStatus status, final AbstractChangeObject change) {
    // If change is neither a CR nor a CN, it is not valid
    return (change instanceof TcChangeRequestRevision) || (change instanceof TcChangeNoticeRevision);
  }

  @Override
  protected void specificTreatmentEmptyLines(final Map<String, String> mapPrinter) {
    emptyFPP(mapPrinter);
    mapPrinter.put(R13Keys.CR.EFFECTIVITES.toString(), EMPTY_STRING);
    mapPrinter.putAll(makeEmptyCN(mapPrinter)); // replace all entries back into the input map
  }

  private void emptyFPP(final Map<String, String> mapPrinter) {
    mapPrinter.put(R13Keys.CR.ID_PROBLEM_REPORT.toString(), EMPTY_STRING);
    mapPrinter.put(R13Keys.CR.REVISION_PROBLEM_REPORT.toString(), EMPTY_STRING);
  }

}
