/* FichothequeLib_API - Copyright (c) 2006-2025 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.io.IOException;
import java.text.ParseException;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.RandomAccess;
import net.fichotheque.Fichotheque;
import net.fichotheque.SubsetItem;
import net.fichotheque.SubsetKey;
import net.fichotheque.include.IconSources;
import net.fichotheque.thesaurus.Motcle;
import net.fichotheque.thesaurus.Thesaurus;
import net.fichotheque.thesaurus.ThesaurusEditor;
import net.fichotheque.thesaurus.metadata.ThesaurusFieldKey;
import net.fichotheque.thesaurus.metadata.ThesaurusLangChecker;
import net.fichotheque.thesaurus.metadata.ThesaurusMetadata;
import net.fichotheque.thesaurus.policies.DynamicEditPolicy;
import net.mapeadores.util.annotation.Nullable;
import net.mapeadores.util.exceptions.ImplementationException;
import net.mapeadores.util.localisation.Lang;
import net.mapeadores.util.localisation.Langs;
import net.mapeadores.util.text.Idalpha;
import net.mapeadores.util.text.Label;
import net.mapeadores.util.text.LabelChange;


/**
 * Classe d'utilitaires pour les thésaurus. Cette classe ne comprend que des
 * méthodes statiques.
 * <p>
 * Outre leur caractère utilitaire, certaines méthodes font des vérifications de
 * la validité de l'implémentation. Elles renvoient alors une exception
 * ImplementationException
 *
 * @author Vincent Calame
 */
public final class ThesaurusUtils {

    public final static List<Motcle> EMPTY_MOTCLELIST = Collections.emptyList();
    public final static DynamicEditPolicy.None NONE_POLICY = new DynamicEditPolicy.None() {

    };
    public final static DynamicEditPolicy.Allow ALLOW_POLICY = new DynamicEditPolicy.Allow() {

    };

    private ThesaurusUtils() {
    }

    public static List<ThesaurusFieldKey> computeCoreFieldList(Thesaurus thesaurus, ThesaurusLangChecker thesaurusLangChecker) {
        List<ThesaurusFieldKey> coreList = new ArrayList<ThesaurusFieldKey>();
        coreList.add(ThesaurusFieldKey.ID);
        coreList.add(ThesaurusFieldKey.PARENT_ID);
        short type = thesaurus.getThesaurusMetadata().getThesaurusType();
        if (type == ThesaurusMetadata.BABELIEN_TYPE) {
            coreList.add(ThesaurusFieldKey.BABELIENLANG);
            coreList.add(ThesaurusFieldKey.BABELIENLABEL);
        } else {
            if (type == ThesaurusMetadata.IDALPHA_TYPE) {
                coreList.add(ThesaurusFieldKey.IDALPHA);
                coreList.add(ThesaurusFieldKey.PARENT_IDALPHA);
            }
            for (Lang lang : thesaurusLangChecker.getAuthorizedLangs(thesaurus)) {
                coreList.add(ThesaurusFieldKey.toLabelThesaurusFieldKey(lang));
            }
        }
        coreList.add(ThesaurusFieldKey.STATUS);
        return coreList;
    }


    /**
     * Chaine de caractère sous la forme {nom du thésaurus}!{idalpha}. Le
     * caractère ! est utilisé pour se différencier du séparateur / qui concerne
     * un identifiant numérique.
     *
     * @param globalIdalpha
     * @param fichotheque
     * @return
     * @throws ParseException
     */
    public static Motcle parseGlobalIdalpha(String globalIdalpha, Fichotheque fichotheque) throws ParseException {
        int idx = globalIdalpha.indexOf('!');
        if (idx == -1) {
            throw new ParseException(globalIdalpha, 0);
        }
        String subsetName = globalIdalpha.substring(0, idx);
        SubsetKey subsetKey = SubsetKey.parse(SubsetKey.CATEGORY_THESAURUS, subsetName);
        Thesaurus thesaurus = (Thesaurus) fichotheque.getSubset(subsetKey);
        if (thesaurus == null) {
            throw new ParseException(globalIdalpha, 0);
        }
        Motcle motcle = thesaurus.getMotcleByIdalpha(globalIdalpha.substring(idx + 1));
        if (motcle == null) {
            throw new ParseException(globalIdalpha, 0);
        }
        return motcle;
    }

    /**
     * Test si la valeur <code>thesaurusType</code> en argument est une valeur
     * correcte.
     */
    public static boolean testThesaurusType(short thesaurusType) {
        switch (thesaurusType) {
            case ThesaurusMetadata.MULTI_TYPE:
            case ThesaurusMetadata.IDALPHA_TYPE:
            case ThesaurusMetadata.BABELIEN_TYPE:
                return true;
            default:
                return false;
        }
    }

    public static Lang checkDisponibility(ThesaurusLangChecker thesaurusLangChecker, Thesaurus thesaurus, Lang lang) {
        ThesaurusMetadata metadata = thesaurus.getThesaurusMetadata();
        if (metadata.getThesaurusType() == ThesaurusMetadata.BABELIEN_TYPE) {
            return lang;
        }
        Langs langs = thesaurusLangChecker.getAuthorizedLangs(thesaurus);
        if ((langs == null) || (langs.isEmpty())) {
            throw new ImplementationException("thesaurus.getMetadata.getAuthorizedLangIntegerList() is null or empty");
        }
        Lang rootLang = lang.getRootLang();
        boolean withRoot = false;
        for (Lang thesaurusLang : langs) {
            if (thesaurusLang.equals(lang)) {
                return lang;
            }
            if (thesaurusLang.equals(rootLang)) {
                withRoot = true;
            }
        }
        if (withRoot) {
            return lang;
        }
        return langs.get(0);
    }

    public static String getMotcleTitle(Motcle motcle, Lang lang, boolean withId, boolean onlySignificant) {
        StringBuilder buf = new StringBuilder();
        String idalpha = motcle.getIdalpha();
        if (idalpha != null) {
            if (!Idalpha.isSignificant(idalpha)) {
                if (!onlySignificant) {
                    buf.append("(");
                    buf.append(idalpha);
                    buf.append(") ");
                }
            } else if (motcle.getThesaurus().getThesaurusMetadata().isBracketsIdalphaStyle()) {
                buf.append("[");
                buf.append(idalpha);
                buf.append("] ");
            } else {
                buf.append(idalpha);
                buf.append(" \u2013 ");
            }
        }
        buf.append(motcle.getLabelString(lang));
        if ((idalpha == null) && (withId)) {
            buf.append(" (");
            buf.append("id=");
            buf.append(motcle.getId());
            buf.append(')');
        }
        return buf.toString();
    }

    /**
     * Retourne le premier mot-clé du thésaurus ou null si le thésaurus est
     * vide.
     * <p>
     * Équivalent de thesaurus.getFirstLevel().get(0) en faisant la vérification
     * de la présence d'au moins un mot-clé.
     */
    public static Motcle getFirst(Thesaurus thesaurus) {
        List<Motcle> firstLevel = thesaurus.getFirstLevelList();
        if (firstLevel.isEmpty()) {
            return null;
        }
        return firstLevel.get(0);
    }

    /**
     * Retourne le mot-clé suivant le mot-clé en argument dans l'arbre de
     * circulation du thésaurus. La circulation dans un thésaurus se fait
     * suivant le même principe que Xpath du langage XSLT : le mot-clé suivant
     * un mot-clé est son fils aîné, sinon son frère cadet, sinon le frère cadet
     * de son père, etc. La méthode retourne <em>null</em> si le mot-clé est le
     * dernier.
     */
    public static Motcle getNext(Motcle motcle) {
        List<Motcle> childList = motcle.getChildList();
        if (childList.size() > 0) {
            return childList.get(0);
        }
        Motcle sibling = getNextSibling(motcle);
        if (sibling != null) {
            return sibling;
        }
        Motcle parent = motcle.getParent();
        while (parent != null) {
            sibling = getNextSibling(parent);
            if (sibling != null) {
                return sibling;
            }
            parent = parent.getParent();
        }
        return null;
    }

    /**
     * Retourne le mot-clé précédent le mot-clé en argument dans l'arbre de
     * circulation du thésaurus. Voir la méthode <em>getNext()</em>. Retourne
     * <em>null</em> si le mot-clé est le premier.
     */
    public static Motcle getPrevious(Motcle motcle) {
        Motcle previous = getPreviousSibling(motcle);
        if (previous == null) {
            return motcle.getParent();
        }
        List<Motcle> childList = previous.getChildList();
        while (!childList.isEmpty()) {
            previous = childList.get(childList.size() - 1);
            childList = previous.getChildList();
        }
        return previous;
    }

    /**
     * Retourne le mot-clé suivant le mot-clé en argument dans la liste des
     * mots-clés du même père. Autrement dit, cette méthode retourne le frère
     * cadet ou <em>null</em> si le mot-clé est le dernier de la fratrie.
     */
    public static Motcle getNextSibling(Motcle motcle) {
        int childIndex = motcle.getChildIndex();
        Motcle parent = motcle.getParent();
        List<Motcle> siblingList = (parent != null) ? parent.getChildList() : motcle.getThesaurus().getFirstLevelList();
        if (childIndex < siblingList.size() - 1) {
            return siblingList.get(childIndex + 1);
        } else {
            return null;
        }
    }

    /**
     * Retourne le mot-clé précédent le mot-clé en argument dans la liste des
     * mots-clés du même père. Autrement dit, cette méthode retourne le frère
     * aîné ou <em>null</em> si le mot-clé est le premier de la fratrie.
     */
    public static Motcle getPreviousSibling(Motcle motcle) {
        int childIndex = motcle.getChildIndex();
        if (childIndex == 0) {
            return null;
        }
        Motcle parent = motcle.getParent();
        List<Motcle> siblingList = (parent != null) ? parent.getChildList() : motcle.getThesaurus().getFirstLevelList();
        return siblingList.get(childIndex - 1);
    }

    public static void sortByIdalpha(ThesaurusEditor thesaurusEditor, Motcle motcle, boolean recursive) {
        List<Motcle> childList = (motcle != null) ? motcle.getChildList() : thesaurusEditor.getThesaurus().getFirstLevelList();
        int size = childList.size();
        if (size == 0) {
            return;
        }
        if (size == 1) {
            if (recursive) {
                sortByIdalpha(thesaurusEditor, childList.get(0), true);
                return;
            }
        }
        Motcle[] array = childList.toArray(new Motcle[size]);
        Arrays.sort(array, Comparators.IDALPHA);
        for (int i = 0; i < size; i++) {
            Motcle child = array[i];
            if (child.getChildIndex() != i) {
                thesaurusEditor.setChildIndex(child, i);
            }
            if (recursive) {
                sortByIdalpha(thesaurusEditor, child, true);
            }
        }
    }

    /**
     * Retourne
     */
    public static boolean isDescendant(Motcle motcle, Motcle ancestor) {
        Motcle parent = motcle.getParent();
        while (parent != null) {
            if (parent.equals(ancestor)) {
                return true;
            }
            parent = parent.getParent();
        }
        return false;
    }

    public static int removeRemoveableMotcle(ThesaurusEditor thesaurusEditor) {
        int count = 0;
        List<Motcle> firstLevel = thesaurusEditor.getThesaurus().getFirstLevelList();
        for (Motcle motcle : firstLevel) {
            count = count + removeRemoveable(motcle, thesaurusEditor);
        }
        return count;
    }

    private static int removeRemoveable(Motcle motcle, ThesaurusEditor thesaurusEditor) {
        Fichotheque fichotheque = motcle.getFichotheque();
        int count = 0;
        for (Motcle child : motcle.getChildList()) {
            count = count + removeRemoveable(child, thesaurusEditor);
        }
        if (fichotheque.isRemoveable(motcle)) {
            thesaurusEditor.removeMotcle(motcle);
            count++;
        }
        return count;
    }

    /**
     *
     * @throws IllegalArgumentException si le type est incorrect.
     */
    public static String thesaurusTypeToString(short type) {
        switch (type) {
            case ThesaurusMetadata.BABELIEN_TYPE:
                return "babelien";
            case ThesaurusMetadata.IDALPHA_TYPE:
                return "idalpha";
            case ThesaurusMetadata.MULTI_TYPE:
                return "multi";
            default:
                throw new IllegalArgumentException("wrong type");
        }
    }

    /**
     *
     * @throws IllegalArgumentException si le type est incorrect.
     */
    public static short thesaurusTypeToShort(String thesaurusType) {
        switch (thesaurusType) {
            case "babelien":
            case "babelian":
                return ThesaurusMetadata.BABELIEN_TYPE;
            case "idalpha":
                return ThesaurusMetadata.IDALPHA_TYPE;
            case "multi":
                return ThesaurusMetadata.MULTI_TYPE;
            default:
                throw new IllegalArgumentException("wrong type");
        }
    }

    /**
     * Retourne la liste des clés des thésaurus babéliens. Cette liste est
     * établie à un instant t. Elle n'est pas synchronisée.
     *
     */
    public static List<Thesaurus> getBabelienThesaurusList(Fichotheque fichotheque) {
        List<Thesaurus> list = new ArrayList<Thesaurus>();
        for (Thesaurus thesaurus : fichotheque.getThesaurusList()) {
            if (thesaurus.isBabelienType()) {
                list.add(thesaurus);
            }
        }
        return list;
    }

    /**
     * Retourne la lslite des clés des thésaurus sans identifiant
     * alphanumériques.
     */
    public static List<Thesaurus> getNoIdalphaThesaurusList(Fichotheque fichotheque) {
        List<Thesaurus> list = new ArrayList<Thesaurus>();
        for (Thesaurus thesaurus : fichotheque.getThesaurusList()) {
            if (!thesaurus.isIdalphaType()) {
                list.add(thesaurus);
            }
        }
        return list;
    }

    public static String getGlobalPosition(Motcle motcle) {
        StringBuilder buf = new StringBuilder();
        try {
            appendGlobalPosition(buf, motcle);
        } catch (IOException ioe) {
        }
        return buf.toString();
    }

    public static void appendGlobalPosition(Appendable buf, Motcle motcle) throws IOException {
        Motcle parent = motcle.getParent();
        int position = motcle.getChildIndex() + 1;
        int size;
        if (parent != null) {
            appendGlobalPosition(buf, parent);
            buf.append('.');
            size = parent.getChildList().size();
        } else {
            size = motcle.getThesaurus().getFirstLevelList().size();
        }
        if (size < 99) {
            size = 99;
        }
        appendZero(buf, size, position);
        buf.append(String.valueOf(position));
    }

    private static void appendZero(Appendable buf, int size, int position) throws IOException {
        int dizaine = 10;
        while ((size >= dizaine)) {
            if (position < dizaine) {
                buf.append('0');
            }
            dizaine = dizaine * 10;
        }
    }

    public static List<Motcle> toHierarchicMotcleList(Thesaurus thesaurus) {
        List<Motcle> flatList = new ArrayList<Motcle>();
        appendMotcleList(flatList, thesaurus.getFirstLevelList());
        return flatList;
    }

    private static void appendMotcleList(List<Motcle> flatList, List<Motcle> motcleList) {
        for (Motcle motcle : motcleList) {
            flatList.add(motcle);
            appendMotcleList(flatList, motcle.getChildList());
        }
    }

    /**
     *
     * @throws IllegalStateException si le thésaurus de transfer n'est pas
     * babélien.
     */
    public static Thesaurus getTransferThesaurus(Fichotheque fichotheque, DynamicEditPolicy policy) {
        Thesaurus thesaurus = (Thesaurus) fichotheque.getSubset(((DynamicEditPolicy.Transfer) policy).getTransferThesaurusKey());
        if (!thesaurus.isBabelienType()) {
            throw new IllegalStateException("thesaurus is not babelien");
        }
        return thesaurus;
    }

    /**
     *
     * @throws IllegalStateException si un des thésaurusKey ne correspond pas à
     * un thésaurus de la base ou si c'est un thésaurus avec idalpha.
     */
    @Nullable
    public static Thesaurus[] getCheckThesaurusArray(Fichotheque fichotheque, DynamicEditPolicy policy) {
        if (!(policy instanceof DynamicEditPolicy.Check)) {
            return null;
        }
        DynamicEditPolicy.Check checkPolicy = (DynamicEditPolicy.Check) policy;
        List<SubsetKey> checkList = checkPolicy.getCheckSubseKeyList();
        int checkCount = checkList.size();
        Thesaurus[] array = new Thesaurus[checkCount];
        for (int i = 0; i < checkCount; i++) {
            SubsetKey thesaurusKey = checkList.get(i);
            Thesaurus thesaurus = (Thesaurus) fichotheque.getSubset(thesaurusKey);
            if (thesaurus == null) {
                throw new IllegalStateException("thesaurusPolicy.getVerifThesaurusKeyList() contains a wrong key");
            }
            if (thesaurus.isIdalphaType()) {
                throw new IllegalStateException("thesaurus with idalpha cannot own to verif list");
            }
            array[i] = thesaurus;
        }
        return array;
    }

    public static Collection<SubsetItem> toSubsetItemList(Thesaurus thesaurus, int max) {
        List<SubsetItem> motcleList = new ArrayList<SubsetItem>();
        Motcle motcle = ThesaurusUtils.getFirst(thesaurus);
        while (motcle != null) {
            motcleList.add(motcle);
            if (max > 0) {
                if (motcleList.size() >= max) {
                    break;
                }
            }
            motcle = ThesaurusUtils.getNext(motcle);
        }
        return motcleList;
    }

    public static boolean changeMotcleLabels(ThesaurusEditor thesaurusEditor, Motcle motcle, LabelChange labelChange) {
        boolean done = false;
        for (Label label : labelChange.getChangedLabels()) {
            boolean stepdone = thesaurusEditor.putLabel(motcle, label);
            if (stepdone) {
                done = true;
            }
        }
        for (Lang lang : labelChange.getRemovedLangList()) {
            boolean stepdone = thesaurusEditor.removeLabel(motcle, lang);
            if (stepdone) {
                done = true;
            }
        }
        return done;
    }

    public static Motcle getMotcle(Thesaurus thesaurus, String idString) throws NumberFormatException {
        if (thesaurus.isIdalphaType()) {
            Motcle motcle = thesaurus.getMotcleByIdalpha(idString);
            if (motcle != null) {
                return motcle;
            } else {
                try {
                    int motcleid = Integer.parseInt(idString);
                    return thesaurus.getMotcleById(motcleid);
                } catch (NumberFormatException nfe) {
                    return null;
                }
            }
        } else {
            int motcleid = Integer.parseInt(idString);
            return thesaurus.getMotcleById(motcleid);
        }
    }

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


    private static class MotcleList extends AbstractList<Motcle> implements RandomAccess {

        private final Motcle[] array;

        private MotcleList(Motcle[] array) {
            this.array = array;
        }

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

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

    }

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


    private static class IconSourcesEntryList extends AbstractList<IconSources.Entry> implements RandomAccess {

        private final IconSources.Entry[] array;

        private IconSourcesEntryList(IconSources.Entry[] array) {
            this.array = array;
        }

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

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

    }

}
