package me.teridax.jcash.lang;

import me.teridax.jcash.Logging;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

public final class Translator {

    /**
     * List of all supported locales in the format: language_country. Examples: en_EN, fr_FR
     * The index inside the list is directly related to the index of the translation array inside the list translations.
     */
    private static final List<String> languages = new ArrayList<>();
    /**
     * Mapping of a default english phrase of the code en_EN which is associated with  a list of possible translations.
     * Index 0 of the translation is equivalent to the key itself since locale 0 is always en_EN.
     */
    private static final Map<String, String[]> translations = new HashMap<>();
    /**
     * Precomputed index of the locale used to statically translate a phrase
     */
    private static int localeTranslationIndex = 0;

    static {
        // read language file and parse
        try (var stream = Objects.requireNonNull(Translator.class.getResourceAsStream("languages.csv"))) {
            var text = new String(stream.readAllBytes(), StandardCharsets.UTF_8);

            // parse each line
            text.lines().forEach(line -> {
                // read header i.e. the locales identifier
                if (languages.isEmpty()) {
                    // split string into columns by comma and trim each column
                    languages.addAll(Arrays.stream(line.split(",")).map(String::trim).collect(Collectors.toList()));

                    // check if default locale is present
                    if (!languages.contains("en_EN")) {
                        Logging.LOGGER.severe("Missing default en_EN locale");
                        throw new IllegalArgumentException("Missing en_EN locale");
                    }

                    Logging.LOGGER.info("Read locales: " + Arrays.deepToString(languages.toArray()));
                    return;
                }

                var translation = Arrays.stream(line.split(",")).map(String::trim).toArray(String[]::new);

                // check if all translations are present
                // it may happen at a locale does not provide a translation
                if (translation.length != languages.size())
                    Logging.LOGGER.warning("invalid translations: " + translation.length + " " + languages.size());

                translations.put(translation[0], translation);
            });

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Translates the given phrase into the corresponding phrase of the selected locale of the translator.
     * If no translation is found or no locale is defined for the translator this function will return the given phrase.
     *
     * @param phrase the text to translate
     * @return the translated phrase, or the phrase itself in case no translation can be found
     */
    public static String translate(String phrase) {
        try {
            return translations.get(phrase)[localeTranslationIndex];

        } catch (ArrayIndexOutOfBoundsException e) {
            Logging.LOGGER.severe("Locale does not exist with index: " + localeTranslationIndex);
        } catch (NullPointerException e) {
            Logging.LOGGER.severe("No translation found for phrase: " + phrase);
        }
        return phrase;
    }

    /**
     * Returns an array of all available locales for this translator
     *
     * @return an array of all available locales for this translator
     */
    public static String[] availableLocales() {
        return languages.toArray(String[]::new);
    }

    /**
     * Map the given locale string to an index indicating which array location to choose when fetching a result from
     * the translation map.
     *
     * @param locale the locale string
     * @return a matching index of the locale
     * @throws IllegalArgumentException if the given locale is not part of the available locales
     */
    private static int mapLocaleToIndex(String locale) throws IllegalArgumentException {
        for (int i = 0; i < languages.size(); i++) {
            if (languages.get(i).equals(locale)) {
                return i;
            }
        }

        throw new IllegalArgumentException("Locale does not exist: " + locale);
    }

    /**
     * Sets the default locale to use when translating.
     * The locale must have the format language_COUNTRY like en_EN
     *
     * @param locale the locale to use when translating
     * @return if the specified locale can be used by the translator
     */
    public static boolean setTranslationLocale(String locale) {
        try {
            localeTranslationIndex = Translator.mapLocaleToIndex(locale);
            return true;
        } catch (IllegalArgumentException ex) {
            Logging.LOGGER.severe("unable to set locale for translation: " + locale + " because: " + ex.getMessage());
            return false;
        }
    }
}
