package com.capgemini.reports.bomlineholder.flattener;

import com.capgemini.framework.teamcenter.authent.TcSession;
import com.capgemini.reports.bomlineholder.BOMLineHolder;
import com.capgemini.reports.bomlineholder.ISnecmaLineProcessor;
import com.capgemini.reports.bomlineholder.flattener.line.*;
import com.capgemini.reports.bomlineholder.visitor.BomLineBrowser;
import com.capgemini.reports.releasestatus.EffectivityBehaviourHelper;
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.List;

/**
 * This class analyses a {@link BOMLineHolder} and <i>flattens</i> its 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 procude a result of type T.
 *
 * @param <T> a Transformer object for transforming each line 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 AbstractBomLineFlattener<T, U> extends BomLineBrowser implements ISnecmaLineProcessor {

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

    /**
     * Constructs a BomLineFlattener with the specified behaviour regarding effectivity, parts in psuedo-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 pseudo-folder. Can produce useful objects when invoked on an Alternate/DET
     * @param generateBrotherLinesBeforeRecursion if true, then all the children will be visited without recursion first, then again with recursion.
     */
    public AbstractBomLineFlattener(final EffectivityBehaviourHelper effectivityBehaviour, final boolean visitPartsInPseudoFolder,
                                    final boolean retrievePartNumberUnderRfGeneric, final boolean visitPartBelowPart,
                                    final DealWithAltOrDetPseudoFolder<T, U> pseudoFolderBehaviour, final boolean generateBrotherLinesBeforeRecursion) {
        super(effectivityBehaviour, visitPartsInPseudoFolder, retrievePartNumberUnderRfGeneric, visitPartBelowPart);
        this.pseudoFolderBehaviour = pseudoFolderBehaviour;
        if (generateBrotherLinesBeforeRecursion) {
            this.setPreProcessor(this);
        } else {
            this.setProcessor(this);
        }
    }

    @Override
    public void visitPartsInPseudoFolder(final BOMLineHolder aPart, final AbstractPart child) {
        Collection<SnecmaLine<T, U>> setFromPseudoFolder = pseudoFolderBehaviour.dealWithIt(aPart, child);
        config.addAll(setFromPseudoFolder);
    }

    @Override
    public void visitQuad(final BOMLineHolder receptor, final BOMLineHolder rfReceptor, final BOMLineHolder component,
                          final BOMLineHolder rfComponent) {
        config.add(makeQuad(receptor, rfReceptor, component, rfComponent));
    }

    @Override
    public void visitTopBom(final BOMLineHolder aTopBomLine) {
        AbstractTopLine<T, U> topLine = makeTopline(aTopBomLine);
        if (topLine != null) {
            config.add(makeTopline(aTopBomLine));
        }
    }

    @Override
    public void visitProductGeneric(final BOMLineHolder productReceptor, final BOMLineHolder genericComponent,
                                    final BOMLineHolder rfGenericComponent) {
        AbstractProductGeneric<T, U> productGeneric = makeProductGeneric(productReceptor, genericComponent, rfGenericComponent);
        if (productGeneric != null) {
            config.add(productGeneric);
        }
    }

    @Override
    public void visitPartNumber(final BOMLineHolder genericReceptor, final BOMLineHolder rfGeneric, final BOMLineHolder child) {
        AbstractPartNumber<T, U> partNumber = makePartNumber(genericReceptor, rfGeneric, child);
        if (partNumber != null) {
            config.add(partNumber);
        }
    }

    @Override
    public void visitPartAsComponent(final BOMLineHolder partReceptor, final BOMLineHolder rfPartComponent, final BOMLineHolder partComponent) {
        AbstractPartRfPart<T, U> partRfPart = makePartRfPart(partReceptor, rfPartComponent, partComponent);
        if (partRfPart != null) {
            config.add(partRfPart);
        }
    }

    @Override
    public void visitPartPart(final BOMLineHolder partReceptor, final BOMLineHolder partComponent) {
        AbstractPartPart<T, U> partPart = makePartPart(partReceptor, partComponent);
        if (partPart != null) {
            config.add(partPart);
        }
    }

    @Override
    public void visitReceptorWithoutComponent(final BOMLineHolder part, final BOMLineHolder rfPart) {
        AbstractReceptorLine<T, U> receptorLine = makeReceptorLine(part, rfPart);
        if (receptorLine != null) {
            config.add(receptorLine);
        }
    }

    @Override
    public final void visitReceptorRepere(TcSession session, final BOMLineHolder anObject, final BOMLineHolder aRF) {
        // Nothing to do here! If you want a Receptor-Repere, use the AbstractRepereFlattener
    }

    protected abstract AbstractQuad<T, U> makeQuad(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(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(final BOMLineHolder part, final BOMLineHolder rfPart);

    protected abstract AbstractProductGeneric<T, U> makeProductGeneric(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 alt                a Alt {@link BOMLineHolder}
         * @param partInPseudoFolder a Part in the pseudo folder of the Alt
         * @return a collections of return objects.
         */
        Collection<SnecmaLine<T, U>> dealWithIt(final BOMLineHolder alt, final AbstractPart partInPseudoFolder);
    }

}
