/* FichothequeLib_API - Copyright (c) 2006-2023 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.text.ParseException;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.RandomAccess;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.Predicate;
import net.fichotheque.Fichotheque;
import net.fichotheque.FichothequeConstants;
import net.fichotheque.Subset;
import net.fichotheque.SubsetItem;
import net.fichotheque.SubsetKey;
import net.fichotheque.addenda.Addenda;
import net.fichotheque.album.Album;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.corpus.FicheMeta;
import net.fichotheque.corpus.fiche.DateItem;
import net.fichotheque.corpus.metadata.FieldKey;
import net.fichotheque.eligibility.SubsetEligibility;
import net.fichotheque.exportation.scrutari.CorpusScrutariDef;
import net.fichotheque.exportation.scrutari.ThesaurusScrutariDef;
import net.fichotheque.include.IncludeKey;
import net.fichotheque.namespaces.PhraseSpace;
import net.fichotheque.sphere.Sphere;
import net.fichotheque.thesaurus.Thesaurus;
import net.mapeadores.util.annotation.Nullable;
import net.mapeadores.util.attr.Attribute;
import net.mapeadores.util.attr.AttributeKey;
import net.mapeadores.util.attr.CheckedNameSpace;
import net.mapeadores.util.localisation.Lang;
import net.mapeadores.util.primitives.FuzzyDate;
import net.mapeadores.util.text.Label;
import net.mapeadores.util.text.Phrase;
import net.mapeadores.util.text.Phrases;
import net.mapeadores.util.text.StringUtils;


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

    public final static List<SubsetItem> EMPTY_SUBSETITEMLIST = Collections.emptyList();
    public final static List<Corpus> EMPTY_CORPUSLIST = Collections.emptyList();
    public final static List<Thesaurus> EMPTY_THESAURUSLIST = Collections.emptyList();
    public final static List<SubsetKey> EMPTY_SUBSETKEYLIST = Collections.emptyList();
    public final static List<IncludeKey> EMPTY_INCLUDEKEYLIST = Collections.emptyList();
    public final static Set<SubsetKey> EMPTY_SUBSETKEYSET = Collections.emptySet();

    private FichothequeUtils() {
    }

    public static String toGlobalId(SubsetKey subsetKey, int id) {
        return subsetKey.getSubsetName() + "/" + id;
    }

    public static SubsetItem parseGlobalId(String globalId, Fichotheque fichotheque, short subsetCategory, @Nullable Subset defaultSubset) throws ParseException {
        int idx = globalId.indexOf('/');
        if (idx == 0) {
            if (globalId.length() == 1) {
                throw new ParseException(globalId, 0);
            }
            idx = -1;
            globalId = globalId.substring(1);
        }
        Subset subset;
        int id;
        if (idx == -1) {
            if (defaultSubset == null) {
                throw new ParseException(globalId, 0);
            }
            subset = defaultSubset;
            try {
                id = Integer.parseInt(globalId);
            } catch (NumberFormatException nfe) {
                throw new ParseException(globalId, 0);
            }
        } else {
            if (idx == globalId.length() - 1) {
                throw new ParseException(globalId, 0);
            }
            String subsetName = globalId.substring(0, idx);
            SubsetKey subsetKey = SubsetKey.parse(subsetCategory, subsetName);
            subset = fichotheque.getSubset(subsetKey);
            if (subset == null) {
                throw new ParseException(globalId, 0);
            }
            try {
                id = Integer.parseInt(globalId.substring(idx + 1));

            } catch (NumberFormatException nfe) {
                throw new ParseException(globalId, idx);
            }
        }
        if (id < 0) {
            throw new ParseException(globalId, 0);
        }
        SubsetItem subsetItem = subset.getSubsetItemById(id);
        if (subsetItem == null) {
            throw new ParseException(globalId, 0);
        }
        return subsetItem;
    }

    /**
     * Correspond à [_a-zA-Z][-_.a-zA-Z0-9]*
     */
    public static boolean isValidPhraseName(String name) {
        try {
            CheckedNameSpace.parse(name);
            return true;
        } catch (ParseException pe) {
            return false;
        }
    }

    public static void checkPhraseName(String name) throws ParseException {
        CheckedNameSpace.parse(name);
    }

    public static DateItem getDateItem(FicheMeta ficheMeta, String name) {
        FuzzyDate date = null;
        switch (name) {
            case FichothequeConstants.DATECREATION_NAME:
                date = ficheMeta.getCreationDate();
                break;
            case FichothequeConstants.DATEMODIFICATION_NAME:
                date = ficheMeta.getModificationDate();
                break;
        }
        if (date == null) {
            return null;
        }
        return new DateItem(date);
    }

    @Nullable
    public static Label getTitleLabel(Subset subset, Lang lang) {
        return subset.getMetadata().getTitleLabels().getLangPartCheckedLabel(lang);
    }

    public static String getTitle(Subset subset, Lang lang) {
        Label label = getTitleLabel(subset, lang);
        if (label != null) {
            return label.getLabelString();
        } else {
            return "[" + subset.getSubsetName() + "]";
        }
    }

    public static String getTitleWithKey(Subset subset, Lang lang) {
        Label label = getTitleLabel(subset, lang);
        if (label != null) {
            return label.getLabelString() + " [" + subset.getSubsetName() + "]";
        } else {
            return "[" + subset.getSubsetName() + "]";
        }
    }

    public static String getTitle(Fichotheque fichotheque, SubsetKey subsetKey, Lang lang, boolean withKey) {
        Subset subset = fichotheque.getSubset(subsetKey);
        if (subset != null) {
            if (withKey) {
                return getTitleWithKey(subset, lang);
            } else {
                return getTitle(subset, lang);
            }
        } else {
            return "[" + subsetKey.getSubsetName() + "]";
        }
    }

    public static String getPhraseLabel(Phrases phrases, String name, Lang lang) {
        Phrase phrase = phrases.getPhrase(name);
        if (phrase != null) {
            Label label = phrase.getLangPartCheckedLabel(lang);
            if (label != null) {
                return label.getLabelString();
            }
        }
        return null;
    }

    public static Thesaurus[] toThesaurusArray(Fichotheque fichotheque, Predicate<Subset> subsetAccessPredicate) {
        List<Thesaurus> thesaurusList = new ArrayList<Thesaurus>();
        for (Thesaurus thesaurus : fichotheque.getThesaurusList()) {
            if (subsetAccessPredicate.test(thesaurus)) {
                thesaurusList.add(thesaurus);
            }
        }
        return thesaurusList.toArray(new Thesaurus[thesaurusList.size()]);
    }

    public static Corpus[] toCorpusArray(Fichotheque fichotheque) {
        List<Corpus> corpusList = fichotheque.getCorpusList();
        return corpusList.toArray(new Corpus[corpusList.size()]);
    }

    public static Corpus[] toCorpusArray(Fichotheque fichotheque, Predicate<Subset> subsetAccessPredicate) {
        List<Corpus> corpusList = new ArrayList<Corpus>();
        for (Corpus corpus : fichotheque.getCorpusList()) {
            if (subsetAccessPredicate.test(corpus)) {
                corpusList.add(corpus);
            }
        }
        return corpusList.toArray(new Corpus[corpusList.size()]);
    }

    public static Addenda[] toAddendaArray(Fichotheque fichotheque) {
        List<Addenda> addendaList = fichotheque.getAddendaList();
        return addendaList.toArray(new Addenda[addendaList.size()]);
    }

    public static Addenda[] toAddendaArray(Fichotheque fichotheque, Predicate<Subset> subsetAccessPredicate) {
        List<Addenda> addendaList = new ArrayList<Addenda>();
        for (Addenda addenda : fichotheque.getAddendaList()) {
            if (subsetAccessPredicate.test(addenda)) {
                addendaList.add(addenda);
            }
        }
        return addendaList.toArray(new Addenda[addendaList.size()]);
    }

    public static Album[] toAlbumArray(Fichotheque fichotheque) {
        List<Album> albumList = fichotheque.getAlbumList();
        return albumList.toArray(new Album[albumList.size()]);
    }

    public static Album[] toAlbumArray(Fichotheque fichotheque, Predicate<Subset> subsetAccessPredicate) {
        List<Album> albumList = new ArrayList<Album>();
        for (Album album : fichotheque.getAlbumList()) {
            if (subsetAccessPredicate.test(album)) {
                albumList.add(album);
            }
        }
        return albumList.toArray(new Album[albumList.size()]);
    }

    public static Sphere[] toSphereArray(Fichotheque fichotheque) {
        List<Sphere> sphereList = fichotheque.getSphereList();
        return sphereList.toArray(new Sphere[sphereList.size()]);
    }

    public static Sphere[] toSphereArray(Fichotheque fichotheque, Predicate<Subset> subsetAccessPredicate) {
        List<Sphere> sphereList = new ArrayList<Sphere>();
        for (Sphere sphere : fichotheque.getSphereList()) {
            if (subsetAccessPredicate.test(sphere)) {
                sphereList.add(sphere);
            }
        }
        return sphereList.toArray(new Sphere[sphereList.size()]);
    }

    public static Subset[] toSubsetArray(Fichotheque fichotheque) {
        List<Subset> list = new ArrayList<Subset>();
        list.addAll(fichotheque.getCorpusList());
        list.addAll(fichotheque.getThesaurusList());
        list.addAll(fichotheque.getSphereList());
        list.addAll(fichotheque.getAddendaList());
        list.addAll(fichotheque.getAlbumList());
        return list.toArray(new Subset[list.size()]);
    }

    public static List<Subset> getSubsetList(Fichotheque fichotheque, short subsetCategory) {
        List<Subset> list = new ArrayList<Subset>();
        switch (subsetCategory) {
            case SubsetKey.CATEGORY_CORPUS:
                list.addAll(fichotheque.getCorpusList());
                break;
            case SubsetKey.CATEGORY_THESAURUS:
                list.addAll(fichotheque.getThesaurusList());
                break;
            case SubsetKey.CATEGORY_SPHERE:
                list.addAll(fichotheque.getSphereList());
                break;
            case SubsetKey.CATEGORY_ADDENDA:
                list.addAll(fichotheque.getAddendaList());
                break;
            case SubsetKey.CATEGORY_ALBUM:
                list.addAll(fichotheque.getAlbumList());
                break;
        }
        return list;
    }

    public static Subset[] toSubsetArray(Fichotheque fichotheque, short subsetCategory) {
        List<Subset> list = getSubsetList(fichotheque, subsetCategory);
        return list.toArray(new Subset[list.size()]);
    }

    public static Subset[] getSortedSubsetArray(Fichotheque fichotheque, SubsetEligibility subsetEligibility) {
        if (subsetEligibility == null) {
            subsetEligibility = EligibilityUtils.ALL_SUBSET_ELIGIBILITY;
        }
        SortedMap<SubsetKey, Subset> sortedMap = new TreeMap<SubsetKey, Subset>();
        for (Corpus corpus : fichotheque.getCorpusList()) {
            if (subsetEligibility.accept(corpus)) {
                Subset masterSubset = ((Corpus) corpus).getMasterSubset();
                if ((masterSubset == null) || (subsetEligibility.accept(masterSubset))) {
                    sortedMap.put(corpus.getSubsetKey(), corpus);
                }
            }
        }
        for (Thesaurus thesaurus : fichotheque.getThesaurusList()) {
            if (subsetEligibility.accept(thesaurus)) {
                sortedMap.put(thesaurus.getSubsetKey(), thesaurus);
            }
        }
        for (Sphere sphere : fichotheque.getSphereList()) {
            if (subsetEligibility.accept(sphere)) {
                sortedMap.put(sphere.getSubsetKey(), sphere);
            }
        }
        for (Addenda addenda : fichotheque.getAddendaList()) {
            if (subsetEligibility.accept(addenda)) {
                sortedMap.put(addenda.getSubsetKey(), addenda);
            }
        }
        for (Album album : fichotheque.getAlbumList()) {
            if (subsetEligibility.accept(album)) {
                sortedMap.put(album.getSubsetKey(), album);
            }
        }
        return sortedMap.values().toArray(new Subset[sortedMap.size()]);
    }

    public static Corpus getCorpus(Fichotheque fichotheque, String corpusName) {
        try {
            SubsetKey subsetKey = SubsetKey.parse(SubsetKey.CATEGORY_CORPUS, corpusName);
            return (Corpus) fichotheque.getSubset(subsetKey);
        } catch (ParseException pe) {
            return null;
        }
    }

    public static Thesaurus getThesaurus(Fichotheque fichotheque, String thesaurusName) {
        try {
            SubsetKey subsetKey = SubsetKey.parse(SubsetKey.CATEGORY_THESAURUS, thesaurusName);
            return (Thesaurus) fichotheque.getSubset(subsetKey);
        } catch (ParseException pe) {
            return null;
        }
    }

    public static Addenda getAddenda(Fichotheque fichotheque, String addendaName) {
        try {
            SubsetKey subsetKey = SubsetKey.parse(SubsetKey.CATEGORY_ADDENDA, addendaName);
            return (Addenda) fichotheque.getSubset(subsetKey);
        } catch (ParseException pe) {
            return null;
        }
    }

    public static Album getAlbum(Fichotheque fichotheque, String thesaurusName) {
        try {
            SubsetKey subsetKey = SubsetKey.parse(SubsetKey.CATEGORY_ALBUM, thesaurusName);
            return (Album) fichotheque.getSubset(subsetKey);
        } catch (ParseException pe) {
            return null;
        }
    }

    public static Sphere getSphere(Fichotheque fichotheque, String sphereName) {
        try {
            SubsetKey subsetKey = SubsetKey.parse(SubsetKey.CATEGORY_SPHERE, sphereName);
            return (Sphere) fichotheque.getSubset(subsetKey);
        } catch (ParseException pe) {
            return null;
        }
    }

    public static Set<SubsetKey> toSubsetKeySet(short category, String s) {
        String[] tokens = StringUtils.getTechnicalTokens(s, true);
        if (tokens.length == 0) {
            return EMPTY_SUBSETKEYSET;
        }
        Set<SubsetKey> result = new LinkedHashSet<SubsetKey>();
        for (String token : tokens) {
            try {
                SubsetKey subsetKey = SubsetKey.parse(category, token);
                result.add(subsetKey);
            } catch (ParseException pe) {
            }
        }
        return result;
    }

    public static Collection<SubsetItem> filterAndSort(Subset subset, Predicate<SubsetItem> predicate) {
        SortedMap<Integer, SubsetItem> sortedMap = new TreeMap<Integer, SubsetItem>();
        for (SubsetItem subsetItem : subset.getSubsetItemList()) {
            if (predicate.test(subsetItem)) {
                sortedMap.put(subsetItem.getId(), subsetItem);
            }
        }
        return sortedMap.values();
    }

    public static boolean ownToSameParentage(Subset subset1, Subset subset2) {
        if (subset1 instanceof Corpus) {
            return isParentageCorpus((Corpus) subset1, subset2);
        } else if (subset2 instanceof Corpus) {
            return isParentageCorpus((Corpus) subset2, subset1);
        } else {
            return false;
        }
    }

    public static boolean isParentageCorpus(Corpus corpus, Subset subset) {
        Subset masterSubset = corpus.getMasterSubset();
        if (masterSubset != null) {
            if (masterSubset.equals(subset)) {
                return true;
            } else if (subset instanceof Corpus) {
                Subset otherMaster = ((Corpus) subset).getMasterSubset();
                if ((otherMaster != null) && (otherMaster.equals(masterSubset))) {
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        } else if (subset instanceof Corpus) {
            Subset otherMaster = ((Corpus) subset).getMasterSubset();
            if ((otherMaster != null) && (otherMaster.equals(corpus))) {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }


    public static List<Corpus> getParentageCorpusList(Corpus corpus) {
        Subset masterSubset = corpus.getMasterSubset();
        if (masterSubset == null) {
            return corpus.getSatelliteCorpusList();
        }
        List<Corpus> satelliteList = masterSubset.getSatelliteCorpusList();
        List<Corpus> result = new ArrayList<Corpus>();
        if (masterSubset instanceof Corpus) {
            result.add((Corpus) masterSubset);
        }
        if (satelliteList.size() > 1) {
            for (Corpus other : satelliteList) {
                if (!(other.equals(corpus))) {
                    result.add(other);
                }
            }
        }
        return result;
    }


    public static Object parseKey(String key) {
        try {
            FieldKey fieldKey = FieldKey.parse(key);
            return fieldKey;
        } catch (ParseException pe) {
            try {
                IncludeKey includeKey = IncludeKey.parse(key);
                return includeKey;
            } catch (ParseException pe2) {
                return null;
            }
        }
    }

    public static String getNumberPhrase(SubsetItem subsetItem, String phraseName, Lang workingLang, Locale formatLocale) {
        return FichothequeUtils.getNumberPhrase(subsetItem.getSubset(), subsetItem.getId(), phraseName, workingLang, formatLocale, null);
    }

    public static String getNumberPhrase(SubsetItem subsetItem, String phraseName, Lang workingLang, Locale formatLocale, String defaultString) {
        return FichothequeUtils.getNumberPhrase(subsetItem.getSubset(), subsetItem.getId(), phraseName, workingLang, formatLocale, defaultString);
    }

    public static String getNumberPhrase(Subset subset, int id, String phraseName, Lang workingLang, Locale formatLocale, String defaultString) {
        if (formatLocale == null) {
            formatLocale = workingLang.toLocale();
        }
        Phrase phrase = subset.getMetadata().getPhrases().getPhrase(phraseName);
        if (phrase != null) {
            Label label = phrase.getLabel(workingLang);
            if (label != null) {
                String labelString = label.getLabelString();
                if (appendNumber(subset, phraseName)) {
                    String numberString = StringUtils.toString(id, formatLocale);
                    if ((labelString.length() > 0) && (Character.isLetter(labelString.charAt(labelString.length() - 1)))) {
                        return labelString + " " + numberString;
                    } else {
                        return labelString + numberString;
                    }
                } else {
                    return labelString;
                }
            }
        }
        if (defaultString != null) {
            return defaultString;
        } else {
            return subset.getSubsetName() + "/" + id;
        }
    }

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

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

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

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

    public static List<SubsetKey> toList(Collection<SubsetKey> collection) {
        int size = collection.size();
        if (size == 0) {
            return EMPTY_SUBSETKEYLIST;
        } else {
            return new SubsetKeyList(collection.toArray(new SubsetKey[size]));
        }
    }

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

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

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

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

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

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

    private static boolean appendNumber(Subset subset, String phraseName) {
        AttributeKey attributeKey = PhraseSpace.getNumberAttributeKey(phraseName);
        if (attributeKey == null) {
            return false;
        }
        Attribute attribute = subset.getMetadata().getAttributes().getAttribute(attributeKey);
        if (attribute == null) {
            return true;
        }
        return (!attribute.getFirstValue().equals("ignore"));
    }


    private static class SphereList extends AbstractList<Sphere> implements RandomAccess {

        private final Sphere[] array;

        private SphereList(Sphere[] array) {
            this.array = array;
        }

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

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

    }


    private static class AlbumList extends AbstractList<Album> implements RandomAccess {

        private final Album[] array;

        private AlbumList(Album[] array) {
            this.array = array;
        }

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

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

    }


    private static class AddendaList extends AbstractList<Addenda> implements RandomAccess {

        private final Addenda[] array;

        private AddendaList(Addenda[] array) {
            this.array = array;
        }

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

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

    }


    private static class CorpusList extends AbstractList<Corpus> implements RandomAccess {

        private final Corpus[] array;

        private CorpusList(Corpus[] array) {
            this.array = array;
        }

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

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

    }


    private static class ThesaurusList extends AbstractList<Thesaurus> implements RandomAccess {

        private final Thesaurus[] array;

        private ThesaurusList(Thesaurus[] array) {
            this.array = array;
        }

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

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

    }


    private static class SubsetKeyList extends AbstractList<SubsetKey> implements RandomAccess {

        private final SubsetKey[] array;

        private SubsetKeyList(SubsetKey[] array) {
            this.array = array;
        }

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

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

    }


    private static class SubsetItemList extends AbstractList<SubsetItem> implements RandomAccess {

        private final SubsetItem[] array;

        private SubsetItemList(SubsetItem[] array) {
            this.array = array;
        }

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

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

    }


    private static class CorpusScrutariDefList extends AbstractList<CorpusScrutariDef> implements RandomAccess {

        private final CorpusScrutariDef[] array;

        private CorpusScrutariDefList(CorpusScrutariDef[] array) {
            this.array = array;
        }

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

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

    }


    private static class ThesaurusScrutariDefList extends AbstractList<ThesaurusScrutariDef> implements RandomAccess {

        private final ThesaurusScrutariDef[] array;

        private ThesaurusScrutariDefList(ThesaurusScrutariDef[] array) {
            this.array = array;
        }

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

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

    }


    private static class IncludeKeyList extends AbstractList<IncludeKey> implements RandomAccess {

        private final IncludeKey[] array;

        private IncludeKeyList(IncludeKey[] array) {
            this.array = array;
        }

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

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

    }

}
