/* UtilLib - Copyright (c) 2014-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.mapeadores.util.localisation;

import java.text.ParseException;
import java.util.AbstractList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import net.mapeadores.util.logging.MessageHandler;
import net.mapeadores.util.text.StringUtils;
import net.mapeadores.util.xml.DOMUtils;
import net.mapeadores.util.xml.DomMessages;
import net.mapeadores.util.xml.XMLUtils;
import org.w3c.dom.Element;


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

    public final static Langs EMPTY_LANGS = new EmptyLangs();
    public final static List<Lang> EMPTY_LANGLIST = Collections.emptyList();

    private LangsUtils() {
    }

    public static Langs toCleanLangs(String s) {
        Lang[] array = toCleanLangArray(s);
        return new ArrayLangs(array);
    }

    public static Lang[] toCleanLangArray(String s) {
        String[] tokens = StringUtils.getTechnicalTokens(s, true);
        Set<Lang> langSet = new LinkedHashSet<Lang>();
        for (String token : tokens) {
            try {
                Lang lang = Lang.parse(token);
                langSet.add(lang);
            } catch (ParseException mce) {
            }
        }
        return langSet.toArray(new Lang[langSet.size()]);
    }

    public static Langs fromCollection(Collection<Lang> collection) {
        Lang[] array = collection.toArray(new Lang[collection.size()]);
        return new ArrayLangs(array);
    }

    public static boolean areEquals(Langs langs, Lang[] array) {
        int length = array.length;
        if (length != langs.size()) {
            return false;
        }
        if (length == 0) {
            return true;
        }
        for (int i = 0; i < length; i++) {
            if (!array[i].equals(langs.get(i))) {
                return false;
            }
        }
        return true;
    }

    public static boolean areEquals(Langs langs, Collection<Lang> collection) {
        int length = collection.size();
        if (length != langs.size()) {
            return false;
        }
        if (length == 0) {
            return true;
        }
        int p = 0;
        for (Lang lang : collection) {
            if (!lang.equals(langs.get(p))) {
                return false;
            }
            p++;
        }
        return true;
    }

    public static boolean areEquals(Langs langs1, Langs langs2) {
        int length = langs2.size();
        if (length != langs1.size()) {
            return false;
        }
        if (length == 0) {
            return true;
        }
        for (int i = 0; i < length; i++) {
            if (!langs2.get(i).equals(langs1.get(i))) {
                return false;
            }
        }
        return true;
    }

    public static Lang getNearestLang(Langs langs, Lang lang) {
        if (langs.isEmpty()) {
            throw new IllegalArgumentException("langs.isEmpty()");
        }
        Lang option1 = null;
        Lang option2 = null;
        Lang rootLang = lang.getRootLang();
        for (Lang testedLang : langs) {
            if (testedLang.equals(lang)) {
                return testedLang;
            } else if (testedLang.equals(rootLang)) {
                option1 = testedLang;
            } else if ((testedLang.getRootLang().equals(rootLang))) {
                option2 = testedLang;
            }
        }
        if (option1 != null) {
            return option1;
        }
        if (option2 != null) {
            return option2;
        }
        return langs.get(0);
    }

    public static Langs merge(Langs... langs) {
        if (langs == null) {
            return EMPTY_LANGS;
        }
        Set<Lang> set = new LinkedHashSet<Lang>();
        for (Langs lgs : langs) {
            if (lgs != null) {
                for (Lang lang : lgs) {
                    set.add(lang);
                }
            }
        }
        return wrap(set);
    }

    public static Langs wrap(Collection<Lang> langs) {
        int size = langs.size();
        if (size == 0) {
            return EMPTY_LANGS;
        }
        return new ArrayLangs(langs.toArray(new Lang[size]));
    }

    public static Langs wrap(Lang... langs) {
        if ((langs == null) || (langs.length == 0)) {
            return EMPTY_LANGS;
        }
        return new ArrayLangs(langs);
    }

    public static Lang[] toArray(Langs langs) {
        return langs.toArray(new Lang[langs.size()]);
    }

    public static Lang checkLang(Lang userLang, List<Lang> authorizedLangs) {
        Lang option1 = null;
        Lang option2 = null;
        Lang rootLang = userLang.getRootLang();
        for (Lang authorizedLang : authorizedLangs) {
            if (authorizedLang.equals(userLang)) {
                return authorizedLang;
            } else if (authorizedLang.equals(rootLang)) {
                option1 = authorizedLang;
            } else if ((authorizedLang.getRootLang().equals(rootLang))) {
                option2 = authorizedLang;
            }
        }
        if (option1 != null) {
            return option1;
        }
        if (option2 != null) {
            return option2;
        }
        return null;
    }

    public static void readLangElements(Collection<Lang> destination, Element parentElement, MessageHandler messageHandler, String xpath) {
        DOMUtils.readChildren(parentElement, new LangConsumer(destination, messageHandler, xpath));
    }

    public static void readLangElements(LangPreferenceBuilder langPreferenceBuilder, Element parentElement, MessageHandler messageHandler, String xpath) {
        DOMUtils.readChildren(parentElement, new LangPreferenceConsumer(langPreferenceBuilder, messageHandler, xpath));
    }

    public static Lang getPreferredAvailableLang(Langs availableLangs, LangPreference langPreference) {
        if (availableLangs.isEmpty()) {
            throw new IllegalArgumentException("availableLangs is empty");
        }
        Set<Lang> currentSet = new HashSet<Lang>(availableLangs);
        for (Lang lang : langPreference) {
            if (currentSet.contains(lang)) {
                return lang;
            }
            if (!lang.isRootLang()) {
                if (currentSet.contains(lang.getRootLang())) {
                    return lang.getRootLang();
                }
            }
        }
        return availableLangs.get(0);
    }


    private static class EmptyLangs extends AbstractList<Lang> implements Langs {

        private EmptyLangs() {
        }

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

        @Override
        public Lang get(int i) {
            throw new IndexOutOfBoundsException("langCount = 0");
        }

    }


    private static class ArrayLangs extends AbstractList<Lang> implements Langs {

        private final Lang[] array;

        private ArrayLangs(Lang[] array) {
            this.array = array;
        }

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

        @Override
        public Lang get(int i) {
            return array[i];
        }

    }


    private static class LangConsumer implements Consumer<Element> {

        private final Collection<Lang> langs;
        private final MessageHandler messageHandler;
        private final String xpath;

        private LangConsumer(Collection<Lang> langs, MessageHandler messageHandler, String xpath) {
            this.langs = langs;
            this.messageHandler = messageHandler;
            this.xpath = xpath;
        }

        @Override
        public void accept(Element element) {
            String tagName = element.getTagName();
            String tagPath = xpath + "/" + tagName;
            if (tagName.equals("lang")) {
                String text = XMLUtils.getData(element);
                try {
                    Lang lang = Lang.parse(text);
                    langs.add(lang);
                } catch (ParseException pe) {
                    DomMessages.wrongElementValue(messageHandler, tagPath, text);
                }
            } else {
                DomMessages.unknownTagWarning(messageHandler, tagPath);
            }
        }

    }


    private static class LangPreferenceConsumer implements Consumer<Element> {

        private final LangPreferenceBuilder langPreferenceBuilder;
        private final MessageHandler messageHandler;
        private final String xpath;

        private LangPreferenceConsumer(LangPreferenceBuilder langPreferenceBuilder, MessageHandler messageHandler, String xpath) {
            this.langPreferenceBuilder = langPreferenceBuilder;
            this.messageHandler = messageHandler;
            this.xpath = xpath;
        }

        @Override
        public void accept(Element element) {
            String tagName = element.getTagName();
            String tagPath = xpath + "/" + tagName;
            if (tagName.equals("lang")) {
                String text = XMLUtils.getData(element);
                try {
                    Lang lang = Lang.parse(text);
                    langPreferenceBuilder.addLang(lang);
                } catch (ParseException pe) {
                    DomMessages.wrongElementValue(messageHandler, tagPath, text);
                }
            } else {
                DomMessages.unknownTagWarning(messageHandler, tagPath);
            }
        }

    }

}
