package com.capgemini.reports.bomlineholder.visitor;

import com.capgemini.framework.teamcenter.object.types.SnecmaObjectTypes;
import com.capgemini.reports.bomlineholder.BOMLineHolder;
import com.capgemini.reports.bomlineholder.processor.AltOrDetBehaviour;
import com.capgemini.reports.releasestatus.EffectivityBehaviourHelper;
import com.capgemini.reports.snecmaobjects.ISnecmaObject;
import com.capgemini.reports.snecmaobjects.part.AbstractAltOrDetRevision;
import com.capgemini.reports.snecmaobjects.part.AlternateRevision;
import com.capgemini.reports.snecmaobjects.part.DETRevision;
import com.capgemini.reports.snecmaobjects.visitor.IComponentVisitor;
import com.capgemini.reports.snecmaobjects.visitor.IReceptorVisitor;
import lombok.EqualsAndHashCode;
import lombok.Getter;

import java.util.*;

/**
 * Visit a {@link BOMLineHolder} tree.<br>
 * Produces a {@link List} of {@link Map}s with the provided Key and Value types.<br>
 * The behaviour of this generic visitor is specified by the constructor parameters.
 *
 * @param <K> the Keys Type
 * @param <V> the Values Type
 */
@Getter
@EqualsAndHashCode
public abstract class AbstractBOMLineHolderVisitor<K, V> implements IBOMLineHolderVisitor<K, V> {

  private static final long serialVersionUID = 1362803093737028558L;
  private static final String VISIT_BOM = "Visit {} Bom: {}";

  // Sub-components that define the behaviour of this Visitor
  protected final IReceptorVisitor<Map<K, V>> receptorVisitor;
  protected final IComponentVisitor<Map<K, V>> componentVisitor;
  /**
   * This is the final list containing all entries.
   */
  protected final List<Map<K, V>> theReportTable = new ArrayList<>();
  private final EffectivityBehaviourHelper effectivityBehaviour;
  private final AltOrDetBehaviour<K, V> altOrDetBehaviour;
  private final boolean retrieveRepereReceptor;
  private final boolean retrievePartNumberUnderRfGeneric;
  private final boolean visitPartBelowPart;

  /**
   * Constructor.
   *
   * @param receptorVisitor                  : specifies behaviour when visiting a Receptor {@link ISnecmaObject}.
   * @param componentVisitor                 : specifies behaviour when visiting a Component {@link ISnecmaObject}.
   * @param effectivityBehaviour             : the behaviour regarding Effectivities
   * @param altOrDetBehaviour                : the behaviour regarding {@link AlternateRevision} or {@link DETRevision} parts.
   * @param retrieveRepereReceptor           boolean Take true if we retrieve the Repere Receptor properties. Else take false.
   * @param retrievePartNumberUnderRfGeneric boolean Take true if we retrieve the Part number under the RF Generic. Take false else.
   * @param visitPartBelowPart               tells the Visitor weather it should visit a Part that is below another Part
   */
  public AbstractBOMLineHolderVisitor(final IReceptorVisitor<Map<K, V>> receptorVisitor, final IComponentVisitor<Map<K, V>> componentVisitor,
                                      final EffectivityBehaviourHelper effectivityBehaviour, final AltOrDetBehaviour<K, V> altOrDetBehaviour,
                                      final boolean retrieveRepereReceptor, final boolean retrievePartNumberUnderRfGeneric, final boolean visitPartBelowPart) {
    this.receptorVisitor = receptorVisitor;
    this.componentVisitor = componentVisitor;
    this.effectivityBehaviour = effectivityBehaviour;
    this.altOrDetBehaviour = altOrDetBehaviour;
    this.retrieveRepereReceptor = retrieveRepereReceptor;
    this.retrievePartNumberUnderRfGeneric = retrievePartNumberUnderRfGeneric;
    this.visitPartBelowPart = visitPartBelowPart;
  }

  /**
   * Sort the children.
   *
   * @param bomHolder IBOMTree
   */
  private static List<BOMLineHolder> sortBomChildren(final BOMLineHolder bomHolder) {
    Collection<BOMLineHolder> childrenSet = bomHolder.getBomChilds();

    // Convert array of elements to sort to a list
    List<BOMLineHolder> sortedList = new ArrayList<>(childrenSet);
    Collections.sort(sortedList, new Comparator<BOMLineHolder>() {
      @Override
      public int compare(final BOMLineHolder arg0, final BOMLineHolder arg1) {
        ISnecmaObject snecmaObject0 = arg0.getSnecmaObject();
        String id0 = snecmaObject0 == null ? arg0.getKey() : snecmaObject0.getItemId();
        ISnecmaObject snecmaObject1 = arg1.getSnecmaObject();
        String id1 = snecmaObject1 == null ? arg1.getKey() : snecmaObject1.getItemId();
        return id0.compareTo(id1);
      }
    });
    return sortedList;
  }

  /**
   * Visit a Top BOMLineHolder and expand all the children to retrieve the properties.
   */
  @Override
  public List<Map<K, V>> visitTopReceptorBom(final BOMLineHolder aTopBomLine) {
    // Create a Specific first line
    visitSpecificFirstLine(aTopBomLine);

    // Analyze the type of the SnecmaObject and visit it in fonction of this type
    analyzeAndVisitTopBom(aTopBomLine);

    return theReportTable;
  }

  /**
   * Visit a Top BOMLineHolder in fonction of his type.
   *
   * @param aTopBomLine BOMLineHolder
   */
  private void analyzeAndVisitTopBom(final BOMLineHolder aTopBomLine) {
    ISnecmaObject theSnecmaObject = aTopBomLine.getSnecmaObject();
    if (theSnecmaObject != null) {
      if (theSnecmaObject.isGeneric() || theSnecmaObject.isProduct() || theSnecmaObject.isSuperModel()) {
        visitGenericAsReceptor(aTopBomLine);
      }
      else if (theSnecmaObject.isPart()) {
        visitPartAsReceptor(aTopBomLine, new HashMap<K, V>());
      }
      else if (theSnecmaObject.isRFPartOrPartAssemb()) {
        visitRFBeforePart(aTopBomLine, new HashMap<K, V>());
      }
      else {
        visitRFGenericAfterGenericReceptor(aTopBomLine, new HashMap<K, V>());
      }
    }
  }

  /**
   * Visit a Generic as a Receptor.
   *
   * @param aGenericReceptorBomLine BOMLineHolder
   */
  private void visitGenericAsReceptor(final BOMLineHolder aGenericReceptorBomLine) {
    Map<K, V> theGenericReceptor = visitThisReceptor(aGenericReceptorBomLine);

    // Modify theGenericReceptor : Associate the generic with his RFGeneric
    if (retrieveRepereReceptor) {
      for (BOMLineHolder actualChildOfTopBom : sortBomChildren(aGenericReceptorBomLine)) {
        if (actualChildOfTopBom.getSnecmaObject() == null) {
          continue;
        }
        if (actualChildOfTopBom.getSnecmaObject().isRFGeneric()) {
          visitRFGenericAfterGenericReceptor(actualChildOfTopBom, theGenericReceptor);
        }
      }
    }
    // Expand the children and use aGenericReceptorBomLine (Receptor + Repere properties)
    for (BOMLineHolder actualChild : sortBomChildren(aGenericReceptorBomLine)) {
      if (actualChild.getSnecmaObject() == null) {
        continue;
      }
      if (actualChild.getSnecmaObject().isGeneric() || actualChild.getSnecmaObject().isSuperModel()) {
        visitGenericAsComponent(actualChild, theGenericReceptor);
      }
      else if (actualChild.getSnecmaObject().isRFPartOrPartAssemb()) {
        visitRFBeforePart(actualChild, theGenericReceptor);
      }
      else if (retrievePartNumberUnderRfGeneric && actualChild.getSnecmaObject().isRFGeneric()) {
        visitRFGenericAndPartNumber(actualChild, theGenericReceptor);
      }
      else if (actualChild.getSnecmaObject().isSuperModel()){
        visitSuperModelAsComponent(actualChild, theGenericReceptor);
      }
    }
  }

  /**
   * Visit a Generic as a Component.
   *
   * @param aGenericComponentBomLine BOMLineHolder
   * @param theGenericReceptor       Map<K, V> the map with the Generic Receptor and the RFGeneric Receptor properties
   */
  private void visitGenericAsComponent(final BOMLineHolder aGenericComponentBomLine, final Map<K, V> theGenericReceptor) {
    // Retrieve the component properties
    // May create an eventual receptor in specific reports
    Map<K, V> theGenericComponent = visitThisComponent(aGenericComponentBomLine);

    // Associate the generic with his RFGeneric
    for (BOMLineHolder actualChildOfTopBom : sortBomChildren(aGenericComponentBomLine)) {
      if (actualChildOfTopBom.getSnecmaObject() == null) {
        continue;
      }
      if (actualChildOfTopBom.getSnecmaObject().isRFGeneric()) {
        visitRFGenericAfterGenericComponent(actualChildOfTopBom, theGenericReceptor, theGenericComponent);
      }
    }

    // aGenericComponentBomLine may be a Receptor for his own children
    if (!aGenericComponentBomLine.getBomChilds().isEmpty()) {
      visitGenericAsReceptor(aGenericComponentBomLine);
    }
  }

  /**
   * Visit a SuperModel as a Component.
   *
   * @param aSuperModelComponentBomLine BOMLineHolder
   * @param theSuperModelReceptor       Map<K, V> the map with the SuperModel Receptor and the RFGeneric Receptor properties
   */
  private void visitSuperModelAsComponent(final BOMLineHolder aSuperModelComponentBomLine, final Map<K, V> theSuperModelReceptor) {
    // Retrieve the component properties
    // May create an eventual receptor in specific reports
    Map<K, V> theSuperModelComponent = visitThisComponent(aSuperModelComponentBomLine);

    // Associate the generic with his RFGeneric
    for (BOMLineHolder actualChildOfTopBom : sortBomChildren(aSuperModelComponentBomLine)) {
      if (actualChildOfTopBom.getSnecmaObject() == null) {
        continue;
      }
      if (actualChildOfTopBom.getSnecmaObject().isRFGeneric()) {
        visitRFGenericAfterGenericComponent(actualChildOfTopBom, theSuperModelReceptor, theSuperModelComponent);
      }
    }

    // aGenericComponentBomLine may be a Receptor for his own children
    if (!aSuperModelComponentBomLine.getBomChilds().isEmpty()) {
      visitGenericAsReceptor(aSuperModelComponentBomLine);
    }
  }


  /**
   * Visit a RFGeneric after a Generic Receptor (link Generic Receptor - RFGeneric Receptor).
   *
   * @param aRFGenericBomLine  BOMLineHolder
   * @param theGenericReceptor Map<K, V> the map with the Generic Receptor properties
   */
  private void visitRFGenericAfterGenericReceptor(final BOMLineHolder aRFGenericBomLine, final Map<K, V> theGenericReceptor) {
    mergeReceptorAndRepereReceptor(theGenericReceptor, getRFGenericForReceptor(aRFGenericBomLine));
  }

  /**
   * Visit a RFGeneric after a Generic Component (link Generic Component - RFGeneric Component).<br>
   * A Generic Component is visited after a Generic Receptor and eventually a RFGeneric Receptor.
   *
   * @param aRFGenericBomLine   BOMLineHolder
   * @param theGenericReceptor  Map<K, V> the map with the Generic Receptor and RFGeneric Receptor properties
   * @param theGenericComponent Map<K, V> the map with the Generic Component properties
   */
  private void visitRFGenericAfterGenericComponent(final BOMLineHolder aRFGenericBomLine, final Map<K, V> theGenericReceptor,
                                                   final Map<K, V> theGenericComponent) {
    // Retrieve the Repere Component properties and merge it with the Component
    mergeComponentAndRepereComponent(theGenericComponent, getRFGenericForComponent(aRFGenericBomLine));

    // Add to report without modify the Receptor
    addToReport(theGenericReceptor, theGenericComponent);
  }

  /**
   * Visit a RFGeneric and a Part number (Part Aero) (link Generic Receptor - RFGeneric - Part number Component).<br>
   * The RF Generic is before the Part number.
   *
   * @param aRFGenericBomLine  BOMLineHolder
   * @param theGenericReceptor Map<K, V> the map with the Generic Receptor
   */
  private void visitRFGenericAndPartNumber(final BOMLineHolder aRFGenericBomLine, final Map<K, V> theGenericReceptor) {
    Map<K, V> theComponent = getRFGenericForComponent(aRFGenericBomLine);
    for (BOMLineHolder actualChild : sortBomChildren(aRFGenericBomLine)) {
      if (actualChild.getSnecmaObject() == null) {
        continue;
      }
      if (actualChild.getSnecmaObject().isPart()) {
        // Retrieve the Component properties and merge it with the Receptor properties
        // Do not modify the Repere Component properties and the Receptor properties
        mergeComponentAndRepereComponent(theComponent, visitThisComponent(actualChild));

        // Add to the report without modify theReceptor
        addToReport(theGenericReceptor, theComponent);
      }
    }
  }

  /**
   * Visit a RF (RFPart or RFPartAssemb only) before a Part Component (link RF Component - Part Component).<br>
   * The parameter should not be a RF Generic.
   *
   * @param aRFBomLine  BOMLineHolder
   * @param theReceptor Map<K, V> the map with the Receptor and Repere properties.<br>
   *                    If the Receptor is a Generic : link GenericReceptor - RFGeneric Receptor.<br>
   *                    If the Receptor is a Part : link RF Receptor - Part Receptor.
   */
  private void visitRFBeforePart(final BOMLineHolder aRFBomLine, final Map<K, V> theReceptor) {
    // Retrieve the Repere Component properties
    Map<K, V> theComponent = getTheRepereForComponent(aRFBomLine);

    // Prepare RF when the Part will become a Receptor
    Map<K, V> theFuturRepereReceptorForParts = new HashMap<>();
    if (retrieveRepereReceptor) {
      theFuturRepereReceptorForParts = getTheRepereForReceptor(aRFBomLine);
    }

    boolean doVisit = this.effectivityBehaviour.shouldVisit(aRFBomLine);
    if (doVisit) {
      for (BOMLineHolder actualChildOfRF : sortBomChildren(aRFBomLine)) {
        if (actualChildOfRF.getSnecmaObject() == null) {
          continue;
        }
        if (actualChildOfRF.getSnecmaObject().isPart()) {
          visitPartAsComponent(actualChildOfRF, theReceptor, theComponent, theFuturRepereReceptorForParts);
        }
      }
    }
  }

  /**
   * Visit a Part as Receptor (link RF Receptor - Part Receptor).
   *
   * @param aPartBomLine      BOMLineHolder
   * @param theRepereReceptor Map<K, V> the map with the RF Receptor properties.
   */
  private void visitPartAsReceptor(final BOMLineHolder aPartBomLine, final Map<K, V> theRepereReceptor) {
    // Retrieve the Part Receptor properties and do not modify it
    Map<K, V> thePartReceptor = visitThisReceptor(aPartBomLine);
    String partType = aPartBomLine.getSnecmaObject().getType();
    if (SnecmaObjectTypes.isAltOrDet(partType)) { // Specific treatment of Alt or Det Receptor
      altOrDetBehaviour.treatAlternateOrDETAsReceptor(thePartReceptor);
      if (altOrDetBehaviour.shouldVisitPartsInPseudoFolder()) {
        // Visit the pseudo-folder
        visitPartsInPseudoFolder(aPartBomLine, thePartReceptor);
      }
      if (SnecmaObjectTypes.isAlt(partType)) {
        visitPartReceptorChilds(aPartBomLine, theRepereReceptor, thePartReceptor, altOrDetBehaviour.shouldVisitPartsBelowAlt());
      }
    }
    else {
      visitPartReceptorChilds(aPartBomLine, theRepereReceptor, thePartReceptor, visitPartBelowPart);
    }
  }

  private void visitPartReceptorChilds(final BOMLineHolder aPartBomLine, final Map<K, V> theRepereReceptor, Map<K, V> thePartReceptor, final boolean doVisitPartAsComponent) {
    for (BOMLineHolder actualChildOfPart : sortBomChildren(aPartBomLine)) {
      if (actualChildOfPart.getSnecmaObject() == null) {
        continue;
      }
      // Handle Part children
      if (actualChildOfPart.getSnecmaObject().isPart()) {
        if (doVisitPartAsComponent) {
          visitPartAsComponent(actualChildOfPart, thePartReceptor, receptorVisitor.emptyRf(), new HashMap<K, V>());
        }
        continue;
      }
      // Handle RF children
      if (actualChildOfPart.getSnecmaObject().isRFPartOrPartAssemb()) {
        // In order to mix Part Receptor and Repere Receptor without modify thePartReceptor
        Map<K, V> thePartReceptorWithRepereReceptor = new HashMap<>();
        thePartReceptorWithRepereReceptor.putAll(thePartReceptor);
        mergeReceptorAndRepereReceptor(thePartReceptorWithRepereReceptor, theRepereReceptor);

        // To have the Repere Receptor properties
        visitRFBeforePart(actualChildOfPart, thePartReceptorWithRepereReceptor);
      }
    }
  }

  /**
   * Visit a Part as Component (link RF Component - Part Component).
   *
   * @param aPartBomLine       BOMLineHolder
   * @param theReceptor        Map<K, V> the map with the Receptor and Repere properties.<br>
   *                           If the Receptor is a Generic : link GenericReceptor - RFGeneric Receptor.<br>
   *                           If the Receptor is a Part : link RF Receptor - Part Receptor.
   * @param theRepereComponent Map<K, V> the map with the RF Component properties
   */
  private void visitPartAsComponent(final BOMLineHolder aPartBomLine, final Map<K, V> theReceptor, final Map<K, V> theRepereComponent,
                                    final Map<K, V> aFuturRepereReceptorForParts) {
    // Visit this BomLineHolder, as a component
    Map<K, V> thePartComponent = visitThisComponent(aPartBomLine);

    // Retrieve the Component properties and merge it with the Receptor properties
    // Do not modify the Repere Component properties and the Receptor properties
    mergeComponentAndRepereComponent(thePartComponent, theRepereComponent);

    // Add to the report without modifying theReceptor
    addToReport(theReceptor, thePartComponent);

    String fatherType = aPartBomLine.getSnecmaObject().getType();
    if (SnecmaObjectTypes.ALTERNATE_REVISION.equals(fatherType) || SnecmaObjectTypes.DET_REVISION.equals(fatherType)) {
      if (altOrDetBehaviour.shouldVisitPartsBelowAlt()) {
        visitPartAsReceptor(aPartBomLine, aFuturRepereReceptorForParts);
      }
    }
    else {
      // Continue the expansion for browsing Component children
      visitPartAsReceptor(aPartBomLine, aFuturRepereReceptorForParts);
    }
  }

  /**
   * Adds a line for each part in the pseudoFolder, including the provided receptor map in each line.
   *
   * @param fatherAltOrDetBomLine the Alt (or Det) whose pseudo-folder we must visit
   */
  private void visitPartsInPseudoFolder(final BOMLineHolder fatherAltOrDetBomLine, final Map<K, V> fatherAltOrDetAsReceptor) {
    AbstractAltOrDetRevision iSnecmaObject = (AbstractAltOrDetRevision) fatherAltOrDetBomLine.getSnecmaObject();
    for (ISnecmaObject obj : iSnecmaObject.getPseudoFolder()) {
      Map<K, V> theLine = obj.accept(componentVisitor);
      theLine.putAll(fatherAltOrDetAsReceptor);
      altOrDetBehaviour.treatPartsInPseudoFolder(theLine);
      theReportTable.add(theLine);
    }
  }

  /**
   * Visit a BOMLineHolder's SnecmaObject using the {@link IReceptorVisitor}.
   */
  protected abstract Map<K, V> visitThisReceptor(final BOMLineHolder aBomLine);

  /**
   * Visit a BOMLineHolder's SnecmaObject using the {@link IComponentVisitor}.
   */
  protected abstract Map<K, V> visitThisComponent(final BOMLineHolder aBomLine);

  /**
   * Get the properties of a RFGeneric Receptor (link Generic Receptor - RFGeneric Receptor).<br>
   * Get the Admin properties on children in order to complete the RFGeneric properties.
   *
   * @param aRFGenericBomLine BOMLineHolder
   * @return theRepere Map<K, V> the RFGeneric Receptor properties
   */
  private Map<K, V> getRFGenericForReceptor(final BOMLineHolder aRFGenericBomLine) {
    // Retrieve the Repere properties
    Map<K, V> theRepere = visitThisReceptor(aRFGenericBomLine);

    for (BOMLineHolder actualChildOfRF : sortBomChildren(aRFGenericBomLine)) {
      // Sometimes the SnecmaObject in the BomLineholder may be null...
      if (actualChildOfRF.getSnecmaObject() == null) {
        continue;
      }

      String childType = actualChildOfRF.getSnecmaObject().getType();
      // We are only concerned about the Admin children of this RF
      if (!actualChildOfRF.getSnecmaObject().isAdmin()) {
        continue;
      }

      if (SnecmaObjectTypes.RF_ADMIN_REVISION.equals(childType) || SnecmaObjectTypes.RF_ADMIN_AG_REVISION.equals(childType)) {
        mergeRepereReceptorandAdmin(theRepere, visitThisReceptor(actualChildOfRF));
      }
    }
    return theRepere;
  }

  /**
   * Get the properties of a RFGeneric Component (link Generic Component - RFGeneric Component).<br>
   * Get the Admin properties on children in order to complete the RFGeneric properties.
   *
   * @param aRFGenericBomLine BOMLineHolder
   * @return theRepere Map<K, V> the RFGeneric Component properties
   */
  private Map<K, V> getRFGenericForComponent(final BOMLineHolder aRFGenericBomLine) {
    // Retrieve the Repere properties
    Map<K, V> theRepere = visitThisComponent(aRFGenericBomLine);

    for (BOMLineHolder actualChildOfRF : sortBomChildren(aRFGenericBomLine)) {
      // Sometimes the SnecmaObject in the BomLineholder may be null...
      if (actualChildOfRF.getSnecmaObject() == null) {
        continue;
      }

      String childType = actualChildOfRF.getSnecmaObject().getType();
      // We are only concerned about the Admin children of this RF
      if (!actualChildOfRF.getSnecmaObject().isAdmin()) {
        continue;
      }

      if (SnecmaObjectTypes.RF_ADMIN_REVISION.equals(childType) || SnecmaObjectTypes.RF_ADMIN_AG_REVISION.equals(childType)) {
        mergeRepereComponentandAdmin(theRepere, visitThisComponent(actualChildOfRF));
      }
    }
    return theRepere;
  }

  /**
   * Get the properties of a RF Receptor (except a RFGeneric) (link RF Receptor - Receptor).<br>
   * Get the Admin properties on children in order to complete the RF properties.
   *
   * @param aRFBomLine BOMLineHolder
   * @return theRepere Map<K, V> the RFGeneric Receptor properties
   */
  private Map<K, V> getTheRepereForReceptor(final BOMLineHolder aRFBomLine) {
    // Retrieve the Repere properties
    Map<K, V> theRepere = visitThisReceptor(aRFBomLine);

    String parentRfType = aRFBomLine.getSnecmaObject().getType();
    for (BOMLineHolder actualChildOfRF : sortBomChildren(aRFBomLine)) {
      // Sometimes the SnecmaObject in the BomLineholder may be null...
      if (actualChildOfRF.getSnecmaObject() == null) {
        continue;
      }

      String childType = actualChildOfRF.getSnecmaObject().getType();
      // We are only concerned about the Admin children of this RF
      if (!actualChildOfRF.getSnecmaObject().isAdmin()) {
        continue;
      }

      if (SnecmaObjectTypes.RF_PART_REVISION.equals(parentRfType)) {
        if (SnecmaObjectTypes.RF_ADMIN_REVISION.equals(childType) || SnecmaObjectTypes.RF_ADMIN_AP_REVISION.equals(childType)) {
          mergeRepereReceptorandAdmin(theRepere, visitThisReceptor(actualChildOfRF));
        }
      }
      else if (SnecmaObjectTypes.RF_PART_ASSEMB_REVISION.equals(parentRfType)
          && SnecmaObjectTypes.RF_ADMIN_A_REVISION.equals(childType)) {
        mergeRepereReceptorandAdmin(theRepere, visitThisReceptor(actualChildOfRF));
      }
    }
    return theRepere;
  }

  /**
   * Get the properties of a RF Component (except a RFGeneric) (link RF Component - Component).<br>
   * Get the Admin properties on children in order to complete the RF properties.
   *
   * @param aRFBomLine BOMLineHolder
   * @return theRepere Map<K, V> the RFGeneric Component properties
   */
  private Map<K, V> getTheRepereForComponent(final BOMLineHolder aRFBomLine) {
    // Retrieve the Repere properties
    Map<K, V> theRepere = visitThisComponent(aRFBomLine);

    String parentRfType = aRFBomLine.getSnecmaObject().getType();
    for (BOMLineHolder actualChildOfRF : sortBomChildren(aRFBomLine)) {
      // Sometimes the SnecmaObject in the BomLineholder may be null...
      if (actualChildOfRF.getSnecmaObject() == null) {
        continue;
      }

      String childType = actualChildOfRF.getSnecmaObject().getType();
      // We are only concerned about the Admin children of this RF
      if (!actualChildOfRF.getSnecmaObject().isAdmin()) {
        continue;
      }

      if (SnecmaObjectTypes.RF_PART_REVISION.equals(parentRfType)) {
        if (SnecmaObjectTypes.RF_ADMIN_REVISION.equals(childType) || SnecmaObjectTypes.RF_ADMIN_AP_REVISION.equals(childType)) {
          mergeRepereComponentandAdmin(theRepere, visitThisComponent(actualChildOfRF));
        }
      }
      else if (SnecmaObjectTypes.RF_PART_ASSEMB_REVISION.equals(parentRfType)
          && SnecmaObjectTypes.RF_ADMIN_A_REVISION.equals(childType)) {
        mergeRepereComponentandAdmin(theRepere, visitThisComponent(actualChildOfRF));
      }
    }
    return theRepere;
  }

  /**
   * Visit a specific first line if necessary for reports.
   *
   * @param bomHolder BOMLineHolder
   */
  protected abstract void visitSpecificFirstLine(final BOMLineHolder bomHolder);

  /**
   * Merge aReceptor and aRepereReceptor in aReceptor.<br>
   * Modify only aReceptor.
   *
   * @param aReceptor       Map<K, V>
   * @param aRepereReceptor Map<K, V>
   */
  protected abstract void mergeReceptorAndRepereReceptor(final Map<K, V> aReceptor, final Map<K, V> aRepereReceptor);

  /**
   * Merge aComponent and aRepereComponent and modify only aComponent.<br>
   * Modify only aComponent.
   *
   * @param aComponent       Map<K, V>
   * @param aRepereComponent Map<K, V>
   */
  protected abstract void mergeComponentAndRepereComponent(final Map<K, V> aComponent, final Map<K, V> aRepereComponent);

  /**
   * Merge aRepereReceptor and anAdmin and modify only aRepereReceptor.
   *
   * @param aRepereReceptor Map<K, V>
   * @param anAdmin         Map<K, V>
   */
  protected abstract void mergeRepereReceptorandAdmin(final Map<K, V> aRepereReceptor, final Map<K, V> anAdmin);

  /**
   * Merge aRepereComponent and anAdmin and modify only aRepereComponent.
   *
   * @param aRepereComponent Map<K, V>
   * @param anAdmin          Map<K, V>
   */
  protected abstract void mergeRepereComponentandAdmin(final Map<K, V> aRepereComponent, final Map<K, V> anAdmin);

  /**
   * Add aReceptor and aComponent to the R21 report.<br>
   * Do not modify aReceptor.
   *
   * @param aReceptor Map<K, V>
   */
  protected abstract void addToReport(final Map<K, V> aReceptor, final Map<K, V> aComponent);

}
