package com.capgemini.reports.bomlineholder.visitor;

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.BOMLineHolderHelper;
import com.capgemini.reports.bomlineholder.ISnecmaLineProcessor;
import com.capgemini.reports.bomlineholder.flattener.line.SnecmaLine;
import com.capgemini.reports.exceptions.InvalidInputException;
import com.capgemini.reports.exceptions.MalformedNomenclatureException;
import com.capgemini.reports.filter.AllIsGoodFilter;
import com.capgemini.reports.filter.IFilterExplosionNomenclature;
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.AbstractPart;
import com.capgemini.reports.snecmaobjects.part.AlternateRevision;
import com.capgemini.reports.snecmaobjects.part.DETRevision;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;

import java.util.List;

/**
 * Browse a {@link BOMLineHolder} tree, and presents methods for dealing with a coherent set of objects having a strong relationship in the Bom, like Generic-RfGeneric-PartAero.<br>
 * The various abstract methods each expose such coherent sets of objects, and are called in the browsing order.
 */
@Getter
@Setter
public class BomLineBrowser {

    // Sub-components that define the behaviour of this Visitor
    private final EffectivityBehaviourHelper effectivityBehaviour;
    private final boolean doRetrievePartNumberUnderRfGeneric;
    private final boolean doVisitPartBelowPart;
    private final boolean doVisitPartsInPseudoFolder;

    /**
     * This filter can remove whole parts of the bom Tree.
     */
    @Setter
    private IFilterExplosionNomenclature filterNomenclature;

    /**
     * This {@link ISnecmaLineProcessor} is called sequentially on all the {@link SnecmaLine}s of a receptor, BEFORE recursing.
     */
    @Setter(value = AccessLevel.PROTECTED)
    private ISnecmaLineProcessor preProcessor;

    /**
     * This {@link ISnecmaLineProcessor} is eventually called on all the {@link SnecmaLine}s of a receptor, through depth-first recursion.
     */
    @Setter(value = AccessLevel.PROTECTED)
    private ISnecmaLineProcessor processor;

    /**
     * Constructor.
     *
     * @param effectivityBehaviour             : the behaviour regarding Effectivities
     * @param visitPartsInPseudoFolder         : the behaviour regarding pseudofolders of {@link AlternateRevision} or {@link DETRevision} parts.
     * @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 BomLineBrowser(final EffectivityBehaviourHelper effectivityBehaviour, final boolean visitPartsInPseudoFolder,
                          final boolean retrievePartNumberUnderRfGeneric, final boolean visitPartBelowPart) {
        this.effectivityBehaviour = effectivityBehaviour;
        this.doVisitPartsInPseudoFolder = visitPartsInPseudoFolder;
        this.doRetrievePartNumberUnderRfGeneric = retrievePartNumberUnderRfGeneric;
        this.doVisitPartBelowPart = visitPartBelowPart;
        this.processor = new PassiveProcessor();
        this.filterNomenclature = new AllIsGoodFilter();
    }

    private static BOMLineHolder extractRfGeneric(final BOMLineHolder aGeneric) throws MalformedNomenclatureException {
        try {
            return BOMLineHolderHelper.findRfGenericBL(aGeneric);
        } catch (InvalidInputException e) {
            throw new MalformedNomenclatureException("No RF Generic found below a null Generic", e);
        }
    }

    /**
     * Visit a Top BOMLineHolder and expand all the children to retrieve the properties.
     */
    public void visitTopReceptorBom(final TcSession session, final BOMLineHolder aTopBomLine) throws MalformedNomenclatureException {
        ISnecmaObject theSnecmaObject = aTopBomLine.getSnecmaObject();
        if (theSnecmaObject != null) {
            processor.visitTopBom(aTopBomLine);
            if (theSnecmaObject.isProduct()) {
                visitProductAsReceptor(session, aTopBomLine);
            } else if (theSnecmaObject.isGeneric()) {
                visitGenericAsReceptor(session, aTopBomLine);
            } else if (theSnecmaObject.isPart()) {
                visitPartAsReceptor(session, aTopBomLine);
            } else {
                throw new MalformedNomenclatureException("Unupported bom: topLine " + aTopBomLine.getSnecmaObject().getItemId() +
                        " is not a Generic, a Product, or a Part. It is a " + aTopBomLine.getSnecmaObject().getType());
            }
        } else {
            throw new MalformedNomenclatureException("Empty top BomLine");
        }
    }

    private void visitPartAsReceptor(final TcSession session, final BOMLineHolder aPart) {
        if (preProcessor != null) {
            for (BOMLineHolder child : aPart.getBomChilds()) {
                if (filterNomenclature.isExcluded(session, child)) {
                    continue;
                }
                if (doVisitPartBelowPart && child.getSnecmaObject().isPart()) {
                    preProcessor.visitPartPart(aPart, child); // Non-recursive
                }
                if (child.getSnecmaObject().isRFPartOrPartAssemb()) {
                    preProcessor.visitReceptorRepere(session, aPart, child);
                    visitRfPartNoRec(session, aPart, child);
                }
            }
            if (SnecmaObjectTypes.isAltOrDet(aPart.getSnecmaObject().getType()) && doVisitPartsInPseudoFolder) { // Specific treatment of Alt or Det Receptor
                List<AbstractPart> pseudoFolder1 = ((AbstractAltOrDetRevision) aPart.getSnecmaObject()).getPseudoFolder();
                for (AbstractPart child : pseudoFolder1) {
                    if (!filterNomenclature.isExcluded(child)) {
                        // Visit the pseudo-folder
                        preProcessor.visitPartsInPseudoFolder(aPart, child);
                    }
                }
            }
        }

        // There may be other children, like some Parts, RF Parts
        for (BOMLineHolder child : aPart.getBomChilds()) {
            if (filterNomenclature.isExcluded(session, child)) {
                continue;
            }
            if (doVisitPartBelowPart && child.getSnecmaObject().isPart()) {
                processor.visitPartPart(aPart, child);
                visitPartAsReceptor(session, child); // Recursive
            }
            if (child.getSnecmaObject().isRFPartOrPartAssemb()) {
                visitRfPart(session, aPart, child);
            }
        }
        if (SnecmaObjectTypes.isAltOrDet(aPart.getSnecmaObject().getType()) && doVisitPartsInPseudoFolder) { // Specific treatment of Alt or Det Receptor
            List<AbstractPart> pseudoFolder1 = ((AbstractAltOrDetRevision) aPart.getSnecmaObject()).getPseudoFolder();
            for (AbstractPart child : pseudoFolder1) {
                if (!filterNomenclature.isExcluded(child)) {
                    processor.visitPartsInPseudoFolder(aPart, child);
                }
            }
        }
    }

    private void visitRfPartNoRec(final TcSession session, final BOMLineHolder aPart, final BOMLineHolder rfPart) {
        boolean doVisit = this.effectivityBehaviour.shouldVisit(rfPart);
        if (doVisit) {
            boolean partFound = false;
            for (BOMLineHolder child : rfPart.getBomChilds()) {
                if (child.getSnecmaObject().isPart()) {
                    partFound = true;
                    if (filterNomenclature.isExcluded(session, child)) {
                        continue;
                    }
                    if (child.getSnecmaObject().isPart()) {
                        preProcessor.visitPartAsComponent(aPart, rfPart, child);
                    }
                }
            }
            if (!partFound) {
                preProcessor.visitReceptorWithoutComponent(aPart, rfPart);
            }
        }
    }

    private void visitRfPart(final TcSession session, final BOMLineHolder aPart, final BOMLineHolder rfPart) {
        boolean doVisit = this.effectivityBehaviour.shouldVisit(rfPart);
        if (doVisit) {
            processor.visitReceptorRepere(session, aPart, rfPart);
            boolean partFound = false;
            for (BOMLineHolder child : rfPart.getBomChilds()) {
                if (child.getSnecmaObject().isPart()) {
                    partFound = true;
                    if (filterNomenclature.isExcluded(session, child)) {
                        continue;
                    }
                    processor.visitPartAsComponent(aPart, rfPart, child);
                    visitPartAsReceptor(session, child);
                }
            }
            if (!partFound) {
                processor.visitReceptorWithoutComponent(aPart, rfPart);
            }
        }
    }

    private void visitProductAsReceptor(final TcSession session, final BOMLineHolder productReceptors) throws MalformedNomenclatureException {
        if (preProcessor != null) {
            for (BOMLineHolder child : productReceptors.getBomChilds()) {
                if (child.getSnecmaObject().isGeneric() && !filterNomenclature.isExcluded(session, child)
                        && !filterNomenclature.isExcluded(session, productReceptors)) {
                    BOMLineHolder rfGenericComponent = extractRfGeneric(child);
                    preProcessor.visitProductGeneric(productReceptors, child, rfGenericComponent);
                    preProcessor.visitReceptorRepere(session, productReceptors, rfGenericComponent); // Receptor appears bundled with its component's repere
                }
            }
        }

        // Recursive processing
        for (BOMLineHolder child : productReceptors.getBomChilds()) {
            if (child.getSnecmaObject().isGeneric() && !filterNomenclature.isExcluded(session, child) && !filterNomenclature.isExcluded(session, productReceptors)) {
                // According to the MDD, there is one and only one RF Generic below any Generic.
                BOMLineHolder rfGenericComponent = extractRfGeneric(child);
                processor.visitProductGeneric(productReceptors, child, rfGenericComponent);
                processor.visitReceptorRepere(session, productReceptors, rfGenericComponent);
                // Now the component becomes receptor
                visitGenericAsReceptor(session, child);
            }
        }

    }

    private void visitGenericAsReceptor(final TcSession session, final BOMLineHolder genericReceptor) throws MalformedNomenclatureException {
        BOMLineHolder rfGeneric = extractRfGeneric(genericReceptor);
        if (preProcessor != null) {
            // Pre-processing
            if (doRetrievePartNumberUnderRfGeneric) {
                // Go ahead and extract the generic / Rf Generic / Part
                visitRfGeneric(session, genericReceptor, rfGeneric, preProcessor);
            }
            // There may be other children, like some RF parts
            for (BOMLineHolder child : genericReceptor.getBomChilds()) {
                if (filterNomenclature.isExcluded(session, child)) {
                    continue;
                }
                if (child.getSnecmaObject().isRFPartOrPartAssemb()) {
                    visitRfPartAsComponentNoRec(session, genericReceptor, rfGeneric, child);
                } else if (child.getSnecmaObject().isGeneric()) {
                    visitGenericAsComponentNoRec(session, genericReceptor, rfGeneric, child);
                }
            }
        }

        // Recursive treatment now
        if (doRetrievePartNumberUnderRfGeneric) {
            // Go ahead and extract the generic / Rf Generic / Part
            visitRfGeneric(session, genericReceptor, rfGeneric, processor);
        }

        // There may be other children, like some RF parts
        for (BOMLineHolder child : genericReceptor.getBomChilds()) {
            if (filterNomenclature.isExcluded(session, child)) {
                continue;
            }
            if (child.getSnecmaObject().isRFPartOrPartAssemb()) {
                visitRfPartAsComponentRec(session, genericReceptor, rfGeneric, child);
            } else if (child.getSnecmaObject().isGeneric()) {
                BOMLineHolder rfGenericComponent = extractRfGeneric(child);
                if (!filterNomenclature.isExcluded(session, rfGenericComponent)) {
                    processor.visitQuad(genericReceptor, rfGeneric, child, rfGenericComponent);
                    processor.visitReceptorRepere(session, genericReceptor, rfGenericComponent);
                    visitGenericAsReceptor(session, child);
                }
            }
        }
    }

    private void visitGenericAsComponentNoRec(final TcSession session, final BOMLineHolder genericReceptor, final BOMLineHolder rfGenericReceptor,
                                              final BOMLineHolder genericComponent) throws MalformedNomenclatureException {
        // According to the MDD, there is one and only one RF Generic below any Generic.
        BOMLineHolder rfGenericComponent = extractRfGeneric(genericComponent);
        if (!filterNomenclature.isExcluded(session, rfGenericComponent)) {
            preProcessor.visitReceptorRepere(session, genericReceptor, rfGenericComponent);
            preProcessor.visitQuad(genericReceptor, rfGenericReceptor, genericComponent, rfGenericComponent);
        }
    }

    private void visitRfPartAsComponentNoRec(final TcSession session, final BOMLineHolder receptor, final BOMLineHolder rfReceptor, final BOMLineHolder rfPart) {
        if (effectivityBehaviour.shouldVisit(rfPart)) {
            boolean partFound = false;
            preProcessor.visitReceptorRepere(session, receptor, rfPart);
            for (BOMLineHolder child : rfPart.getBomChilds()) {
                if (child.getSnecmaObject().isPart()) {
                    if (!filterNomenclature.isExcluded(session, child)) {
                        preProcessor.visitQuad(receptor, rfReceptor, child, rfPart);
                    }
                    partFound = true;
                }
            }
            if (!partFound) {
                preProcessor.visitReceptorWithoutComponent(receptor, rfPart);
            }
        }
    }

    private void visitRfPartAsComponentRec(final TcSession session, final BOMLineHolder receptor, final BOMLineHolder rfReceptor, final BOMLineHolder rfPart) {
        if (effectivityBehaviour.shouldVisit(rfPart)) {
            // Recursivity
            boolean partFound = false;
            processor.visitReceptorRepere(session, receptor, rfPart);
            for (BOMLineHolder child : rfPart.getBomChilds()) {
                if (child.getSnecmaObject().isPart()) {
                    if (!filterNomenclature.isExcluded(session, child)) {
                        processor.visitQuad(receptor, rfReceptor, child, rfPart);
                        visitPartAsReceptor(session, child);
                    }
                    partFound = true;
                }
            }
            if (!partFound) {
                processor.visitReceptorWithoutComponent(receptor, rfPart);
            }
        }
    }

    // RF Generics have a single PartAero below them. Also below , RF Admin(s) carry data about this RfGeneric
    private void visitRfGeneric(final TcSession session, final BOMLineHolder genericReceptor, final BOMLineHolder rfGeneric, final ISnecmaLineProcessor proc)
            throws MalformedNomenclatureException {
        for (BOMLineHolder child : rfGeneric.getBomChilds()) {
            if (child.getSnecmaObject().isPart() && !filterNomenclature.isExcluded(session, child)) {
                proc.visitPartNumber(genericReceptor, rfGeneric, child);
                return;
            }
        }
        throw new MalformedNomenclatureException("No part aero found below RfGeneric");
    }

    private static class PassiveProcessor implements ISnecmaLineProcessor {

        @Override
        public void visitTopBom(final BOMLineHolder aTopBom) {
            // Nothing to do
        }

        @Override
        public void visitQuad(final BOMLineHolder receptor, final BOMLineHolder rfReceptor, final BOMLineHolder component,
                              final BOMLineHolder rfComponent) {
            // Nothing to do
        }

        @Override
        public void visitProductGeneric(final BOMLineHolder productReceptor, final BOMLineHolder genericComponent,
                                        final BOMLineHolder rfGenericComponent) {
            // Nothing to do
        }

        @Override
        public void visitPartNumber(final BOMLineHolder genericReceptor, final BOMLineHolder rfGeneric, final BOMLineHolder child) {
            // Nothing to do
        }

        @Override
        public void visitPartAsComponent(final BOMLineHolder part, final BOMLineHolder rfPart, final BOMLineHolder child) {
            // Nothing to do
        }

        @Override
        public void visitPartPart(final BOMLineHolder fatherPart, final BOMLineHolder childPart) {
            // Nothing to do
        }

        @Override
        public void visitPartsInPseudoFolder(final BOMLineHolder aPart, final AbstractPart child) {
            // Nothing to do
        }

        @Override
        public void visitReceptorWithoutComponent(final BOMLineHolder part, final BOMLineHolder rfPart) {
            // Nothing to do
        }

        @Override
        public void visitReceptorRepere(TcSession session, final BOMLineHolder anObject, final BOMLineHolder aRF) {
            // Nothing to do
        }
    }

}
