/* FichothequeLib_Tools - Copyright (c) 2006-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.tools.corpus;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.fichotheque.SubsetKey;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.corpus.FicheMeta;
import net.fichotheque.corpus.Fiches;
import net.fichotheque.selection.FicheSelector;
import net.fichotheque.utils.Comparators;
import net.fichotheque.utils.CorpusUtils;
import net.mapeadores.util.annotation.Nullable;
import net.mapeadores.util.localisation.Lang;


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

    private final Comparator<FicheMeta> comparator;
    private final Map<SubsetKey, EntryBuilder> entryBuilderMap = new LinkedHashMap<SubsetKey, EntryBuilder>();
    private final Set<FicheMeta> ficheMetaSet = new HashSet<FicheMeta>();

    public FichesBuilder(@Nullable Comparator<FicheMeta> comparator) {
        this.comparator = comparator;
    }

    public FichesBuilder initSubsetKeyOrder(List<SubsetKey> subsetKeyList) {
        for (SubsetKey subsetKey : subsetKeyList) {
            entryBuilderMap.put(subsetKey, null);
        }
        return this;
    }

    public FichesBuilder addAll(Collection<FicheMeta> ficheMetaCollection) {
        for (FicheMeta ficheMeta : ficheMetaCollection) {
            add(ficheMeta);
        }
        return this;
    }

    public FichesBuilder add(FicheMeta ficheMeta) {
        if (ficheMetaSet.contains(ficheMeta)) {
            return this;
        }
        Corpus corpus = ficheMeta.getCorpus();
        EntryBuilder entryBuilder = (EntryBuilder) entryBuilderMap.get(corpus.getSubsetKey());
        if (entryBuilder == null) {
            entryBuilder = new EntryBuilder(corpus);
            entryBuilderMap.put(corpus.getSubsetKey(), entryBuilder);
        }
        entryBuilder.addFicheMeta(ficheMeta);
        ficheMetaSet.add(ficheMeta);
        return this;
    }

    public FichesBuilder populate(FicheSelector ficheSelector) {
        for (Corpus corpus : ficheSelector.getCorpusList()) {
            for (FicheMeta ficheMeta : corpus.getFicheMetaList()) {
                if (ficheSelector.test(ficheMeta)) {
                    add(ficheMeta);
                }
            }
        }
        return this;
    }

    public Fiches toFiches() {
        List<Fiches.Entry> entryList = new ArrayList<Fiches.Entry>();
        for (EntryBuilder entryBuilder : entryBuilderMap.values()) {
            if (entryBuilder != null) {
                if (comparator != null) {
                    entryBuilder.sort(comparator);
                }
                entryList.add(entryBuilder.toEntry());
            }
        }
        return new InternalFiches(CorpusUtils.wrap(entryList.toArray(new Fiches.Entry[entryList.size()])), new HashSet(ficheMetaSet));
    }

    public static FichesBuilder build(String sortType) {
        if (Comparators.needCollator(sortType)) {
            throw new IllegalArgumentException("sortType need collator");
        }
        return build(sortType, null);
    }

    public static FichesBuilder build(String sortType, Lang sortLang) {
        Comparator comparator = Comparators.getComparator(sortType, sortLang);
        return new FichesBuilder(comparator);
    }

    public static FichesBuilder init() {
        return new FichesBuilder(null);
    }

    public static FichesBuilder init(@Nullable Comparator<FicheMeta> comparator) {
        return new FichesBuilder(comparator);
    }


    private static class EntryBuilder {

        private final Corpus corpus;
        private FicheMeta[] ficheMetaArray = new FicheMeta[32];
        private int size = 0;

        private EntryBuilder(Corpus corpus) {
            this.corpus = corpus;
        }

        private void addFicheMeta(FicheMeta ficheMeta) {
            if (size == ficheMetaArray.length) {
                FicheMeta[] nv = new FicheMeta[size * 2];
                System.arraycopy(ficheMetaArray, 0, nv, 0, size);
                ficheMetaArray = nv;
            }
            ficheMetaArray[size] = ficheMeta;
            size++;
        }

        private void sort(Comparator<FicheMeta> ficheMetaComparator) {
            Arrays.sort(ficheMetaArray, 0, size, ficheMetaComparator);
        }

        private Fiches.Entry toEntry() {
            FicheMeta[] result = new FicheMeta[size];
            System.arraycopy(ficheMetaArray, 0, result, 0, size);
            return CorpusUtils.toFichesEntry(corpus, result);
        }

    }


    private static class InternalFiches implements Fiches {

        private final Set<FicheMeta> ficheMetaSet;
        private final List<Fiches.Entry> entryList;

        private InternalFiches(List<Fiches.Entry> entryList, Set<FicheMeta> ficheMetaSet) {
            this.entryList = entryList;
            this.ficheMetaSet = ficheMetaSet;
        }

        @Override
        public List<Fiches.Entry> getEntryList() {
            return entryList;
        }

        /**
         * Implémentation de l'interface SubsetItemEgibility. Une fiche est
         * éligible si elle appartient à la liste.
         */
        @Override
        public boolean test(FicheMeta ficheMeta) {
            return ficheMetaSet.contains(ficheMeta);
        }

        @Override
        public int getFicheCount() {
            return ficheMetaSet.size();
        }

    }


}
