package com.capgemini.reports.bomlineholder.processor;

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.BOMLineHolder.BOMLineHolderState;
import com.capgemini.reports.exceptions.NotSupportedException;
import com.capgemini.reports.releasestatus.DefaultRevisionSelector;
import com.capgemini.reports.releasestatus.RevisionSelector;
import com.capgemini.reports.snecmaobjects.AbstractSnecmaObject;
import com.capgemini.reports.snecmaobjects.ISnecmaObject;
import com.capgemini.reports.snecmaobjects.SnecmaObjectFactory;
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 com.teamcenter.soa.client.model.strong.Item;
import com.teamcenter.soa.client.model.strong.ItemRevision;
import lombok.extern.slf4j.Slf4j;

import java.util.*;

/**
 * This class does nothing specific with {@link AlternateRevision} or {@link DETRevision} parts. <br>
 * This class does provide some useful static methods like loading parts in pseudo folder, or obtaining the ItemRevision of an Alt or Det. <br>
 * Several methods should be overriden to do something useful on a as-needed basis, like:
 * <ul>
 * <li>{@link AltOrDetBehaviour#treatPartsInPseudoFolder(Map) fillRemainingFieldsOfPseudoFolder()}</li>
 * <li>{@link AltOrDetBehaviour#treatAlternateOrDETAsReceptor(Map) treatAltOrDetReceptor()}</li>
 * </ul>
 * Can produce a {@link List} of {@link Map}s with specified Key and Value types.
 *
 * @param <K> the Keys Type
 * @param <V> the Values Type
 */
@Slf4j
public class DefaultAltOrDetBehaviour<K, V> implements AltOrDetBehaviour<K, V>, IBOMLineHolderProcessor {
    private static final long serialVersionUID = 9107530299095760608L;

    protected final transient TcSession session;

    private final RevisionSelector revisionSelector;

    /**
     * Constructs a pseudo-folder loader.
     *
     * @param session an active {@link TcSession}
     * @param date    the limit date for revision's release date.
     */
    public DefaultAltOrDetBehaviour(final TcSession session, final Calendar date) {
        this.session = session;
        this.revisionSelector = new DefaultRevisionSelector(date);
    }

    @Override
    public boolean shouldVisitPartsInPseudoFolder() {
        return true;
    }

    @Override
    public boolean shouldVisitPartsBelowAlt() {
        return true;
    }

    @Override
    public void treatPartsInPseudoFolder(final Map<K, V> map) {
        // Don't know what to do here, I'm just the default behaviour... maybe you want to override me and do something useful instead?
    }

    @Override
    public void treatAlternateOrDETAsReceptor(final Map<K, V> receptorMap) {
        // Don't know what to do here, I'm just the default behaviour... maybe you want to override me and do something useful instead?
    }

    /**
     * Processes the Pseudo-folder. Each {@link Item} in the pseudo-folder of the {@link BOMLineHolder} will load a corresponding {@link ItemRevision}. <br>
     * A purpose-built algorithm is provided for electing the 'correct' revision, which is then loaded in a {@link ISnecmaObject} back into the {@link BOMLineHolder}
     *
     * @param aBomLineHolder a {@link BOMLineHolder} to process
     */
    @Override
    public void process(final BOMLineHolder aBomLineHolder) {
        ISnecmaObject snecmaObject = aBomLineHolder.getSnecmaObject();
        if (aBomLineHolder.getState() == BOMLineHolderState.PROCESSING && snecmaObject != null
                && SnecmaObjectTypes.isAltOrDet(snecmaObject.getType())) {

            AbstractAltOrDetRevision altOrDetRevision = (AbstractAltOrDetRevision) snecmaObject;
            // Select the revision of each Item
            Map<Item, ItemRevision> item2rev = selectAllRevisions(aBomLineHolder.getPseudoFolder());
            log.debug("Found a pseudo-Folder with {} parts in it. Found {} Revisions for them.", aBomLineHolder.getPseudoFolder().size(),
                    item2rev.size());

            // Now build the Revision into a ISnecmaObject
            altOrDetRevision.clearPseudoFolder();
            buildSnecmaObject(altOrDetRevision, item2rev.values());
        }
    }

    protected Map<Item, ItemRevision> selectAllRevisions(final List<Item> items) {
        Map<Item, ItemRevision> pseudoFolderRev = new HashMap<>();
        // This object has a pseudo-folder, let's find the correct revisions of its content
        for (Item item : items) {
            ItemRevision itemRev = revisionSelector.selectRevision(session, item).getItemRevision();
            pseudoFolderRev.put(item, itemRev);
        }
        return pseudoFolderRev;
    }

    protected Map<ItemRevision, AbstractPart> buildSnecmaObject(final AbstractAltOrDetRevision altOrDetRevision,
                                                                final Collection<ItemRevision> pseudoFolderRev) {
        Map<ItemRevision, AbstractPart> map = new HashMap<>();
        for (ItemRevision itemRev : pseudoFolderRev) {
            try {
                AbstractSnecmaObject objectInPF = SnecmaObjectFactory.build(session, itemRev);
                if (objectInPF.isPart()) {
                    AbstractPart partInPF = (AbstractPart) objectInPF;
                    altOrDetRevision.addInPseudoFolder(partInPF);
                    map.put(itemRev, partInPF);
                }
            } catch (NotSupportedException e) {
                log.error("Could not build one of ModelObjects from the Pseudo-folder of an Alt or Det", e);
            }
        }
        return map;
    }

}
