/*
 *******************************************************************************
 * Copyright (C) 2008-2015, International Business Machines Corporation and
 * others. All Rights Reserved.
 *******************************************************************************
 */
package com.ibm.icu.impl.javaspi;

import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.ULocale.Builder;

public class ICULocaleServiceProvider {
    private static final String SPI_PROP_FILE = "com/ibm/icu/impl/javaspi/ICULocaleServiceProviderConfig.properties";

    private static final String SUFFIX_KEY = "com.ibm.icu.impl.javaspi.ICULocaleServiceProvider.icuVariantSuffix";
    private static final String ENABLE_VARIANTS_KEY = "com.ibm.icu.impl.javaspi.ICULocaleServiceProvider.enableIcuVariants";
    private static final String ENABLE_ISO3_LANG_KEY = "com.ibm.icu.impl.javaspi.ICULocaleServiceProvider.enableIso3Languages";
    private static final String USE_DECIMALFORMAT_KEY = "com.ibm.icu.impl.javaspi.ICULocaleServiceProvider.useDecimalFormat";

    private static boolean configLoaded = false;

    private static String suffix = "ICU4J";
    private static boolean enableVariants = true;
    private static boolean enableIso3Lang = true;
    private static boolean useDecimalFormat = false;

    private static final Locale[] SPECIAL_LOCALES = {
        new Locale("ja", "JP", "JP"),
        new Locale("no"),
        new Locale("no", "NO"),
        new Locale("no", "NO", "NY"),
        new Locale("sr", "CS"),
        new Locale("th", "TH", "TH"),
    };

    private static Map<Locale, Locale> SPECIAL_LOCALES_MAP = null;

    private static Locale[] LOCALES = null;

    public static Locale[] getAvailableLocales() {
        Locale[] all = getLocales();
        return Arrays.copyOf(all, all.length);
    }

    public static ULocale toULocaleNoSpecialVariant(Locale locale) {
        // If the given Locale has legacy ill-formed variant
        // reserved by JDK, use the map to resolve the locale.
        Locale spLoc = getSpecialLocalesMap().get(locale);
        if (spLoc != null) {
            return ULocale.forLocale(spLoc);
        }

        // The locale may have script field on Java 7+.
        // So we once convert it to ULocale, then strip the ICU suffix off
        // if necessary.
        ULocale result = ULocale.forLocale(locale);
        String variant = result.getVariant();
        String suffix = getIcuSuffix();
        String variantNoSuffix = null;
        if (variant.equals(suffix)) {
            variantNoSuffix = "";
        } else if (variant.endsWith(suffix) && variant.charAt(variant.length() - suffix.length() - 1) == '_') {
            variantNoSuffix = variant.substring(0, variant.length() - suffix.length() - 1);
        }
        if (variantNoSuffix == null) {
            return result;
        }

        // Strip off ICU's special suffix - cannot use Builder because
        // original locale may have ill-formed variant
        StringBuilder id = new StringBuilder(result.getLanguage());
        String script = result.getScript();
        String country = result.getCountry();
        if (script.length() > 0) {
            id.append('_');
            id.append(script);
        }
        if (country.length() > 0 || variantNoSuffix.length() > 0) {
            id.append('_');
            id.append(country);
        }
        if (variantNoSuffix.length() > 0) {
            id.append('_');
            id.append(variantNoSuffix);
        }
        String orgID = result.getName();
        int kwdIdx = orgID.indexOf('@');
        if (kwdIdx >= 0) {
            id.append(orgID.substring(kwdIdx));
        }
        return new ULocale(id.toString());
    }

    public static boolean useDecimalFormat() {
        loadConfiguration();
        return useDecimalFormat;
    }

    private static synchronized Map<Locale, Locale> getSpecialLocalesMap() {
        if (SPECIAL_LOCALES_MAP != null) {
            return SPECIAL_LOCALES_MAP;
        }

        Map<Locale, Locale> splocs = new HashMap<Locale, Locale>();
        for (Locale spLoc : SPECIAL_LOCALES) {
            String var = spLoc.getVariant();
            if (var.length() > 0) {
                splocs.put(new Locale(spLoc.getLanguage(), spLoc.getCountry(), var + "_" + getIcuSuffix()), spLoc);
            }
        }
        SPECIAL_LOCALES_MAP = Collections.unmodifiableMap(splocs);
        return SPECIAL_LOCALES_MAP;
    }

    private static synchronized Locale[] getLocales() {
        if (LOCALES != null) {
            return LOCALES;
        }

        Set<Locale> localeSet = new HashSet<Locale>();
        ULocale[] icuLocales = ICUResourceBundle.getAvailableULocales();

        for (ULocale uloc : icuLocales) {
            String language = uloc.getLanguage();
            if (language.length() >= 3 && !enableIso3Languages()) {
                continue;
            }
            addULocale(uloc, localeSet);

            if (uloc.getScript().length() > 0 && uloc.getCountry().length() > 0) {
                // ICU's available locales do not contain language+country
                // locales if script is available. Need to add them too.
                Builder locBld = new Builder();
                try {
                    locBld.setLocale(uloc);
                    locBld.setScript(null);
                    ULocale ulocWithoutScript = locBld.build();
                    addULocale(ulocWithoutScript, localeSet);
                } catch (Exception e) {
                    // ignore
                }
            }
        }

        for (Locale l : SPECIAL_LOCALES) {
            addLocale(l, localeSet);
        }

        LOCALES = localeSet.toArray(new Locale[0]);
        return LOCALES;
    }

    private static void addLocale(Locale loc, Set<Locale> locales) {
        locales.add(loc);

        if (enableIcuVariants()) {
            // Add ICU variant
            String language = loc.getLanguage();
            String country = loc.getCountry();
            String variant = loc.getVariant();

            StringBuilder var = new StringBuilder(variant);
            if (var.length() != 0) {
                var.append("_");
            }
            var.append(getIcuSuffix());
            locales.add(new Locale(language, country, var.toString()));
        }
    }

    private static void addULocale(ULocale uloc, Set<Locale> locales) {
        // special case - nn
        // ULocale#toLocale on Java 6 maps "nn" to "no_NO_NY"
        if (uloc.getLanguage().equals("nn") && uloc.getScript().length() == 0) {
            Locale locNN = new Locale(uloc.getLanguage(), uloc.getCountry(), uloc.getVariant());
            addLocale(locNN, locales);
            return;
        }

        locales.add(uloc.toLocale());

        if (enableIcuVariants()) {
            // Add ICU variant
            StringBuilder var = new StringBuilder(uloc.getVariant());
            if (var.length() != 0) {
                var.append("_");
            }
            var.append(getIcuSuffix());

            Builder locBld = new Builder();
            try {
                locBld.setLocale(uloc);
                locBld.setVariant(var.toString());
                ULocale ulocWithVar = locBld.build();
                locales.add(ulocWithVar.toLocale());
            } catch (Exception e) {
                // ignore
            }
        }
    }

    private static boolean enableIso3Languages() {
        return enableIso3Lang;
    }

    private static boolean enableIcuVariants() {
        loadConfiguration();
        return enableVariants;
    }

    private static String getIcuSuffix() {
        loadConfiguration();
        return suffix;
    }

    private static synchronized void loadConfiguration() {
        if (configLoaded) {
            return;
        }
        Properties spiConfigProps = new Properties();
        try {
            InputStream is = ClassLoader.getSystemResourceAsStream(SPI_PROP_FILE);
            try {
                spiConfigProps.load(is);
            } finally {
                is.close();
            }

            String val = (String)spiConfigProps.get(SUFFIX_KEY);
            if (val != null && val.length() > 0) {
                suffix = val;
            }
            enableVariants = parseBooleanString((String)spiConfigProps.get(ENABLE_VARIANTS_KEY), enableVariants);
            enableIso3Lang = parseBooleanString((String)spiConfigProps.get(ENABLE_ISO3_LANG_KEY), enableIso3Lang);
            useDecimalFormat = parseBooleanString((String)spiConfigProps.get(USE_DECIMALFORMAT_KEY), useDecimalFormat);
        } catch (IOException ioe) {
            // Any IO errors, ignore
        }
        configLoaded = true;
    }

    private static boolean parseBooleanString(String str, boolean defaultVal) {
        if (str == null) {
            return defaultVal;
        }
        if (str.equalsIgnoreCase("true")) {
            return true;
        } else if (str.equalsIgnoreCase("false")) {
            return false;
        }
        return defaultVal;
    }
}
