/* FichothequeLib_API - Copyright (c) 2020-2022 Vincent Calame - Exemole
 * Logiciel libre donné sous triple licence :
 * 1) selon les termes de la CeCILL V2
 * 2) selon les termes de l’EUPL V.1.1
 * 3) selon les termes de la GNU GPLv3
 * Voir le fichier licences.txt
 */


package net.fichotheque.utils.selection;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.fichotheque.SubsetKey;
import net.fichotheque.addenda.Addenda;
import net.fichotheque.addenda.Document;
import net.fichotheque.selection.DocumentCondition;
import net.fichotheque.selection.DocumentQuery;
import net.fichotheque.selection.DocumentSelector;
import net.fichotheque.selection.SelectionContext;
import net.fichotheque.utils.FichothequeUtils;
import net.fichotheque.utils.SelectionUtils;
import net.mapeadores.util.annotation.Nullable;
import net.fichotheque.junction.Junction;
import net.fichotheque.selection.JunctionCondition;


/**
 *
 * @author Vincent Calame
 */
public class DocumentSelectorBuilder {

    private final SelectionContext selectionContext;
    private final Map<SubsetKey, List<DocumentSelectEngine>> listMap = new HashMap<SubsetKey, List<DocumentSelectEngine>>();
    private final List<Addenda> addendaList = new ArrayList<Addenda>();
    private DocumentSelectEngine firstEngine;
    private int engineCount = 0;

    public DocumentSelectorBuilder(SelectionContext selectionContext) {
        this.selectionContext = selectionContext;
    }

    public DocumentSelectorBuilder add(DocumentQuery documentQuery, JunctionCondition junctionCondition) {
        Addenda[] addendaArray = SelectionUtils.toAddendaArray(selectionContext.getFichotheque(), documentQuery, selectionContext.getSubsetAccessPredicate());
        DocumentSelectEngine documentSelectEngine = new DocumentSelectEngine(documentQuery, junctionCondition, selectionContext);
        if (firstEngine == null) {
            firstEngine = documentSelectEngine;
        }
        engineCount++;
        for (Addenda addenda : addendaArray) {
            List<DocumentSelectEngine> list = checkAddenda(addenda);
            list.add(documentSelectEngine);
        }
        return this;
    }

    public DocumentSelectorBuilder addAll(Collection<DocumentCondition.Entry> entries) {
        for (DocumentCondition.Entry entry : entries) {
            add(entry.getDocumentQuery(), entry.getJunctionCondition());
        }
        return this;
    }

    public DocumentSelector toDocumentSelector() {
        switch (engineCount) {
            case 0: {
                List<Addenda> finalAddendaList = FichothequeUtils.wrap(FichothequeUtils.toAddendaArray(selectionContext.getFichotheque(), selectionContext.getSubsetAccessPredicate()));
                return new UniqueDocumentSelector(finalAddendaList, null, new DocumentSelectEngine(SelectionUtils.EMPTY_DOCUMENTQUERY, null, selectionContext));
            }
            case 1: {
                List<Addenda> finalAddendaList = FichothequeUtils.wrap(addendaList.toArray(new Addenda[addendaList.size()]));
                return new UniqueDocumentSelector(finalAddendaList, new HashSet<SubsetKey>(listMap.keySet()), firstEngine);
            }
            default: {
                List<Addenda> finalAddendaList = FichothequeUtils.wrap(addendaList.toArray(new Addenda[addendaList.size()]));
                Map<SubsetKey, DocumentSelectEngine[]> arrayMap = new HashMap<SubsetKey, DocumentSelectEngine[]>();
                for (Map.Entry<SubsetKey, List<DocumentSelectEngine>> entry : listMap.entrySet()) {
                    List<DocumentSelectEngine> list = entry.getValue();
                    DocumentSelectEngine[] array = list.toArray(new DocumentSelectEngine[list.size()]);
                    arrayMap.put(entry.getKey(), array);
                }
                return new MultiDocumentSelector(finalAddendaList, arrayMap);
            }
        }
    }

    private List<DocumentSelectEngine> checkAddenda(Addenda addenda) {
        List<DocumentSelectEngine> currentList = listMap.get(addenda.getSubsetKey());
        if (currentList == null) {
            currentList = new ArrayList<DocumentSelectEngine>();
            addendaList.add(addenda);
            listMap.put(addenda.getSubsetKey(), currentList);
        }
        return currentList;
    }

    public static DocumentSelectorBuilder init(SelectionContext selectionContext) {
        return new DocumentSelectorBuilder(selectionContext);
    }


    private static class MultiDocumentSelector implements DocumentSelector {

        private final List<Addenda> addendaList;
        private final Map<SubsetKey, DocumentSelectEngine[]> arrayMap;

        private MultiDocumentSelector(List<Addenda> addendaList, Map<SubsetKey, DocumentSelectEngine[]> arrayMap) {
            this.addendaList = addendaList;
            this.arrayMap = arrayMap;
        }

        @Override
        public List<Addenda> getAddendaList() {
            return addendaList;
        }

        @Override
        public boolean test(Document document) {
            DocumentSelectEngine[] array = arrayMap.get(document.getSubsetKey());
            if (array == null) {
                return false;
            }
            for (DocumentSelectEngine engine : array) {
                if (engine.isSelected(document)) {
                    return true;
                }
            }
            return false;
        }

        @Override
        public Junction isSelected(Document document, Junction junction) {
            DocumentSelectEngine[] array = arrayMap.get(document.getSubsetKey());
            if (array == null) {
                return null;
            }
            FilteredJunctionBuilder builder = new FilteredJunctionBuilder(junction);
            for (DocumentSelectEngine engine : array) {
                engine.filter(document, builder);
            }
            return builder.toJunction();
        }

    }


    private static class UniqueDocumentSelector implements DocumentSelector {

        private final List<Addenda> addendaList;
        private final Set<SubsetKey> keySet;
        private final DocumentSelectEngine documentSelectEngine;

        private UniqueDocumentSelector(List<Addenda> addendaList, @Nullable Set<SubsetKey> keySet, DocumentSelectEngine documentSelectEngine) {
            this.addendaList = addendaList;
            this.keySet = keySet;
            this.documentSelectEngine = documentSelectEngine;
        }

        @Override
        public List<Addenda> getAddendaList() {
            return addendaList;
        }

        @Override
        public boolean test(Document document) {
            if ((keySet != null) && (!keySet.contains(document.getSubsetKey()))) {
                return false;
            }
            return documentSelectEngine.isSelected(document);
        }

        @Override
        public Junction isSelected(Document document, Junction junction) {
            if ((keySet != null) && (!keySet.contains(document.getSubsetKey()))) {
                return null;
            }
            FilteredJunctionBuilder builder = new FilteredJunctionBuilder(junction);
            documentSelectEngine.filter(document, builder);
            return builder.toJunction();
        }

    }

}
