/* FichothequeLib_API - Copyright (c) 2006-2024 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;

import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.RandomAccess;
import java.util.Set;
import net.fichotheque.Fichotheque;
import net.fichotheque.Subset;
import net.fichotheque.SubsetItem;
import net.fichotheque.SubsetKey;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.corpus.FicheChange;
import net.fichotheque.corpus.FicheMeta;
import net.fichotheque.corpus.Fiches;
import net.fichotheque.corpus.fiche.Fiche;
import net.fichotheque.corpus.fiche.FicheAPI;
import net.fichotheque.corpus.fiche.FicheItem;
import net.fichotheque.corpus.fiche.FicheItems;
import net.fichotheque.corpus.fiche.Info;
import net.fichotheque.corpus.fiche.ParaItem;
import net.fichotheque.corpus.fiche.Prop;
import net.fichotheque.corpus.fiche.Section;
import net.fichotheque.corpus.metadata.CorpusField;
import net.fichotheque.corpus.metadata.CorpusMetadata;
import net.fichotheque.corpus.metadata.FieldKey;
import net.mapeadores.util.exceptions.SwitchException;
import net.mapeadores.util.localisation.Lang;


/**
 *
 * @author Vincent Calame
 */
public final class CorpusUtils {

    public final static List<FicheMeta> EMPTY_FICHEMETALIST = Collections.emptyList();
    public final static Fiches EMPTY_FICHES = new EmptyFiches();
    public final static List<Fiches.Entry> EMPTY_FICHESENTRYLIST = Collections.emptyList();
    public final static FicheChange EMPTY_FICHECHANGE = new EmptyFicheChange();

    private CorpusUtils() {
    }

    /**
     * Retourne le nombre total de fiches de la base.
     */
    public static int getFicheCount(Fichotheque fichotheque) {
        int count = 0;
        for (Corpus corpus : fichotheque.getCorpusList()) {
            count = count + corpus.size();
        }
        return count;
    }

    public static boolean replaceInField(Fiche fiche, CorpusUtils.FieldSelection fieldSelection, FicheItem oldFicheItem, FicheItem newFicheItem) {
        boolean done = false;
        if (fieldSelection.withOwners()) {
            FicheItems owners = fiche.getOwners();
            if (owners != null) {
                FicheItems newItems = replaceInFicheItems(owners, oldFicheItem, newFicheItem);
                if (newItems != null) {
                    fiche.setOwners(newItems);
                    done = true;
                }
            }
        }
        if (fieldSelection.withInfo()) {
            for (Info info : fiche.getInfoList()) {
                if (fieldSelection.containsInfo(info.getFieldKey())) {
                    FicheItems newItems = replaceInFicheItems(info, oldFicheItem, newFicheItem);
                    if (newItems != null) {
                        fiche.setInfo(info.getFieldKey(), newItems);
                        done = true;
                    }
                }
            }
        }
        if (fieldSelection.withProp()) {
            for (Prop prop : fiche.getPropList()) {
                FieldKey fieldKey = prop.getFieldKey();
                if (fieldSelection.containsProp(prop.getFieldKey())) {
                    FicheItem current = prop.getFicheItem();
                    if (current.equals(oldFicheItem)) {
                        fiche.setProp(fieldKey, newFicheItem);
                        done = true;
                    }
                }
            }
        }
        return done;
    }

    /**
     * Return null si aucun remplacement n'est fait.
     */
    private static FicheItems replaceInFicheItems(FicheItems currentItems, FicheItem oldFicheItem, FicheItem newFicheItem) {
        List<FicheItem> newList = new ArrayList<FicheItem>();
        int count = currentItems.size();
        boolean done = false;
        for (int i = 0; i < count; i++) {
            FicheItem ficheItem = currentItems.get(i);
            if (ficheItem.equals(oldFicheItem)) {
                newList.add(newFicheItem);
                done = true;
            } else {
                newList.add(ficheItem);
            }
        }
        if (done) {
            return FicheUtils.toFicheItems(newList);
        } else {
            return null;
        }
    }

    public static FieldSelection getPersonneFieldSelection(Corpus corpus) {
        FieldSelection fieldSelection = new FieldSelection();
        fieldSelection.addField(FieldKey.OWNERS);
        CorpusMetadata corpusMetadata = corpus.getCorpusMetadata();
        for (CorpusField corpusField : corpusMetadata.getPropList()) {
            if (corpusField.isType(CorpusField.PERSON_FIELD)) {
                fieldSelection.addField(corpusField);
            }
        }
        for (CorpusField corpusField : corpusMetadata.getInfoList()) {
            if (corpusField.isType(CorpusField.PERSON_FIELD)) {
                fieldSelection.addField(corpusField);
            }
        }
        return fieldSelection;
    }

    public static List<FicheMeta> getFicheMetaListByCorpus(Fiches fiches, Corpus corpus) {
        SubsetKey corpusKey = corpus.getSubsetKey();
        for (Fiches.Entry entry : fiches.getEntryList()) {
            Corpus other = entry.getCorpus();
            if (other.getSubsetKey().equals(corpusKey)) {
                return entry.getFicheMetaList();
            }
        }
        return EMPTY_FICHEMETALIST;
    }

    public static List<SubsetItem> getFicheMetaListByCorpus(Fiches fiches, SubsetKey corpusKey) {
        for (Fiches.Entry entry : fiches.getEntryList()) {
            Corpus other = entry.getCorpus();
            if (other.getSubsetKey().equals(corpusKey)) {
                return entry.getSubsetItemList();
            }
        }
        return FichothequeUtils.EMPTY_SUBSETITEMLIST;
    }

    public static List<FicheMeta> toList(Fiches fiches) {
        List<FicheMeta> list = new ArrayList<FicheMeta>();
        for (Fiches.Entry entry : fiches.getEntryList()) {
            for (FicheMeta ficheMeta : entry.getFicheMetaList()) {
                list.add(ficheMeta);
            }
        }
        return list;
    }

    public static List<FicheMeta> getSatelliteFicheMetaList(SubsetItem masterSubsetItem) {
        List<FicheMeta> list = new ArrayList<FicheMeta>();
        Subset masterSubset = masterSubsetItem.getSubset();
        int id = masterSubsetItem.getId();
        for (Corpus satellite : masterSubset.getSatelliteCorpusList()) {
            FicheMeta ficheMeta = (FicheMeta) satellite.getFicheMetaById(id);
            if (ficheMeta != null) {
                list.add(ficheMeta);
            }
        }
        return list;
    }

    public static List<FicheMeta> getOtherSatelliteFicheMetaList(FicheMeta satelliteFicheMeta) {
        Corpus currentCorpus = satelliteFicheMeta.getCorpus();
        Subset masterSubset = currentCorpus.getMasterSubset();
        List<Corpus> corpusList = masterSubset.getSatelliteCorpusList();
        if (corpusList.size() == 1) {
            return EMPTY_FICHEMETALIST;
        }
        int ficheid = satelliteFicheMeta.getId();
        List<FicheMeta> list = new ArrayList<FicheMeta>();
        for (Corpus corpus : corpusList) {
            if (!corpus.equals(currentCorpus)) {
                FicheMeta ficheMeta = (FicheMeta) corpus.getFicheMetaById(ficheid);
                if (ficheMeta != null) {
                    list.add(ficheMeta);
                }
            }
        }
        return list;
    }

    public static boolean hasChanges(FicheChange ficheChange) {
        return ((ficheChange.getFicheAPI() != null) || (!ficheChange.getRemovedList().isEmpty()));
    }

    public static Fiche toFiche(FicheChange ficheChange) {
        Fiche fiche = new Fiche();
        updateFiche(fiche, ficheChange);
        return fiche;
    }

    public static void updateFiche(Fiche fiche, FicheChange ficheChange) {
        for (FieldKey fieldKey : ficheChange.getRemovedList()) {
            switch (fieldKey.getCategory()) {
                case FieldKey.PROP_CATEGORY:
                    fiche.setProp(fieldKey, null);
                    break;
                case FieldKey.INFO_CATEGORY:
                    fiche.setInfo(fieldKey, null);
                    break;
                case FieldKey.SECTION_CATEGORY:
                    fiche.setSection(fieldKey, null);
                    break;
                case FieldKey.SPECIAL_CATEGORY:
                    switch (fieldKey.getKeyString()) {
                        case FieldKey.SPECIAL_TITLE:
                            fiche.setTitle("");
                            break;
                        case FieldKey.SPECIAL_SUBTITLE:
                            fiche.setSubtitle(null);
                            break;
                        case FieldKey.SPECIAL_LANG:
                            fiche.setLang(null);
                            break;
                        case FieldKey.SPECIAL_OWNERS:
                            fiche.setOwners(null);
                            break;
                        case FieldKey.SPECIAL_ID:
                            break;
                        default:
                            throw new SwitchException("Unknown special field = " + fieldKey.getKeyString());
                    }
                    break;
                default:
                    throw new SwitchException("Unknown category = " + fieldKey.getCategory());
            }
        }
        FicheAPI ficheAPI = ficheChange.getFicheAPI();
        if (ficheAPI != null) {
            String title = ficheAPI.getTitle();
            if (title.length() > 0) {
                fiche.setTitle(title);
            }
            ParaItem subtitle = ficheAPI.getSubtitle();
            if (subtitle != null) {
                fiche.setSubtitle(subtitle);
            }
            Lang lang = ficheAPI.getLang();
            if (lang != null) {
                fiche.setLang(lang);
            }
            FicheItems owners = ficheAPI.getOwners();
            if (owners != null) {
                fiche.setOwners(owners);
            }
            for (Prop prop : ficheAPI.getPropList()) {
                fiche.setProp(prop.getFieldKey(), prop.getFicheItem());
            }
            for (Info info : ficheAPI.getInfoList()) {
                fiche.setInfo(info.getFieldKey(), info);
            }
            for (Section section : ficheAPI.getSectionList()) {
                fiche.setSection(section.getFieldKey(), section);
            }
        }
    }

    public static void appendFiche(Fiche fiche, FicheChange ficheChange) {
        FicheAPI ficheAPI = ficheChange.getFicheAPI();
        if (ficheAPI != null) {
            String title = ficheAPI.getTitle();
            if (title.length() > 0) {
                fiche.setTitle(title);
            }
            ParaItem subtitle = ficheAPI.getSubtitle();
            if (subtitle != null) {
                fiche.setSubtitle(subtitle);
            }
            Lang lang = ficheAPI.getLang();
            if (lang != null) {
                fiche.setLang(lang);
            }
            FicheItems redacteurs = ficheAPI.getOwners();
            if (redacteurs != null) {
                fiche.appendOwners(redacteurs);
            }
            for (Prop prop : ficheAPI.getPropList()) {
                fiche.setProp(prop.getFieldKey(), prop.getFicheItem());
            }
            for (Info info : ficheAPI.getInfoList()) {
                fiche.appendInfo(info.getFieldKey(), info);
            }
            for (Section section : ficheAPI.getSectionList()) {
                fiche.appendSection(section.getFieldKey(), section);
            }
        }
    }

    public static List<FicheMeta> wrap(FicheMeta[] array) {
        return new FicheMetaList(array);
    }

    public static List<Fiches.Entry> wrap(Fiches.Entry[] array) {
        return new EntryList(array);
    }

    public static Fiches toSingletonFiches(FicheMeta ficheMeta) {
        if (ficheMeta == null) {
            throw new IllegalArgumentException("ficheMeta is null");
        }
        return new SingletonFiches(ficheMeta);
    }

    public static Fiches reduce(Fiches fiches, Corpus corpus) {
        for (Fiches.Entry entry : fiches.getEntryList()) {
            if (entry.getCorpus().equals(corpus)) {
                return new CorpusFiches(entry);
            }
        }
        return EMPTY_FICHES;
    }

    public static Fiches reduce(Fiches fiches, int limit) {
        Set<FicheMeta> ficheMetaSet = new HashSet<FicheMeta>();
        List<Fiches.Entry> resultList = new ArrayList<Fiches.Entry>();
        for (Fiches.Entry entry : fiches.getEntryList()) {
            List<FicheMeta> ficheMetaList = entry.getFicheMetaList();
            int size = ficheMetaList.size();
            if (size <= limit) {
                for (FicheMeta ficheMeta : ficheMetaList) {
                    ficheMetaSet.add(ficheMeta);
                }
                resultList.add(entry);
            } else {
                int count = 0;
                List<FicheMeta> filteredList = new ArrayList<FicheMeta>();
                for (FicheMeta ficheMeta : ficheMetaList) {
                    filteredList.add(ficheMeta);
                    ficheMetaSet.add(ficheMeta);
                    count++;
                    if (count == limit) {
                        break;
                    }
                }
                resultList.add(CorpusUtils.toFichesEntry(entry.getCorpus(), filteredList));
            }
        }
        return new InternalFiches(resultList, ficheMetaSet);
    }

    public static Fiches.Entry toFichesEntry(Corpus corpus, FicheMeta[] ficheMetaArray) {
        return new InternalEntry(corpus, ficheMetaArray);
    }

    public static Fiches.Entry toFichesEntry(Corpus corpus, Collection<FicheMeta> ficheMetas) {
        return new InternalEntry(corpus, ficheMetas.toArray(new FicheMeta[ficheMetas.size()]));
    }


    public static class FieldSelection {

        private final Set<FieldKey> propSet = new HashSet<FieldKey>();
        private final Set<FieldKey> infoSet = new HashSet<FieldKey>();
        private final Set<String> sectionSet = new HashSet<String>();
        private boolean withOwners = false;

        private FieldSelection() {

        }

        private void addField(CorpusField corpusField) {
            addField(corpusField.getFieldKey());
        }

        private void addField(FieldKey fieldKey) {
            switch (fieldKey.getCategory()) {
                case FieldKey.PROP_CATEGORY:
                    propSet.add(fieldKey);
                    break;
                case FieldKey.INFO_CATEGORY:
                    infoSet.add(fieldKey);
                    break;
                case FieldKey.SECTION_CATEGORY:
                    sectionSet.add(fieldKey.getFieldName());
                    break;
                case FieldKey.SPECIAL_CATEGORY:
                    switch (fieldKey.getKeyString()) {
                        case FieldKey.SPECIAL_OWNERS:
                            withOwners = true;
                            break;

                    }
                    break;
            }
        }

        public boolean withProp() {
            return !propSet.isEmpty();
        }

        public boolean withInfo() {
            return !infoSet.isEmpty();
        }

        public boolean withSection() {
            return !sectionSet.isEmpty();
        }

        public boolean withOwners() {
            return withOwners;
        }

        public boolean containsProp(FieldKey fieldKey) {
            return propSet.contains(fieldKey);
        }

        public boolean containsInfo(FieldKey fieldKey) {
            return infoSet.contains(fieldKey);
        }

        public boolean containsSection(String type) {
            return sectionSet.contains(type);
        }

    }


    private static class FicheMetaList extends AbstractList<FicheMeta> implements RandomAccess {

        private final FicheMeta[] array;

        private FicheMetaList(FicheMeta[] array) {
            this.array = array;
        }

        @Override
        public int size() {
            return array.length;
        }

        @Override
        public FicheMeta get(int index) {
            return array[index];
        }

    }


    private static class SingletonFiches implements Fiches {

        private final FicheMeta ficheMeta;
        private final List<Fiches.Entry> entryList;

        private SingletonFiches(FicheMeta ficheMeta) {
            this.ficheMeta = ficheMeta;
            FicheMeta[] ficheMetaArray = {ficheMeta};
            Fiches.Entry[] array = new Fiches.Entry[1];
            array[0] = toFichesEntry(ficheMeta.getCorpus(), ficheMetaArray);
            entryList = wrap(array);
        }

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

        @Override
        public boolean test(FicheMeta otherFicheMeta) {
            return (otherFicheMeta.equals(ficheMeta));
        }

        @Override
        public int getFicheCount() {
            return 1;
        }

    }


    private static class EmptyFiches implements Fiches {

        private EmptyFiches() {

        }

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

        @Override
        public boolean test(FicheMeta ficheMeta) {
            return false;
        }

        @Override
        public int getFicheCount() {
            return 0;
        }

    }


    private static class InternalEntry implements Fiches.Entry {

        private final Corpus corpus;
        private final List<FicheMeta> ficheMetaList;
        private final List<SubsetItem> subsetItemList;

        private InternalEntry(Corpus corpus, FicheMeta[] ficheMetaArray) {
            this.corpus = corpus;
            this.ficheMetaList = CorpusUtils.wrap(ficheMetaArray);
            this.subsetItemList = FichothequeUtils.wrap(ficheMetaArray);
        }

        @Override
        public Corpus getCorpus() {
            return corpus;
        }

        @Override
        public List<FicheMeta> getFicheMetaList() {
            return ficheMetaList;
        }

        @Override
        public List<SubsetItem> getSubsetItemList() {
            return subsetItemList;
        }

    }


    private static class EntryList extends AbstractList<Fiches.Entry> implements RandomAccess {

        private final Fiches.Entry[] array;

        private EntryList(Fiches.Entry[] array) {
            this.array = array;
        }

        @Override
        public int size() {
            return array.length;
        }

        @Override
        public Fiches.Entry get(int index) {
            return array[index];
        }

    }


    private static class CorpusFiches implements Fiches {

        private final Fiches.Entry entry;
        private final List<Fiches.Entry> entryList;

        private CorpusFiches(Fiches.Entry entry) {
            this.entry = entry;
            this.entryList = Collections.singletonList(entry);
        }

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

        @Override
        public boolean test(FicheMeta ficheMeta) {
            return entry.getSubsetItemList().contains(ficheMeta);
        }

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

    }


    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 Predicate<FicheMeta>. 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();
        }

    }


    private static class EmptyFicheChange implements FicheChange {

        private EmptyFicheChange() {

        }

        @Override
        public FicheAPI getFicheAPI() {
            return null;
        }

        @Override
        public List<FieldKey> getRemovedList() {
            return CorpusMetadataUtils.EMPTY_FIELDKEYLIST;
        }

    }

}
