package com.capgemini.reports.bomlineholder.comparison;

import com.capgemini.framework.teamcenter.authent.TcSession;
import com.capgemini.reports.bomlineholder.BOMLineHolder;
import com.capgemini.reports.bomlineholder.flattener.line.*;
import com.capgemini.reports.releasestatus.EffectivityBehaviourHelper;
import com.capgemini.reports.snecmaobjects.ISnecmaObject;
import com.capgemini.reports.snecmaobjects.part.AbstractPart;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;

/**
 * This class analyses two {@link BOMLineHolder} and recursively <i>flattens</i> their tree structure into a {@link List} of Receptor-component links.<br>
 * Each Receptor-component relationship can be expressed with a {@link SnecmaLine} object like {@link AbstractQuad}, {@link AbstractTopLine}, {@link AbstractPartNumber}, {@link AbstractPartRfPart}, {@link AbstractPartPart}, {@link AbstractReceptorLine}, {@link AbstractProductGeneric}.<br>
 * These {@link SnecmaLine}s are all able to receive a transformer object of type U, and produce a result of type T.<br>
 * The result is two {@link List}s of {@link SnecmaLine}s which can be obtained with {@link AbstractDualBomLineFlattener#getConfig1()} and {@link AbstractDualBomLineFlattener#getConfig2()}.
 *
 * @param <T> a Transformer object for transforming each {@link SnecmaLine} and producing results of type U.
 * @param <U> the type of results, as an outcome of a transformation by object of type T.
 */
@Getter
public abstract class AbstractDualBomLineFlattener<T, U> extends DualBomLineBrowser implements IDualSnecmaLineProcessor {

    private final List<SnecmaLine<T, U>> config1 = new ArrayList<>();
    private final List<SnecmaLine<T, U>> config2 = new ArrayList<>();
    @Setter(value = AccessLevel.PROTECTED)
    private DealWithAltOrDetPseudoFolder<T, U> pseudoFolderBehaviour;

    /**
     * Constructs a BomLineFlattener with the specified behaviour regarding effectivity, parts in pseudo-folders, part numbers, etc.
     *
     * @param effectivityBehaviour                the behaviour to be had when having to resolve the effectivity of a Part (i.e. should a part that is managed by date visited?)
     * @param visitPartsInPseudoFolder            the behaviour to be had when encountering a pseudo-folder: should it be looked at, or not at all?
     * @param retrievePartNumberUnderRfGeneric    the behaviour to be had when encountering a Generic/RF Generic/Part: should it be looked at, or not at all?
     * @param visitPartBelowPart                  the behaviour to be had when encountering a Part right below an other (without RF in-between): should it be looked at, or not at all?
     * @param pseudoFolderBehaviour               the behaviour to be had when encountering a psuedo-folder. Can produce useful objects when invoked on one
     * @param bomComparator                       {@link Comparator} for {@link BOMLineHolder}s, to know which bomlines are matching accross configurations
     * @param itemComparator                      {@link Comparator} for {@link ISnecmaObject}s, to know which items are matching <i>in pseudo-folders</i> accross configurations
     * @param generateBrotherLinesBeforeRecursion if <code>true</code>, then all brother lines of a parent object will be generated <i>before</i> recursing through the brothers' children
     */
    public AbstractDualBomLineFlattener(final EffectivityBehaviourHelper effectivityBehaviour, final boolean visitPartsInPseudoFolder,
                                        final boolean retrievePartNumberUnderRfGeneric, final boolean visitPartBelowPart,
                                        final DealWithAltOrDetPseudoFolder<T, U> pseudoFolderBehaviour, final Comparator<BOMLineHolder> bomComparator,
                                        final Comparator<ISnecmaObject> itemComparator, final boolean generateBrotherLinesBeforeRecursion) {
        super(effectivityBehaviour, visitPartsInPseudoFolder, retrievePartNumberUnderRfGeneric, visitPartBelowPart, bomComparator, itemComparator);
        this.pseudoFolderBehaviour = pseudoFolderBehaviour;
        if (generateBrotherLinesBeforeRecursion) {
            this.setPreProcessor(this);
        } else {
            this.setProcessor(this);
        }
    }

    @Override
    public void visitPartsInPseudoFolder(final OptionalBomPair aPart, final OptionalPartPair child) {
        if (aPart.firstExists() && child.firstExists()) {
            Collection<SnecmaLine<T, U>> setFromPseudoFolder = pseudoFolderBehaviour.dealWithIt(aPart.getFirst(), child.getFirst());
            config1.addAll(setFromPseudoFolder);
        }
        if (aPart.secondExists() && child.secondExists()) {
            Collection<SnecmaLine<T, U>> setFromPseudoFolder = pseudoFolderBehaviour.dealWithIt(aPart.getSecond(), child.getSecond());
            config2.addAll(setFromPseudoFolder);
        }
    }

    @Override
    public void visitQuad(final TcSession session, final OptionalBomPair receptor, final OptionalBomPair rfReceptor, final OptionalBomPair component,
                          final OptionalBomPair rfComponent) {
        if (receptor.firstExists() && rfReceptor.firstExists() && component.firstExists() && rfComponent.firstExists()) {
            config1.add(makeQuad(session, receptor.getFirst(), rfReceptor.getFirst(), component.getFirst(), rfComponent.getFirst()));
        }
        if (receptor.secondExists() && rfReceptor.secondExists() && component.secondExists() && rfComponent.secondExists()) {
            config2.add(makeQuad(session, receptor.getSecond(), rfReceptor.getSecond(), component.getSecond(), rfComponent.getSecond()));
        }
    }

    @Override
    public void visitTopBom(final OptionalBomPair aTopBomLine) {
        AbstractTopLine<T, U> topLine1 = maketopline(aTopBomLine.getFirst());
        AbstractTopLine<T, U> topLine2 = maketopline(aTopBomLine.getSecond());
        if (topLine1 != null && topLine2 != null) {
            config1.add(topLine1);
            config2.add(topLine2);
        }
    }

    @Override
    public void visitProductGeneric(final TcSession session, final OptionalBomPair productReceptor, final OptionalBomPair genericComponent,
                                    final OptionalBomPair rfGenericComponent) {
        if (productReceptor.firstExists() && genericComponent.firstExists() && rfGenericComponent.firstExists()) {
            AbstractProductGeneric<T, U> productGeneric1 =
                    makeProductGeneric(session, productReceptor.getFirst(), genericComponent.getFirst(), rfGenericComponent.getFirst());
            if (productGeneric1 != null) {
                config1.add(productGeneric1);
            }
        }
        if (productReceptor.secondExists() && genericComponent.secondExists() && rfGenericComponent.secondExists()) {
            AbstractProductGeneric<T, U> productGeneric2 =
                    makeProductGeneric(session, productReceptor.getSecond(), genericComponent.getSecond(), rfGenericComponent.getSecond());
            if (productGeneric2 != null) {
                config2.add(productGeneric2);
            }
        }
    }

    @Override
    public void visitPartNumber(final OptionalBomPair genericReceptor, final OptionalBomPair rfGeneric, final OptionalBomPair partNumber) {
        if (genericReceptor.firstExists() && rfGeneric.firstExists() && partNumber.firstExists()) {
            AbstractPartNumber<T, U> partNumberLine1 = makePartNumber(genericReceptor.getFirst(), rfGeneric.getFirst(), partNumber.getFirst());
            if (partNumberLine1 != null) {
                config1.add(partNumberLine1);
            }
        }
        if (genericReceptor.secondExists() && rfGeneric.secondExists() && partNumber.secondExists()) {
            AbstractPartNumber<T, U> partNumberLine2 = makePartNumber(genericReceptor.getSecond(), rfGeneric.getSecond(), partNumber.getSecond());
            if (partNumberLine2 != null) {
                config2.add(partNumberLine2);
            }
        }
    }

    @Override
    public void visitPartAsComponent(final TcSession session, final OptionalBomPair partReceptor, final OptionalBomPair rfPartComponent, final OptionalBomPair partComponent) {
        if (partReceptor.firstExists() && rfPartComponent.firstExists() && partComponent.firstExists()) {
            AbstractPartRfPart<T, U> partRfPart1 = makePartRfPart(session, partReceptor.getFirst(), rfPartComponent.getFirst(), partComponent.getFirst());
            if (partRfPart1 != null) {
                config1.add(partRfPart1);
            }
        }
        if (partReceptor.secondExists() && rfPartComponent.secondExists() && partComponent.secondExists()) {
            AbstractPartRfPart<T, U> partRfPart2 = makePartRfPart(session, partReceptor.getSecond(), rfPartComponent.getSecond(), partComponent.getSecond());
            if (partRfPart2 != null) {
                config2.add(partRfPart2);
            }
        }
    }

    @Override
    public void visitPartPart(final OptionalBomPair partReceptor, final OptionalBomPair partComponent) {
        if (partReceptor.firstExists() && partComponent.firstExists()) {
            AbstractPartPart<T, U> partPart1 = makePartPart(partReceptor.getFirst(), partComponent.getFirst());
            if (partPart1 != null) {
                config1.add(partPart1);
            }
        }
        if (partReceptor.secondExists() && partComponent.secondExists()) {
            AbstractPartPart<T, U> partPart2 = makePartPart(partReceptor.getSecond(), partComponent.getSecond());
            if (partPart2 != null) {
                config2.add(partPart2);
            }
        }
    }

    @Override
    public void visitReceptorWithoutComponent(final TcSession session, final OptionalBomPair part, final OptionalBomPair rfPart) {
        if (part.firstExists() && rfPart.firstExists()) {
            AbstractReceptorLine<T, U> receptorLine = makeReceptorLine(session, part.getFirst(), rfPart.getFirst());
            if (receptorLine != null) {
                config1.add(receptorLine);
            }
        }
        if (part.secondExists() && rfPart.secondExists()) {
            AbstractReceptorLine<T, U> receptorLine = makeReceptorLine(session, part.getSecond(), rfPart.getSecond());
            if (receptorLine != null) {
                config2.add(receptorLine);
            }
        }
    }

    protected abstract AbstractQuad<T, U> makeQuad(TcSession session, final BOMLineHolder receptor, final BOMLineHolder rfReceptor, final BOMLineHolder component,
                                                   final BOMLineHolder rfComponent);

    protected abstract AbstractTopLine<T, U> maketopline(final BOMLineHolder aTopBomLine);

    protected abstract AbstractPartNumber<T, U> makePartNumber(final BOMLineHolder genericReceptor, final BOMLineHolder rfGeneric,
                                                               final BOMLineHolder partAero);

    protected abstract AbstractPartRfPart<T, U> makePartRfPart(TcSession session, final BOMLineHolder partReceptor, final BOMLineHolder rfPartComponent,
                                                               final BOMLineHolder partComponent);

    protected abstract AbstractPartPart<T, U> makePartPart(final BOMLineHolder partReceptor, final BOMLineHolder partComponent);

    protected abstract AbstractReceptorLine<T, U> makeReceptorLine(TcSession session, final BOMLineHolder part, final BOMLineHolder rfPart);

    protected abstract AbstractProductGeneric<T, U> makeProductGeneric(TcSession session, final BOMLineHolder productReceptor, final BOMLineHolder genericComponent,
                                                                       final BOMLineHolder rfGenericComponent);

    /**
     * Behaviour to be had when encountering a pseudoFolder.<br>
     * The objective of the behaviour is to produce a set of objects representing the contents of the pseudofolder, as a {@link Collection} of {@link SnecmaLine}. <br>
     * A {@link SnecmaLine} can produce an object of type T when submitted to a transformaer object of Type U.
     *
     * @param <T> the type of object the {@link SnecmaLine}s can produce
     * @param <U> the type of Transformer that can be applied to the SnecmaLine
     */
    public interface DealWithAltOrDetPseudoFolder<T, U> {

        /**
         * Deals with a {@link BOMLineHolder} holding reference to an Alt or a DET.
         *
         * @param bomLineHolder
         * @param iSnecmaObject
         * @return a collections of return objects.
         */
        Collection<SnecmaLine<T, U>> dealWithIt(final BOMLineHolder bomLineHolder, final AbstractPart iSnecmaObject);
    }

}
