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

import java.text.ParseException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import net.mapeadores.util.conditions.ConditionsConstants;
import net.mapeadores.util.conditions.ConditionsUtils;
import net.mapeadores.util.conditions.TextCondition;
import net.mapeadores.util.conditions.TextTestEngine;
import net.mapeadores.util.exceptions.ShouldNotOccurException;
import net.mapeadores.util.text.StringUtils;


/**
 *
 * @author Vincent Calame
 */
public class MessageLocalisationFactory implements CodeCatalog {

    private final static Lang NO_LINGUISTIC = Lang.build("zxx");
    private final static Lang ENGLISH = Lang.build("en");
    private final static Lang FRENCH = Lang.build("fr");
    private final static Pattern langCodePattern;
    private final static Pattern countryCodePattern;
    private final Map<String, MessageInfo> infoMap = new HashMap<String, MessageInfo>();
    private final SortedMap<String, MessageInfo> langMap = new TreeMap<String, MessageInfo>();
    private final SortedMap<String, MessageInfo> countryMap = new TreeMap<String, MessageInfo>();
    private final Set<String> unmodifiableLangSet = Collections.unmodifiableSet(langMap.keySet());
    private final Set<String> unmodifiableCountrySet = Collections.unmodifiableSet(countryMap.keySet());


    static {
        try {
            langCodePattern = Pattern.compile("^[a-z][a-z][a-z]?$");
            countryCodePattern = Pattern.compile("^[A-Z][A-Z]$");
        } catch (PatternSyntaxException pse) {
            throw new ShouldNotOccurException(pse);
        }
    }

    public MessageLocalisation newInstance(Lang lang) {
        Lang[] langArray = new Lang[1];
        langArray[0] = lang;
        return new InternalMessageLocalisation(langArray, lang.toLocale());
    }

    public MessageLocalisation newInstance(LangPreference langPreference, Locale formatLocale) {
        int langLength = langPreference.size();
        Lang[] langArray = new Lang[langLength];
        for (int i = 0; i < langLength; i++) {
            langArray[i] = langPreference.get(i);
        }
        return new InternalMessageLocalisation(langArray, formatLocale);
    }

    protected void addEntry(String key, Lang lang, String text) {
        MessageInfo messageInfo = infoMap.get(key);
        if (messageInfo == null) {
            messageInfo = new MessageInfo(key, text);
            infoMap.put(key, messageInfo);
            if (langCodePattern.matcher(key).matches()) {
                langMap.put(key, messageInfo);
            } else if (countryCodePattern.matcher(key).matches()) {
                countryMap.put(key, messageInfo);
            }
        }
        messageInfo.add(lang, text);
    }

    @Override
    public Set<String> getLangCodeSet() {
        return unmodifiableLangSet;
    }

    @Override
    public Set<String> getCountryCodeSet() {
        return unmodifiableCountrySet;
    }

    @Override
    public Set<String> getLangCodeSet(TextCondition condition, LangPreference langPreference) {
        switch (ConditionsUtils.getConditionType(condition)) {
            case ConditionsConstants.PARTIAL_CONDITION:
            case ConditionsConstants.PARTIALOREMPTY_CONDITION:
                return getPartialCodeSet(langMap.values(), condition, langPreference);
            case ConditionsConstants.EMPTY_CONDITION:
            case ConditionsConstants.IMPOSSIBLE_CONDITION:
                return Collections.emptySet();
            default:
                return unmodifiableLangSet;
        }
    }

    @Override
    public Set<String> getCountryCodeSet(TextCondition condition, LangPreference langPreference) {
        switch (ConditionsUtils.getConditionType(condition)) {
            case ConditionsConstants.PARTIAL_CONDITION:
            case ConditionsConstants.PARTIALOREMPTY_CONDITION:
                return getPartialCodeSet(countryMap.values(), condition, langPreference);
            case ConditionsConstants.IMPOSSIBLE_CONDITION:
                return Collections.emptySet();
            default:
                return unmodifiableCountrySet;
        }
    }

    private static Set<String> getPartialCodeSet(Collection<MessageInfo> collection, TextCondition condition, LangPreference langPreference) {
        TextTestEngine[] engineArray = initEngineArray(condition, langPreference);
        SortedSet<String> result = new TreeSet<String>();
        for (MessageInfo messageInfo : collection) {
            if (messageInfo.test(engineArray)) {
                result.add(messageInfo.key);
            }
        }
        return result;
    }


    private class InternalMessageLocalisation implements MessageLocalisation {

        private final Lang[] langArray;
        private final Locale formatLocale;

        private InternalMessageLocalisation(Lang[] langArray, Locale formatLocale) {
            this.langArray = langArray;
            this.formatLocale = formatLocale;
        }

        @Override
        public String toString(String messageKey) {
            if (messageKey.isEmpty()) {
                return "";
            }
            MessageInfo messageInfo = infoMap.get(messageKey);
            if (messageInfo == null) {
                return testCode(messageKey);
            }
            return messageInfo.getText(langArray);
        }

        @Override
        public String toString(Message message) {
            String messageKey = message.getMessageKey();
            if (messageKey.isEmpty()) {
                return LocalisationUtils.joinValues(message);
            }
            MessageInfo messageInfo = infoMap.get(messageKey);
            if (messageInfo == null) {
                return testCode(messageKey);
            }
            String text = messageInfo.getText(langArray);
            if (text == null) {
                return null;
            } else {
                try {
                    return StringUtils.formatMessage(text, message.getMessageValues(), formatLocale);
                } catch (StringUtils.FormatException fe) {
                    return "[" + messageKey + "] " + fe.getMessage();
                }
            }
        }

        private String testCode(String messageKey) {
            try {
                Lang lang = Lang.parse(messageKey);
                if (!lang.isRootLang()) {
                    StringBuilder buf = new StringBuilder();
                    LangScript langScript = lang.getScript();
                    if (langScript != null) {
                        String langTitle = toString(lang.getRootLang().toString() + "-" + langScript.toString());
                        if (langTitle != null) {
                            buf.append(StringUtils.getFirstPart(langTitle));
                        } else {
                            langTitle = toString(lang.getRootLang().toString());
                            if (langTitle != null) {
                                buf.append(StringUtils.getFirstPart(langTitle));
                                buf.append(" (");
                                String scriptTitle = toString(langScript.toString());
                                if (scriptTitle != null) {
                                    buf.append(StringUtils.getFirstPart(scriptTitle));
                                } else {
                                    buf.append(langScript.toString());
                                }
                                buf.append(")");
                            } else {
                                return null;
                            }
                        }
                    } else {
                        String langTitle = toString(lang.getRootLang().toString());
                        if (langTitle != null) {
                            buf.append(StringUtils.getFirstPart(langTitle));
                        } else {
                            return null;
                        }
                    }
                    int variantCount = lang.getVariantCount();
                    if (variantCount > 0) {
                        buf.append(" (");
                        for (int i = 0; i < variantCount; i++) {
                            if (i > 0) {
                                buf.append('-');
                            }
                            buf.append(lang.getVariant(i));
                        }
                        buf.append(")");
                    }
                    Country region = lang.getRegion();
                    if (region != null) {
                        buf.append(" (");
                        String regionTitle = toString(region.toString());
                        if (regionTitle != null) {
                            buf.append(StringUtils.getFirstPart(regionTitle));
                        } else {
                            buf.append(region.toString());
                        }
                        buf.append(")");
                    }
                    return buf.toString();
                }
            } catch (ParseException pe) {

            }
            return null;
        }

        @Override
        public boolean containsKey(String messageKey) {
            if (infoMap.containsKey(messageKey)) {
                return true;
            }
            try {
                Lang lang = Lang.parse(messageKey);
                return infoMap.containsKey(lang.getRootLang().toString());
            } catch (ParseException pe) {
                return false;
            }
        }

    }


    private class MessageInfo {

        private final String key;
        private final Map<Lang, String> textMap = new HashMap<Lang, String>();
        private final String defaultText;

        private MessageInfo(String key, String defaultText) {
            this.key = key;
            this.defaultText = defaultText;
        }

        private void add(Lang lang, String text) {
            if (!textMap.containsKey(lang)) {
                textMap.put(lang, text);
            }
        }

        private String getText(Lang[] langArray) {
            String text;
            for (Lang lang : langArray) {
                if (lang.equals(NO_LINGUISTIC)) {
                    return key;
                }
                text = getText(lang);
                if (text != null) {
                    return text;
                }
            }
            text = getText(ENGLISH);
            if (text != null) {
                return text;
            }
            text = getText(FRENCH);
            if (text != null) {
                return text;
            }
            return defaultText;
        }

        private String getText(Lang lang) {
            String text = textMap.get(lang);
            if (text != null) {
                return text;
            }
            if (!lang.isRootLang()) {
                text = textMap.get(lang.getRootLang());
                if (text != null) {
                    return text;
                }
            }
            return null;
        }

        private boolean test(TextTestEngine[] engineArray) {
            for (TextTestEngine engine : engineArray) {
                Lang lang = engine.getLang();
                String text = textMap.get(lang);
                if (text != null) {
                    String[] tokens = StringUtils.getTokens(text, ';', StringUtils.EMPTY_EXCLUDE);
                    for (String token : tokens) {
                        if (engine.isSelected(token)) {
                            return true;
                        }
                    }
                    return false;
                }
            }
            return false;
        }

    }

    private static TextTestEngine[] initEngineArray(TextCondition condition, LangPreference langPreference) {
        int langLength = langPreference.size();
        TextTestEngine[] engineArray = new TextTestEngine[langLength];
        for (int i = 0; i < langLength; i++) {
            Lang lang = langPreference.get(i);
            engineArray[i] = TextTestEngine.newInstance(condition, lang);
        }
        return engineArray;
    }


}
