/* UtilLib - Copyright (c) 2008-2017 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.text.alphabet;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.Collator;
import java.text.RuleBasedCollator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.mapeadores.util.exceptions.InternalResourceException;
import net.mapeadores.util.localisation.Lang;
import net.mapeadores.util.text.StringUtils;
import net.mapeadores.util.text.collation.CollationUnit;


/**
 *
 * @author Vincent Calame
 */
public final class Alphabet implements AlphabeticEntry {

    private final static Lang DEFAULT_LANG = Lang.build("en");
    private final static Map<Lang, InitialInfo> initialInfoMap = new HashMap<Lang, InitialInfo>();
    private final static Map<Lang, Article[]> articleArrayMap = new HashMap<Lang, Article[]>();
    private final static Set<Lang> nulLangIntegerSet = new HashSet<Lang>();
    private final String sourceString;
    private final String articleString;
    private final String entryString;
    private final String initialChar;
    private final String alphabeticSort;
    private final boolean withSeparationSpace;

    private Alphabet(String sourceString, String articleString, String entryString, String initialChar, String alphabeticSort, boolean withSeparationSpace) {
        this.sourceString = sourceString;
        this.articleString = articleString;
        this.entryString = entryString;
        this.initialChar = initialChar;
        this.alphabeticSort = alphabeticSort;
        this.withSeparationSpace = withSeparationSpace;
    }

    public static AlphabeticEntry newInstance(String sourceString, Lang lang) {
        return newInstance(sourceString, lang, lang);
    }

    public static AlphabeticEntry newInstance(String sourceString, Lang articleLang, Lang sortLang) {
        String articleString = null;
        boolean withEspace = false;
        String entryString = sourceString;
        Article[] array = getArray(articleLang);
        if (array != null) {
            for (Article currentArticle : array) {
                if (sourceString.startsWith(currentArticle.getArticleString())) {
                    int length = currentArticle.getArticleLength();
                    if (sourceString.length() > length) {
                        articleString = currentArticle.getTrimedArticleString();
                        withEspace = currentArticle.isWithEspace();
                        entryString = sourceString.substring(length);
                    }
                    break;
                }
            }
        }
        InitialInfo initialInfo = getInitialInfo(sortLang);
        String alphabeticSort = initialInfo.getAlphabeticSort(entryString);
        String initialChar = initialInfo.getInitial(alphabeticSort);
        return new Alphabet(sourceString, articleString, entryString, initialChar, alphabeticSort, withEspace);
    }

    private static Article[] getArray(Lang lang) {
        if (lang == null) {
            return null;
        }
        if (nulLangIntegerSet.contains(lang)) {
            return null;
        }
        Article[] obj = articleArrayMap.get(lang);
        if (obj == null) {
            obj = initArray(lang);
            if (obj == null) {
                if (!lang.isRootLang()) {
                    obj = getArray(lang.getRootLang());
                }
            }
            if (obj == null) {
                synchronized (nulLangIntegerSet) {
                    nulLangIntegerSet.add(lang);
                }
            } else {
                synchronized (articleArrayMap) {
                    articleArrayMap.put(lang, obj);
                }
            }
        }
        return obj;
    }

    /*
     * Les articles doivent commencer par les plus longs (voir la boucle de newInstance)
     */
    private static Article[] initArray(Lang lang) {
        InputStream inputStream = Alphabet.class.getResourceAsStream("resources/article_" + lang.toString() + ".txt");
        if (inputStream == null) {
            return null;
        }
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"))) {
            List<String> list = new ArrayList<String>();
            String ligne;
            while ((ligne = reader.readLine()) != null) {
                ligne = ligne.trim();
                if ((ligne.length() > 0) && (ligne.charAt(0) != '#')) {
                    ligne = ligne.replace('_', ' ');
                    list.add(ligne);
                }
            }
            int size = list.size();
            Article[] result = new Article[size];
            for (int i = 0; i < size; i++) {
                result[i] = new Article(list.get(i));
            }
            return result;
        } catch (IOException ioe) {
            throw new InternalResourceException(ioe);
        }
    }

    @Override
    public boolean isWithArticle() {
        return (!sourceString.equals(entryString));
    }

    @Override
    public String getSourceString() {
        return sourceString;
    }

    @Override
    public String getArticleString() {
        return articleString;
    }

    @Override
    public String getEntryString() {
        return entryString;
    }

    @Override
    public boolean isWithSeparationSpace() {
        return withSeparationSpace;
    }

    @Override
    public String getInitialChar() {
        return initialChar;
    }

    @Override
    public String getAlphabeticSort() {
        return alphabeticSort;
    }

    private static InitialInfo getInitialInfo(Lang lang) {
        if (lang == null) {
            return getInitialInfo(DEFAULT_LANG);
        }
        InitialInfo initialInfo = initialInfoMap.get(lang);
        if (initialInfo == null) {
            synchronized (initialInfoMap) {
                RuleBasedCollator collator = (RuleBasedCollator) Collator.getInstance(lang.toLocale());
                collator.setStrength(Collator.PRIMARY);
                initialInfo = new InitialInfo(collator);
                initialInfoMap.put(lang, initialInfo);
            }
        }
        return initialInfo;
    }


    private static class Article {

        private final String articleString;
        private final boolean withEspace;
        private final String trimedArticleString;

        Article(String articleString) {
            this.articleString = articleString;
            if (articleString.charAt(articleString.length() - 1) == ' ') {
                trimedArticleString = articleString.substring(0, articleString.length() - 1);
                withEspace = true;
            } else {
                withEspace = false;
                trimedArticleString = articleString;
            }
        }

        public String getArticleString() {
            return articleString;
        }

        public boolean isWithEspace() {
            return withEspace;
        }

        public String getTrimedArticleString() {
            return trimedArticleString;
        }

        public int getArticleLength() {
            return articleString.length();
        }

    }


    private static class InitialInfo {

        private final RuleBasedCollator collator;
        private final Map<Character, String> initialStringMap = new HashMap<Character, String>();

        private InitialInfo(RuleBasedCollator collator) {
            this.collator = collator;
            for (char c = '0'; c <= '9'; c++) {
                addInitialString(c);
            }
            for (char c = 'A'; c <= 'Z'; c++) {
                addInitialString(c);
            }
        }

        private void addInitialString(char c) {
            char[] array = {c};
            String initialString = new String(array);
            String collatedKey = CollationUnit.collate(initialString, collator, true);
            initialStringMap.put(collatedKey.charAt(0), initialString);
        }

        private String getAlphabeticSort(String entryString) {
            String cleanedEntry = StringUtils.reduceToLetterAndDigit(entryString);
            return CollationUnit.collate(cleanedEntry, collator, true);
        }

        private String getInitial(String alphabeticSort) {
            if (alphabeticSort.length() == 0) {
                return null;
            }
            Character initial = alphabeticSort.charAt(0);
            String result = initialStringMap.get(initial);
            if (result == null) {
                return "_";
            }
            return result;
        }

    }

}
