package com.capgemini.reports.teamcenter.resolution.structureservice;

import com.capgemini.framework.teamcenter.authent.TcSession;
import com.capgemini.framework.teamcenter.exceptions.EmptyNomenclatureException;
import com.capgemini.framework.teamcenter.exceptions.NoSuchBomViewException;
import com.capgemini.framework.teamcenter.resolution.filtering.*;
import com.capgemini.reports.bomlineholder.BOMLineHolder;
import com.capgemini.reports.bomlineholder.BOMLineHolderHelper;
import com.capgemini.reports.exceptions.InvalidInputException;
import com.capgemini.reports.exceptions.MalformedNomenclatureException;
import com.capgemini.reports.filter.IFilterExplosionNomenclature;
import com.capgemini.reports.teamcenter.resolution.IBOMLineExplorer;
import com.capgemini.reports.teamcenter.resolution.structureservice.SingleStructureServiceCaller.SingleLevelExpander;
import com.teamcenter.soa.client.model.strong.BOMLine;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;

import static com.capgemini.framework.teamcenter.object.types.SnecmaObjectTypes.*;

/**
 * Helper dedicated to triple levels resolution of configurations.
 **/
@Slf4j
public class TripleStructureServiceCaller implements IStructureServiceCaller {

    private final TcSession session;

    /**
     * Constructor for Triple levels resolution.
     */
    public TripleStructureServiceCaller(final TcSession session) {
        this.session = session;
    }

    @Override
    public BOMLineHolder resolve(final BOMLine topBomLine, final IFilterExplosionNomenclature filter) throws EmptyNomenclatureException, NoSuchBomViewException, InvalidInputException, MalformedNomenclatureException {
        return new SingleStructureServiceCaller(session).resolve(topBomLine, new TripleLevelExplorer(filter));
    }

    private static class TripleLevelExplorer implements IBOMLineExplorer {
        private static final ITcTypeFilter RF_GENERIC_FILTER = new TcTypeInclusionFilter(RF_ADMIN_REVISION, RF_ADMIN_AG_REVISION, PART_AERO_REVISION);
        private static final ITcTypeFilter COMPONENT_REJECTOR = new TcTypeExclusionFilter(RF_PART_REVISION, RF_PART_ASSEMB_REVISION, GENERIC_REVISION, GENERIC_ASSEMB_REVISION);

        private final ITcTypeFilter componentFilter;

        TripleLevelExplorer(final ITcTypeFilter componentFilter) {
            this.componentFilter = componentFilter;
        }

        @Override
        public BOMLineHolder explore(final TcSession session, final SingleLevelExpander expander, final BOMLineHolder topLine) throws EmptyNomenclatureException, MalformedNomenclatureException {
            log.debug("Expand level 1...");
            BOMLineHolder expandedTopLine = expander.expand(topLine, AllFilter.getInstance());
            log.debug("Expand level 1 DONE, size = {}", expandedTopLine.getBomChilds().size());

            log.debug("Filter the results...");
            // filter out generics & rf generics from the rest (they must receive a special treatment)
            List<BOMLineHolder> genericsComponents = new ArrayList<>();
            List<BOMLineHolder> rfParts = new ArrayList<>();
            BOMLineHolder rfGenericReceptor = filterChildren(expandedTopLine.getBomChilds(), genericsComponents, rfParts);
            log.debug("Filtering DONE: {} generics, {} RF Generics and {} RF Part found", genericsComponents.size(), rfGenericReceptor == null ? 0 : 1, rfParts.size());

            ITcTypeFilter rfGenFilter = new OrFilter(RF_GENERIC_FILTER, componentFilter);
            if (rfGenericReceptor != null) { // happens If topline is a Generic / Generic Assemb
                for (BOMLineHolder bomChild : expander.expand(rfGenericReceptor, rfGenFilter).getBomChilds()) {
                    rfGenericReceptor.addChild(bomChild);
                }
            }

            // Simply expand the lvl 1 RF Parts
            log.debug("Expand All RF Parts..");
            List<BOMLineHolder> alternates = expandRfPartsExtractAlternates(expander, rfParts);
            log.debug("Expand RF Part DONE");

            // For each Alternate found expand one level further.
            log.debug("Expand Alternates...");
            //List<BOMLineHolder> expandedAlternates = expander.expandEach(alternates, AllFilter.getInstance());
            for (BOMLineHolder alternate : alternates) {
                for (BOMLineHolder bomChild : expander.expand(alternate, AllFilter.getInstance()).getBomChilds()) {
                    alternate.addChild(bomChild);
                }
            }
            log.debug("Expand Alternates DONE");

            // The Generics must be expanded. This may also return some RF Generics, which must be added to the existing list.
            List<BOMLineHolder> rfGenericComponents = expandGenericComponents(expander, genericsComponents);
            // RF Generics MUST have their RF Admin loaded because they hold relevant info
            // expander.expandEach(rfGenericComponents, rfGenFilter);
            for (BOMLineHolder rfGenericComponent : rfGenericComponents) {
                for (BOMLineHolder bomChild : expander.expand(rfGenericComponent, rfGenFilter).getBomChilds()) {
                    rfGenericComponent.addChild(bomChild);
                }
            }

            log.debug("Mono-level expansion DONE");
            return expandedTopLine;
        }


        /**
         * Second level of MONO explosion.<br>
         * This method expands the provided Generics, but only keeps their RF Generic (Assemb) children.<br>
         * they will be included into the tree of existing {@link BOMLineHolder}s.
         */
        List<BOMLineHolder> expandGenericComponents(final SingleLevelExpander expander, final List<BOMLineHolder> generics) throws EmptyNomenclatureException, MalformedNomenclatureException {
            log.debug("Expand newly found Generics..");
            //expander.expandEach(generics, new AndFilter(COMPONENT_REJECTOR, componentFilter)); // Each Generic now has children, but no further components
            for (BOMLineHolder generic : generics) {
                for (BOMLineHolder bomChild : expander.expand(generic, new TcTypeInclusionFilter(RF_GENERIC_REVISION)).getBomChilds()) {
                    generic.addChild(bomChild);
                }
            }

            // Filter only the RF Generics, add them to the BOMLineholder tree and return them
            List<BOMLineHolder> rfGenerics = addAndGetRfGenerics(generics);

            log.debug("Expand Generics DONE: {} RF Generics found", rfGenerics.size());
            return rfGenerics;
        }

        /**
         * Expands one level below the provided RF Generics. <br>
         * The generated children are all linked to the existing {@link BOMLineHolder}s.<br>
         * This method also looks for any Alternate and returns them.
         */
        List<BOMLineHolder> expandRfPartsExtractAlternates(final SingleLevelExpander expander, final List<BOMLineHolder> rfParts) throws EmptyNomenclatureException {
            // Expand one level
            log.debug("Expand RF Generics...");
            //List<BOMLineHolder> expandedRfParts = expander.expandEach(rfParts, AllFilter.getInstance());// Each RF Generic now has children
            for (BOMLineHolder rfPart : rfParts) {
                for (BOMLineHolder bomChild : expander.expand(rfPart, AllFilter.getInstance()).getBomChilds()) {
                    rfPart.addChild(bomChild);
                }
            }

            // look for alternates/DET in the children
            List<BOMLineHolder> alternates = new ArrayList<>();
            for (BOMLineHolder rfPartBom : rfParts) {
                for (BOMLineHolder child : rfPartBom.getBomChilds()) {
                    String objectType = child.getObjectType();
                    if (ALTERNATE_REVISION.equals(objectType) || DET_REVISION.equals(objectType)) {
                        alternates.add(child);
                    }
                }
            }
            log.debug("Expand RF Parts DONE: {} alternates found", alternates.size());
            return alternates;
        }

        /**
         * Adds the RG Generics found in the expansion results (with BOMLines) to the {@link BOMLineHolder} map provided.
         */
        List<BOMLineHolder> addAndGetRfGenerics(final List<BOMLineHolder> generics) throws MalformedNomenclatureException {
            List<BOMLineHolder> rfGenerics = new ArrayList<>();
            log.debug("addAndGetRfGenerics: {} items...", generics.size());
            for (BOMLineHolder gen : generics) {
                // Register this BOMLine's children, and create the parent-child link
                try {
                    rfGenerics.add(BOMLineHolderHelper.findRfGenericBL(gen));
                } catch (InvalidInputException e) {
                    log.error("Null BomLineHolder detected below a Generic", e);
                }
            }
            log.debug("addAndGetRfGenerics: {} RF Generics found", rfGenerics.size());
            return rfGenerics;
        }

        BOMLineHolder filterChildren(final List<BOMLineHolder> children, final List<BOMLineHolder> generics, final List<BOMLineHolder> rfPart) {
            BOMLineHolder theTopRfGen = null;
            for (BOMLineHolder child : children) {
                String objectType = child.getObjectType();
                if (GENERIC_REVISION.equals(objectType) || GENERIC_ASSEMB_REVISION.equals(objectType) || SUPERMODEL_REVISION.equals(objectType)) {
                    generics.add(child);
                } else {
                    if (RF_GENERIC_REVISION.equals(objectType)) {
                        theTopRfGen = child;
                    } else {
                        rfPart.add(child);
                    }
                }
            }
            return theTopRfGen;
        }
    }
}
