package com.capgemini.reports.definition.r21report.visitors;

import com.capgemini.framework.teamcenter.authent.TcSession;
import com.capgemini.framework.teamcenter.object.types.SnecmaObjectTypes;
import com.capgemini.reports.bomlineholder.BOMLineHolder;
import com.capgemini.reports.bomlineholder.IBOMTree;
import com.capgemini.reports.definition.r21report.ExecutionR21;
import com.capgemini.reports.definition.r21report.ExecutionR21.ReportDataR21;
import com.capgemini.reports.definition.r21report.R21ReportKeys;
import com.capgemini.reports.definition.r21report.visitors.bomlineholder.R21BOMLineHolderVisitor;
import com.capgemini.reports.execution.mapconstructor.AbstractStaticTemplateMapConstructor;
import com.capgemini.reports.printing.PrintParameters;
import com.capgemini.reports.reports.parameters.EffectivityDate;
import com.capgemini.reports.reports.parameters.ProductUnit;
import org.apache.commons.collections.ListUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

import static com.capgemini.reports.definition.r21report.visitors.R21VisitorKeys.*;


public class R21Visitor extends AbstractStaticTemplateMapConstructor<ExecutionR21, ReportDataR21> {
  private static final long serialVersionUID = 7945453397269802050L;
  private static final Logger LOGGER = LoggerFactory.getLogger(R21Visitor.class);

  private final List<Map<R21VisitorKeys, String>> listOfAssemblyLines;
  private final List<Map<R21VisitorKeys, String>> listOfTechnicalLines;

  private final ProductUnit productUnit;
  private final EffectivityDate effectivity;

  /**
   * Constructor.
   *
   * @param productUnit ProductUnit
   * @param effectivity EffectivityDate
   */
  public R21Visitor(final ProductUnit productUnit, final EffectivityDate effectivity) {
    super();
    this.productUnit = productUnit;
    this.effectivity = effectivity;
    this.listOfAssemblyLines = new ArrayList<>();
    this.listOfTechnicalLines = new ArrayList<>();
  }


  @Override
  public void constructDataMaps(TcSession session, final PrintParameters printParameters, final ReportDataR21 data) {
    long start = System.currentTimeMillis();
    if (data.getTopLineAssembly() == null || data.getTopLineTechnical() == null) {
      throw new NoSuchElementException("No Product structure found");
    }

    // Parse all the snecma objects...
    List<Map<String, ?>> theResultReport = visitReport(data.getTopLineAssembly(), data.getTopLineTechnical());
    printParameters.addReportData(theResultReport);
    LOGGER.info(EXECUTION_TIME_XXX_MS_FOR_YYY, System.currentTimeMillis() - start, "Data Map Construction");
  }

  /**
   * Visit a R21 report.
   *
   * @param holderAssembly  BOMLineHolder
   * @param holderTechnical BOMLineHolder
   * @return theR21report List<Map<String, ?>> the known format of the printer
   */
  private List<Map<String, ?>> visitReport(final BOMLineHolder holderAssembly, final BOMLineHolder holderTechnical) {
    parseExpansionBomAndRemoveRFandPartAssembs(holderAssembly);

    List<Map<R21VisitorKeys, String>> assemblyResult = visitReportAssembly(holderAssembly);
    List<Map<R21VisitorKeys, String>> technicalResult = visitReportTechnical(holderTechnical);
    LOGGER.info("There are {} initial element(s) in the Assembly view", assemblyResult.size());
    LOGGER.info("There are {} initial element(s) in the Technical view", technicalResult.size());

    completeReportData(assemblyResult, technicalResult);
    LOGGER.info("There are {} element(s) remaining in the Assembly view", listOfAssemblyLines.size());
    LOGGER.info("There are {} element(s) remaining in the Technical view", listOfTechnicalLines.size());

    List<Map<R21ReportKeys, String>> theR21report = createReport();

    // Add the effectivity column
    addEffectivityToReport(theR21report);

    // Translate the report in a known format for the printer and return it
    return translate(theR21report);
  }

  /**
   * Create a R21 report.
   *
   * @return theR21report List<Map<R21ReportKeys, String>>
   */
  private List<Map<R21ReportKeys, String>> createReport() {
    List<Map<R21ReportKeys, String>> theR21report;
    if ((listOfAssemblyLines.isEmpty()) || (listOfTechnicalLines.isEmpty())) {
      // Create the empty line if the Assembly or Technical are empty
      theR21report = createEmptyLine();
    }
    else {
      // Retrieve the R21 report properties
      theR21report = getListOfReportLines(listOfAssemblyLines, listOfTechnicalLines);
    }
    return theR21report;
  }

  /**
   * Create an empty line.
   *
   * @return List<Map       <       R21ReportKeys       ,       String>> a list with the only empty map
   */
  private List<Map<R21ReportKeys, String>> createEmptyLine() {
    List<Map<R21ReportKeys, String>> theR21report = new ArrayList<>();
    Map<R21ReportKeys, String> firstLine = new EnumMap<>(R21ReportKeys.class);

    // Technical view
    firstLine.put(R21ReportKeys.ETUDE_REPERE_FONCTIONNEL_RECEPTEUR, "");
    firstLine.put(R21ReportKeys.ETUDE_REVISION_REPERE_FONCTIONNEL_RECEPTEUR, "");
    firstLine.put(R21ReportKeys.ETUDE_IDENTIFIANT_RECEPTEUR, "");
    firstLine.put(R21ReportKeys.ETUDE_REVISION_RECEPTEUR, "");
    firstLine.put(R21ReportKeys.ETUDE_NOM_RECEPTEUR, "");
    firstLine.put(R21ReportKeys.ETUDE_REPERE_FONCTIONNEL_COMPOSANT, "");
    firstLine.put(R21ReportKeys.ETUDE_REVISION_REPERE_FONCTIONNEL_COMPOSANT, "");
    firstLine.put(R21ReportKeys.ETUDE_REF_FABRICANT_REF_SNECMA, "");
    firstLine.put(R21ReportKeys.ETUDE_REVISION_REFERENCE, "");
    firstLine.put(R21ReportKeys.ETUDE_TYPE_REFERENCE, "");
    firstLine.put(R21ReportKeys.ETUDE_NOM_REFERENCE, "");
    firstLine.put(R21ReportKeys.ETUDE_NOMBRE_REPERE_FONCTIONNEL, "");

    // Assembly view
    firstLine.put(R21ReportKeys.MONTAGE_REPERE_FONCTIONNEL_RECEPTEUR, "");
    firstLine.put(R21ReportKeys.MONTAGE_REVISION_REPERE_FONCTIONNEL_RECEPTEUR, "");
    firstLine.put(R21ReportKeys.MONTAGE_IDENTIFIANT_RECEPTEUR, "");
    firstLine.put(R21ReportKeys.MONTAGE_REVISION_RECEPTEUR, "");
    firstLine.put(R21ReportKeys.MONTAGE_NOM_RECEPTEUR, "");
    firstLine.put(R21ReportKeys.MONTAGE_REPERE_FONCTIONNEL_COMPOSANT, "");
    firstLine.put(R21ReportKeys.MONTAGE_REVISION_REPERE_FONCTIONNEL_COMPOSANT, "");
    firstLine.put(R21ReportKeys.MONTAGE_REF_FABRICANT_REF_SNECMA, "");
    firstLine.put(R21ReportKeys.MONTAGE_REVISION_REFERENCE, "");
    firstLine.put(R21ReportKeys.MONTAGE_TYPE_REFERENCE, "");
    firstLine.put(R21ReportKeys.MONTAGE_NOM_REFERENCE, "");
    firstLine.put(R21ReportKeys.MONTAGE_IDENTIFIANT_RECEPTEUR_ORIGINE, "");
    firstLine.put(R21ReportKeys.MONTAGE_NOM_RECEPTEUR_ORIGINE, "");
    firstLine.put(R21ReportKeys.MONTAGE_NOMBRE_REPERE_FONCTIONNEL, "");

    theR21report.add(firstLine);
    return theR21report;
  }

  /**
   * Create the R21 report by mixing the Assembly list and the Technical list.
   */
  private List<Map<R21ReportKeys, String>> getListOfReportLines(final List<Map<R21VisitorKeys, String>> assemblyList,
                                                                final List<Map<R21VisitorKeys, String>> technicalList) {
    List<Map<R21ReportKeys, String>> theR21report = new ArrayList<>();

    // Create the Technical view in the R21 report
    for (Map<R21VisitorKeys, String> curentMapInTechnicalList : technicalList) {
      theR21report.add(makeTechnicalLine(curentMapInTechnicalList));
    }

    // Create the Assembly view in the R21 report
    for (Map<R21VisitorKeys, String> curentMapInAssemblyList : assemblyList) {
      theR21report.add(makeAssemblyLine(curentMapInAssemblyList));
    }
    return theR21report;
  }

  private Map<R21ReportKeys, String> makeAssemblyLine(final Map<R21VisitorKeys, String> curentMapInAssemblyList) {
    Map<R21ReportKeys, String> theLine = new EnumMap<>(R21ReportKeys.class);

    theLine.put(R21ReportKeys.MONTAGE_REPERE_FONCTIONNEL_RECEPTEUR, curentMapInAssemblyList.get(REPERE_FONCTIONNEL_RECEPTEUR));
    theLine.put(R21ReportKeys.MONTAGE_REVISION_REPERE_FONCTIONNEL_RECEPTEUR, curentMapInAssemblyList.get(REVISION_REPERE_FONCTIONNEL_RECEPTEUR));
    theLine.put(R21ReportKeys.MONTAGE_IDENTIFIANT_RECEPTEUR, curentMapInAssemblyList.get(IDENTIFIANT_RECEPTEUR));
    theLine.put(R21ReportKeys.MONTAGE_REVISION_RECEPTEUR, curentMapInAssemblyList.get(REVISION_RECEPTEUR));
    theLine.put(R21ReportKeys.MONTAGE_NOM_RECEPTEUR, curentMapInAssemblyList.get(NOM_RECEPTEUR));
    theLine.put(R21ReportKeys.MONTAGE_REPERE_FONCTIONNEL_COMPOSANT, curentMapInAssemblyList.get(REPERE_FONCTIONNEL_COMPOSANT));
    theLine.put(R21ReportKeys.MONTAGE_REVISION_REPERE_FONCTIONNEL_COMPOSANT, curentMapInAssemblyList.get(REVISION_REPERE_FONCTIONNEL_COMPOSANT));
    theLine.put(R21ReportKeys.MONTAGE_REF_FABRICANT_REF_SNECMA, curentMapInAssemblyList.get(REF_FABRICANT_REF_SNECMA));
    theLine.put(R21ReportKeys.MONTAGE_REVISION_REFERENCE, curentMapInAssemblyList.get(REVISION_REFERENCE));
    theLine.put(R21ReportKeys.MONTAGE_TYPE_REFERENCE, curentMapInAssemblyList.get(TYPE_REFERENCE));
    theLine.put(R21ReportKeys.MONTAGE_NOM_REFERENCE, curentMapInAssemblyList.get(NOM_REFERENCE));
    theLine.put(R21ReportKeys.MONTAGE_IDENTIFIANT_RECEPTEUR_ORIGINE, curentMapInAssemblyList.get(IDENTIFIANT_RECEPTEUR_ORIGINE));
    theLine.put(R21ReportKeys.MONTAGE_NOM_RECEPTEUR_ORIGINE, curentMapInAssemblyList.get(NOM_RECEPTEUR_ORIGINE));
    theLine.put(R21ReportKeys.MONTAGE_NOMBRE_REPERE_FONCTIONNEL, curentMapInAssemblyList.get(NOMBRE_REPERE_FONCTIONNEL));
    theLine.put(R21ReportKeys.MONTAGE_IS_YELLOW_BACKGROUND, curentMapInAssemblyList.get(IS_YELLOW_BACKGROUND));
    theLine.put(R21ReportKeys.MONTAGE_RF_IS_YELLOW_BACKGROUND, curentMapInAssemblyList.get(RF_IS_YELLOW_BACKGROUND));

    return theLine;
  }

  private Map<R21ReportKeys, String> makeTechnicalLine(final Map<R21VisitorKeys, String> curentMapInTechnicalList) {
    Map<R21ReportKeys, String> theLine = new EnumMap<>(R21ReportKeys.class);

    theLine.put(R21ReportKeys.ETUDE_REPERE_FONCTIONNEL_RECEPTEUR, curentMapInTechnicalList.get(REPERE_FONCTIONNEL_RECEPTEUR));
    theLine.put(R21ReportKeys.ETUDE_REVISION_REPERE_FONCTIONNEL_RECEPTEUR, curentMapInTechnicalList.get(REVISION_REPERE_FONCTIONNEL_RECEPTEUR));
    theLine.put(R21ReportKeys.ETUDE_IDENTIFIANT_RECEPTEUR, curentMapInTechnicalList.get(IDENTIFIANT_RECEPTEUR));
    theLine.put(R21ReportKeys.ETUDE_REVISION_RECEPTEUR, curentMapInTechnicalList.get(REVISION_RECEPTEUR));
    theLine.put(R21ReportKeys.ETUDE_NOM_RECEPTEUR, curentMapInTechnicalList.get(NOM_RECEPTEUR));
    theLine.put(R21ReportKeys.ETUDE_REPERE_FONCTIONNEL_COMPOSANT, curentMapInTechnicalList.get(REPERE_FONCTIONNEL_COMPOSANT));
    theLine.put(R21ReportKeys.ETUDE_REVISION_REPERE_FONCTIONNEL_COMPOSANT, curentMapInTechnicalList.get(REVISION_REPERE_FONCTIONNEL_COMPOSANT));
    theLine.put(R21ReportKeys.ETUDE_REF_FABRICANT_REF_SNECMA, curentMapInTechnicalList.get(REF_FABRICANT_REF_SNECMA));
    theLine.put(R21ReportKeys.ETUDE_REVISION_REFERENCE, curentMapInTechnicalList.get(REVISION_REFERENCE));
    theLine.put(R21ReportKeys.ETUDE_TYPE_REFERENCE, curentMapInTechnicalList.get(TYPE_REFERENCE));
    theLine.put(R21ReportKeys.ETUDE_NOM_REFERENCE, curentMapInTechnicalList.get(NOM_REFERENCE));
    theLine.put(R21ReportKeys.ETUDE_NOMBRE_REPERE_FONCTIONNEL, curentMapInTechnicalList.get(NOMBRE_REPERE_FONCTIONNEL));
    theLine.put(R21ReportKeys.ETUDE_IS_YELLOW_BACKGROUND, curentMapInTechnicalList.get(IS_YELLOW_BACKGROUND));
    theLine.put(R21ReportKeys.ETUDE_RF_IS_YELLOW_BACKGROUND, curentMapInTechnicalList.get(RF_IS_YELLOW_BACKGROUND));

    return theLine;
  }

  /**
   * Parse the expansion Bom and remove all the RF Part Assemb and the Part Assemb.
   *
   * @param aBom BOMLineHolder
   */
  private void parseExpansionBomAndRemoveRFandPartAssembs(final BOMLineHolder aBom) {
    // Parse the expansion Bom
    List<BOMLineHolder> children = aBom.getBomChilds();
    if (!children.isEmpty()) {
      for (BOMLineHolder actualChild : aBom.getBomChilds()) {
        parseExpansionBomAndRemoveRFandPartAssembs(actualChild);
      }
    }

    // Treatment of the object by raising the expansion bom
    // Avoid the ConcurrentModificationExecption by this way
    if ((aBom.getSnecmaObject() != null) && (aBom.getParentBomLine() != null)) {
      String parentType = aBom.getParentBomLine().getSnecmaObject().getType();

      // Modify the value parent of the Bom
      if (SnecmaObjectTypes.RF_PART_ASSEMB_REVISION.equals(parentType) || SnecmaObjectTypes.PART_ASSEMB_REVISION.equals(parentType)) {
        BOMLineHolder newParentFound = getParentNoAssembs(aBom);
        aBom.setParent(newParentFound);
      }
    }

    // Modify the value bomChildMap of the Bom
    if (!children.isEmpty()) {
      findAssembChildAndRemoveIt(aBom);
    }
  }

  /**
   * Find RF Part Assemb or Part Assemb children.<br>
   * Remove this Assemb children and add their own children to the Bom's children.
   *
   * @param aBom BOMLineHolder
   */
  private void findAssembChildAndRemoveIt(final BOMLineHolder aBom) {
    List<BOMLineHolder> children = aBom.getBomChilds();
    List<BOMLineHolder> removedChildren = new ArrayList<>();
    List<BOMLineHolder> addedChildren = new ArrayList<>();

    // Check if there are a RF Part Assemb or Part Assemb children
    if (!children.isEmpty()) {
      for (BOMLineHolder child : children) {
        if (child.getSnecmaObject() != null) {
          String entryChildType = child.getSnecmaObject().getType();
          if (SnecmaObjectTypes.RF_PART_ASSEMB_REVISION.equals(entryChildType) || SnecmaObjectTypes.PART_ASSEMB_REVISION.equals(entryChildType)) {
            // List the RF Part Assemb or Part Assemb children
            removedChildren.add(child);

            // Retrieve the bomChildMap of the RF Part Assemb or Part Assemb children
            // No need to remove the Admin under a RF Part Assemb
            // Because, for a R21 report, the Admin are treated only for a RF Part Assemb
            addedChildren.addAll(child.getBomChilds());
          }
        }
      }
    }

    // Removed the RF Part Assemb or Part Assemb children
    if (!removedChildren.isEmpty()) {
      for (BOMLineHolder toRemove : removedChildren) {
        children.remove(toRemove);
      }
    }

    // Add the bomChildMap of the removed RF Part Assemb or Part Assemb children
    // to the Bom's bomChildMap
    if (!addedChildren.isEmpty()) {
      children.addAll(addedChildren);
    }
  }

  /**
   * Get a parent which is not a RF Part Assemb or a Part Assemb.
   *
   * @param aBom BOMLineHolder
   */
  private BOMLineHolder getParentNoAssembs(final IBOMTree aBom) {
    String parentBomType = aBom.getParentBomLine().getSnecmaObject().getType();
    if (!SnecmaObjectTypes.RF_PART_ASSEMB_REVISION.equals(parentBomType)
        && !SnecmaObjectTypes.PART_ASSEMB_REVISION.equals(parentBomType)) {
      return new BOMLineHolder(aBom);
    }
    else {
      return getParentNoAssembs(aBom.getParentBomLine());
    }
  }

  /**
   * Find an Remove duplicate keys between two maps.
   *
   * @param assemblyList  a List of key-values that exist in assembly view
   * @param technicalList a List of key-values that exist in technical view
   */
  private void completeReportData(final List<Map<R21VisitorKeys, String>> assemblyList, final List<Map<R21VisitorKeys, String>> technicalList) {
    List<SuperMap> comparativeAssemblyList = transformListForComparaisonInRemoveKeys(assemblyList);
    List<SuperMap> comparativeTechnicalList = transformListForComparaisonInRemoveKeys(technicalList);

    List<SuperMap> consolidatedAssemblyList = countDuplicatedLines(comparativeAssemblyList);
    List<SuperMap> consolidateTechnicalList = countDuplicatedLines(comparativeTechnicalList);

    List<SuperMap> theRemovedList = ListUtils.intersection(consolidatedAssemblyList, consolidateTechnicalList);
    List<SuperMap> uniqueAssembLines = ListUtils.removeAll(consolidatedAssemblyList, theRemovedList);
    List<SuperMap> uniqueTechnicalLines = ListUtils.removeAll(consolidateTechnicalList, theRemovedList);

    addReceptorOriginsData(uniqueAssembLines, uniqueTechnicalLines);
    addBackgroundColor(uniqueAssembLines, uniqueTechnicalLines);

    listOfAssemblyLines.addAll(uniqueAssembLines);
    listOfTechnicalLines.addAll(uniqueTechnicalLines);
  }

  private void addBackgroundColor(List<SuperMap> uniqueAssembLines, List<SuperMap> uniqueTechnicalLines) {
    addBackgroundColorAttributes(uniqueAssembLines, uniqueTechnicalLines);
    addBackgroundColorAttributes(uniqueTechnicalLines, uniqueAssembLines);
  }

  private void addBackgroundColorAttributes(List<SuperMap> toCompare, List<SuperMap> from) {
    for (SuperMap fromSuperMap : from) {
      SuperMap toCompareSuperMap = getSuperMapEquals(toCompare, fromSuperMap);
      if (fromSuperMap.containsKey(NOMBRE_REPERE_FONCTIONNEL)
          && Integer.parseInt(fromSuperMap.get(NOMBRE_REPERE_FONCTIONNEL)) > 1
          && toCompareSuperMap.isEmpty()) {
        fromSuperMap.put(R21VisitorKeys.IS_YELLOW_BACKGROUND, "true");
        fromSuperMap.put(R21VisitorKeys.RF_IS_YELLOW_BACKGROUND, "true");
      }
      else if (toCompareSuperMap.containsKey(NOMBRE_REPERE_FONCTIONNEL)
          && fromSuperMap.containsKey(NOMBRE_REPERE_FONCTIONNEL)
          && Integer.parseInt(toCompareSuperMap.get(NOMBRE_REPERE_FONCTIONNEL)) != Integer.parseInt(fromSuperMap.get(NOMBRE_REPERE_FONCTIONNEL))) {
        fromSuperMap.put(R21VisitorKeys.IS_YELLOW_BACKGROUND, "true");
      }
    }
  }

  private void addReceptorOriginsData(List<SuperMap> uniqueAssembLines, List<SuperMap> uniqueTechnicalLines) {
    for (SuperMap uniqueAssembLine : uniqueAssembLines) {
      SuperMap superMap = getSuperMapOnlyDiffByRfReceptor(uniqueTechnicalLines, uniqueAssembLine);
      uniqueAssembLine.put(IDENTIFIANT_RECEPTEUR_ORIGINE, superMap.containsKey(IDENTIFIANT_RECEPTEUR) ? superMap.get(IDENTIFIANT_RECEPTEUR) : "");
      uniqueAssembLine.put(NOM_RECEPTEUR_ORIGINE, superMap.containsKey(NOM_RECEPTEUR) ? superMap.get(NOM_RECEPTEUR) : "");
    }
  }

  private SuperMap getSuperMapOnlyDiffByRfReceptor(List<SuperMap> superMaps, SuperMap uniqueLine) {
    for (SuperMap superMap : superMaps) {
      if (uniqueLine.isOnlyDiffByRfReceptor(superMap)
          && !uniqueLine.get(IDENTIFIANT_RECEPTEUR).equals(superMap.get(IDENTIFIANT_RECEPTEUR))
          && !uniqueLine.get(NOM_RECEPTEUR).equals(superMap.get(NOM_RECEPTEUR))
          ) {
        return superMap;
      }
    }
    return new SuperMap();
  }

  private SuperMap getSuperMapEquals(List<SuperMap> superMaps, SuperMap uniqueLine) {
    for (SuperMap superMap : superMaps) {
      if (uniqueLine.isEquals(superMap)) {
        return superMap;
      }
    }
    return new SuperMap();
  }

  private List<SuperMap> countDuplicatedLines(List<SuperMap> comparativeList) {
    List<SuperMap> consolidatedList = new ArrayList<>();
    for (SuperMap map : comparativeList) {
      if (consolidatedList.contains(map)) {
        SuperMap superMap = consolidatedList.get(consolidatedList.indexOf(map));
        superMap.put(NOMBRE_REPERE_FONCTIONNEL, Integer.toString(Integer.parseInt(superMap.get(NOMBRE_REPERE_FONCTIONNEL)) + 1));
      }
      else {
        map.put(NOMBRE_REPERE_FONCTIONNEL, Integer.toString(1));
        consolidatedList.add(map);
      }
    }
    return consolidatedList;
  }

  private List<SuperMap> transformListForComparaisonInRemoveKeys(final List<Map<R21VisitorKeys, String>> aListToTransform) {
    List<SuperMap> aListToReturn = new ArrayList<>();

    for (Map<R21VisitorKeys, String> currentMap : aListToTransform) {
      SuperMap simpleMap = new SuperMap();
      simpleMap.putAll(currentMap);
      aListToReturn.add(simpleMap);
    }
    return aListToReturn;
  }

  /**
   * Visit an Assembly BOM.
   *
   * @param holderAssembly BOMLineHolder
   */
  private List<Map<R21VisitorKeys, String>> visitReportAssembly(final BOMLineHolder holderAssembly) {
    return new R21BOMLineHolderVisitor().visitTopReceptorBom(holderAssembly);
  }

  /**
   * Visit a Technical BOM.
   *
   * @param holderTechnical BOMLineHolder
   */
  private List<Map<R21VisitorKeys, String>> visitReportTechnical(final BOMLineHolder holderTechnical) {
    return new R21BOMLineHolderVisitor().visitTopReceptorBom(holderTechnical);
  }

  /**
   * Add the value of the column TcEffectivity to the Report.
   */
  private void addEffectivityToReport(final List<Map<R21ReportKeys, String>> theResultReport) {
    // Create the value of the effectivity for the R21 report
    String theEffectivity = productUnit.getProductID() + " / " + productUnit.getUnit() + " / " + formatEffectivityDate(effectivity);

    // Put the effectivity in all the lines of the report
    for (Map<R21ReportKeys, String> actualMap : theResultReport) {
      actualMap.put(R21ReportKeys.EFFECTIVITE, theEffectivity);
    }
  }

  private static String formatEffectivityDate(final EffectivityDate eff) {
    Calendar date;
    if (eff.isNow()) {
      date = Calendar.getInstance();
    }
    else {
      date = eff.getDateTime();
    }
    DateFormat dateFormat = new SimpleDateFormat("dd-MMMMM-yy", Locale.FRENCH);
    return dateFormat.format(date.getTime());
  }

  /**
   * Translate the specific result of the visitor into something the printer can understand.
   */
  private List<Map<String, ?>> translate(final List<Map<R21ReportKeys, String>> theActualReportList) {
    // Construct the map list expected by the printer
    List<Map<String, ?>> printerMaps = new ArrayList<>();
    for (Map<R21ReportKeys, String> visitorMap : theActualReportList) {
      Map<String, String> printerMap = new HashMap<>();
      for (R21ReportKeys key : R21ReportKeys.values()) {
        printerMap.put(key.toString(), visitorMap.get(key));
      }
      printerMaps.add(printerMap);
    }

    return printerMaps;
  }

  @Override
  public boolean equals(Object o) {
    if (o == this) return true;
    if (!(o instanceof R21Visitor)) return false;
    R21Visitor other = (R21Visitor) o;
    if (!other.canEqual(this)) return false;
    if (!super.equals(o)) return false;
    if (this.listOfAssemblyLines == null ? other.listOfAssemblyLines != null : !this.listOfAssemblyLines.equals(other.listOfAssemblyLines))
      return false;
    if (this.listOfTechnicalLines == null ? other.listOfTechnicalLines != null : !this.listOfTechnicalLines.equals(other.listOfTechnicalLines))
      return false;
    if (this.productUnit == null ? other.productUnit != null : !this.productUnit.equals(other.productUnit))
      return false;
    return this.effectivity == null ? other.effectivity == null : this.effectivity.equals(other.effectivity);
  }

  @Override
  public int hashCode() {
    final int PRIME = 59;
    int result = 1;
    result = result * PRIME + super.hashCode();
    result = result * PRIME + (this.listOfAssemblyLines == null ? 43 : this.listOfAssemblyLines.hashCode());
    result = result * PRIME + (this.listOfTechnicalLines == null ? 43 : this.listOfTechnicalLines.hashCode());
    result = result * PRIME + (this.productUnit == null ? 43 : this.productUnit.hashCode());
    result = result * PRIME + (this.effectivity == null ? 43 : this.effectivity.hashCode());
    return result;
  }

  @Override
  protected boolean canEqual(Object other) {
    return other instanceof R21Visitor;
  }

  /**
   * This class is a HashMap with a special {@code equals} method.
   */
  class SuperMap extends HashMap<R21VisitorKeys, String> {

    private static final long serialVersionUID = 7843304491480106535L;

    @Override
    public boolean equals(final Object other) {
      if (other instanceof SuperMap) {
        Map<R21VisitorKeys, String> otherMap = (SuperMap) other;
        for (Map.Entry<R21VisitorKeys, String> entry : this.entrySet()) {
          R21VisitorKeys key = entry.getKey();
          if (key != IDENTIFIANT_RECEPTEUR
              && key != REVISION_RECEPTEUR
              && key != NOM_RECEPTEUR
              && key != TYPE_REFERENCE
              && key != NOM_REFERENCE
              && key != IS_YELLOW_BACKGROUND
              && key != RF_IS_YELLOW_BACKGROUND
              && key != NOM_RECEPTEUR_ORIGINE
              && key != IDENTIFIANT_RECEPTEUR_ORIGINE
              && !entry.getValue().equals(otherMap.get(key))) {
            return false;
          }
        }
        return true;
      }
      else {
        return false;
      }
    }

    boolean isEquals(SuperMap otherMap) {
      for (Map.Entry<R21VisitorKeys, String> entry : this.entrySet()) {
        R21VisitorKeys key = entry.getKey();
        if (key != IDENTIFIANT_RECEPTEUR
            && key != REVISION_RECEPTEUR
            && key != NOM_RECEPTEUR
            && key != TYPE_REFERENCE
            && key != NOM_REFERENCE
            && key != NOMBRE_REPERE_FONCTIONNEL
            && key != IS_YELLOW_BACKGROUND
            && key != RF_IS_YELLOW_BACKGROUND
            && key != NOM_RECEPTEUR_ORIGINE
            && key != IDENTIFIANT_RECEPTEUR_ORIGINE
            && !entry.getValue().equals(otherMap.get(key))) {
          return false;
        }
      }
      return true;
    }

    boolean isOnlyDiffByRfReceptor(SuperMap otherMap) {
      for (Map.Entry<R21VisitorKeys, String> entry : this.entrySet()) {
        R21VisitorKeys key = entry.getKey();
        if (
            key != REPERE_FONCTIONNEL_RECEPTEUR
            && key != REVISION_REPERE_FONCTIONNEL_RECEPTEUR
            && key != IDENTIFIANT_RECEPTEUR
            && key != REVISION_RECEPTEUR
            && key != NOM_RECEPTEUR
            && key != TYPE_REFERENCE
            && key != NOM_REFERENCE
            && key != NOMBRE_REPERE_FONCTIONNEL
            && key != IS_YELLOW_BACKGROUND
            && key != RF_IS_YELLOW_BACKGROUND
            && key != NOM_RECEPTEUR_ORIGINE
            && key != IDENTIFIANT_RECEPTEUR_ORIGINE
            && !entry.getValue().equals(otherMap.get(key))) {
          return false;
        }
      }
      return true;
    }
  }
}
