package org.limewire.ui.swing.util;

import java.awt.Font;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.limewire.core.settings.ApplicationSettings;
import org.limewire.io.IOUtils;
import org.limewire.util.FileUtils;
import org.limewire.util.OSUtils;


/**
 * This class provides utility methods retrieving supported languages and
 * changing language settings.
 */
public class LanguageUtils {

    private static Log LOG = LogFactory.getLog(LanguageUtils.class);

    private static final String BUNDLE_PREFIX = "org/limewire/i18n/Messages_";

    private static final String BUNDLE_POSTFIX = ".class";

    private static final String BUNDLE_MARKER = "org/limewire/i18n/Messages.class";

    /**
     * Applies this language code to be the new language of the program.
     */
    public static void setLocale(Locale locale) {
        ApplicationSettings.LANGUAGE.set(locale.getLanguage());
        ApplicationSettings.COUNTRY.set(locale.getCountry());
        ApplicationSettings.LOCALE_VARIANT.set(locale.getVariant());
        
        LocaleUtils.setLocaleFromPreferences();
    }
    
    /**
     * Returns a Locale of the current language that is being used.
     */
    public static Locale getCurrentLocale() {
        return LocaleUtils.getCurrentLocale();
    }

    /**
     * Returns an array of supported language as a LanguageInfo[], always having
     * the English language as the first element.
     * <p>
     * This will only include languages that can be displayed using the given
     * font. If the font is null, all languages are returned.
     */
    public static Locale[] getLocales(Font font) {
        final List<Locale> locales = new LinkedList<Locale>();
        
        File jar = FileUtils.getJarFromClasspath(LanguageUtils.class.getClassLoader(), BUNDLE_MARKER);
        if (jar != null) {
            addLocalesFromJar(locales, jar);
        } else {
            LOG.warn("Could not find bundle jar to determine locales");
        }

        // TODO: fix for build
      /*  if (LimeWireUtils.isTestingVersion()) { 
            addLocalesFromJar(locales, new File(CVS_BUNDLE_FILE));
        } */
        
        Collections.sort(locales, new Comparator<Locale>() {
            @Override
			public int compare(Locale o1, Locale o2) {
                return o1.getDisplayName(o1).compareToIgnoreCase(
                        o2.getDisplayName(o2));
            }
        });
        
        locales.remove(Locale.ENGLISH);
        locales.add(0, Locale.ENGLISH);

        // remove languages that cannot be displayed using this font
        if (font != null && !OSUtils.isMacOSX()) {
            for (Iterator<Locale> it = locales.iterator(); it.hasNext();) {
                Locale locale = it.next();
                if (!GuiUtils.canDisplay(font, locale.getDisplayName(locale))) {
                    it.remove();
                }
            }
        }

        return locales.toArray(new Locale[0]);
    }

    /**
     * Returns the languages as found from the classpath in messages.jar.
     */
    static void addLocalesFromJar(List<Locale> locales, File jar) {
        ZipFile zip = null;
        try {
            zip = new ZipFile(jar);
            Enumeration<? extends ZipEntry> entries = zip.entries();
            while (entries.hasMoreElements()) {
                String name = entries.nextElement().getName();
                if (!name.startsWith(BUNDLE_PREFIX) || !name.endsWith(BUNDLE_POSTFIX)
                        || name.indexOf("$") != -1) {
                    continue;
                }

                String iso = name.substring(BUNDLE_PREFIX.length(), name.length()
                        - BUNDLE_POSTFIX.length());
                List<String> tokens = new ArrayList<String>(Arrays.asList(iso.split("_", 3)));
                if (tokens.size() < 1) {
                    continue;
                }
                while (tokens.size() < 3) {
                    tokens.add("");
                }

                Locale locale = new Locale(tokens.get(0), tokens.get(1), tokens.get(2));
                locales.add(locale);
            }
        } catch (IOException e) {
            LOG.warn("Could not determine locales", e);
        } finally {
            if (zip != null) {
                try {
                    zip.close();
                } catch (IOException ioe) {
                }
            }
        }
    }

    /**
     * Returns true if the language of <code>locale</code> is English.
     */
    public static boolean isEnglishLocale(Locale locale) {
        return Locale.ENGLISH.getLanguage().equals(locale.getLanguage());
    }

    /**
     * Returns a score between -1 and 3 how well <code>specificLocale</code>
     * matches <code>genericLocale</code>.
     * 
     * @return -1, if locales do not match, 3 if locales are equal
     */
    public static int getMatchScore(Locale specificLocale, Locale genericLocale) {
        int i = 0;
        if (specificLocale.getLanguage().equals(genericLocale.getLanguage())) {
            i += 1;
        } else if (genericLocale.getLanguage().length() > 0) {
            return -1;
        }
        if (specificLocale.getCountry().equals(genericLocale.getCountry())) {
            i += 1;
        } else if (genericLocale.getCountry().length() > 0) {
            return -1;
        }
        if (specificLocale.getVariant().equals(genericLocale.getVariant())) {
            i += 1;
        } else if (genericLocale.getVariant().length() > 0) {
            return -1;
        }
        
        return i;
    }
    
    /**
     * Uses the guess mechanism to attempt to guess the current locale.  The guess
     *  is then compared to the given list of available languages and the closest
     *  match is returned.  If no match is found English is defaulted since
     *  English will always be available.
     */
    public static Locale guessBestAvailableLocale(Locale... locales) {
        Locale guessLocale = guessLocale();
        Locale bestLocale = Locale.ENGLISH;
        int bestScore = Integer.MIN_VALUE;
        
        for ( Locale item : locales ) {
            int checkScore = getMatchScore(guessLocale, item); 
            if (checkScore >= 0 && checkScore > bestScore) {
                bestLocale = item;
                bestScore = checkScore; 
            }
        }
        
        return bestLocale;
    }
    
    public static Locale guessLocale() {
        String[] language = guessLanguage();
        return new Locale(language[0], language[1], language[2]);
    }
    
    public static String[] guessLanguage() {
        String ln = ApplicationSettings.LANGUAGE.get();
        String cn = ApplicationSettings.COUNTRY.get();
        String vn = ApplicationSettings.LOCALE_VARIANT.get();
        
        File file = new File("language.prop");
        if(!file.exists())
            return new String[] { ln, cn, vn };
            
        InputStream in = null;
        BufferedReader reader = null;
        String code = "";
        try {
            in = new FileInputStream(file);
            reader = new BufferedReader(new InputStreamReader(in));
            code = reader.readLine();
        } catch(IOException ignored) {
        } finally {
            IOUtils.close(in);
            IOUtils.close(reader);
        }
        
        String[] mapped = getLCID(code);
        if(mapped != null)
            return mapped;
        else
            return new String[] { ln, cn, vn };
    }
    
    /**
     * Returns the String[] { languageCode, countryCode, variantCode }
     * for the Windows LCID.
     */
    public static String[] getLCID(String code) {
        Map<String, String[]> map = new HashMap<String, String[]>();
        map.put("1078", new String[] { "af", "", "" } );
        map.put("1052", new String[] { "sq", "", "" } );
        map.put("5121", new String[] { "ar", "", "" } );
        map.put("15361", new String[] { "ar", "", "" } );
        map.put("3073", new String[] { "ar", "", "" } );
        map.put("2049", new String[] { "ar", "", "" } );
        map.put("11265", new String[] { "ar", "", "" } );
        map.put("13313", new String[] { "ar", "", "" } );
        map.put("12289", new String[] { "ar", "", "" } );
        map.put("4097", new String[] { "ar", "", "" } );
        map.put("6145", new String[] { "ar", "", "" } );
        map.put("8193", new String[] { "ar", "", "" } );
        map.put("16385", new String[] { "ar", "", "" } );
        map.put("1025", new String[] { "ar", "", "" } );
        map.put("10241", new String[] { "ar", "", "" } );
        map.put("7169", new String[] { "ar", "", "" } );
        map.put("14337", new String[] { "ar", "", "" } );
        map.put("9217", new String[] { "ar", "", "" } );
        map.put("1069", new String[] { "eu", "", "" } );
        map.put("1059", new String[] { "be", "", "" } );
        map.put("1093", new String[] { "bn", "", "" } );
        map.put("1027", new String[] { "ca", "", "" } );
        map.put("3076", new String[] { "zh", "", "" } );
        map.put("5124", new String[] { "zh", "", "" } );
        map.put("2052", new String[] { "zh", "", "" } );
        map.put("4100", new String[] { "zh", "", "" } );
        map.put("1028", new String[] { "zh", "TW", "" } );
        map.put("1050", new String[] { "hr", "", "" } );
        map.put("1029", new String[] { "cs", "", "" } );
        map.put("1030", new String[] { "da", "", "" } );
        map.put("2067", new String[] { "nl", "", "" } );
        map.put("1043", new String[] { "nl", "", "" } );
        map.put("3081", new String[] { "en", "", "" } );
        map.put("10249", new String[] { "en", "", "" } );
        map.put("4105", new String[] { "en", "", "" } );
        map.put("9225", new String[] { "en", "", "" } );
        map.put("6153", new String[] { "en", "", "" } );
        map.put("8201", new String[] { "en", "", "" } );
        map.put("5129", new String[] { "en", "", "" } );
        map.put("13321", new String[] { "en", "", "" } );
        map.put("7177", new String[] { "en", "", "" } );
        map.put("11273", new String[] { "en", "", "" } );
        map.put("2057", new String[] { "en", "", "" } );
        map.put("1033", new String[] { "en", "", "" } );
        map.put("12297", new String[] { "en", "", "" } );
        map.put("1061", new String[] { "et", "", "" } );
        map.put("1035", new String[] { "fi", "", "" } );
        map.put("2060", new String[] { "fr", "", "" } );
        map.put("11276", new String[] { "fr", "", "" } );
        map.put("3084", new String[] { "fr", "", "" } );
        map.put("9228", new String[] { "fr", "", "" } );
        map.put("12300", new String[] { "fr", "", "" } );
        map.put("1036", new String[] { "fr", "", "" } );
        map.put("5132", new String[] { "fr", "", "" } );
        map.put("13324", new String[] { "fr", "", "" } );
        map.put("6156", new String[] { "fr", "", "" } );
        map.put("10252", new String[] { "fr", "", "" } );
        map.put("4108", new String[] { "fr", "", "" } );
        map.put("7180", new String[] { "fr", "", "" } );
        map.put("3079", new String[] { "de", "", "" } );
        map.put("1031", new String[] { "de", "", "" } );
        map.put("5127", new String[] { "de", "", "" } );
        map.put("4103", new String[] { "de", "", "" } );
        map.put("2055", new String[] { "de", "", "" } );
        map.put("1032", new String[] { "el", "", "" } );
        map.put("1037", new String[] { "iw", "", "" } );
        map.put("1081", new String[] { "hi", "", "" } );
        map.put("1038", new String[] { "hu", "", "" } );
        map.put("1039", new String[] { "is", "", "" } );
        map.put("1057", new String[] { "id", "", "" } );
        map.put("1040", new String[] { "it", "", "" } );
        map.put("2064", new String[] { "it", "", "" } );
        map.put("1041", new String[] { "ja", "", "" } );
        map.put("1042", new String[] { "ko", "", "" } );
        map.put("1062", new String[] { "lv", "", "" } );
        map.put("2110", new String[] { "ms", "", "" } );
        map.put("1086", new String[] { "ms", "", "" } );
        map.put("1082", new String[] { "mt", "", "" } );
        map.put("1044", new String[] { "no", "", "" } );
        map.put("2068", new String[] { "nn", "", "" } );
        map.put("1045", new String[] { "pl", "", "" } );
        map.put("1046", new String[] { "pt", "BR", "" } );
        map.put("2070", new String[] { "pt", "", "" } );
        map.put("1048", new String[] { "ro", "", "" } );
        map.put("2072", new String[] { "ro", "", "" } );
        map.put("1049", new String[] { "ru", "", "" } );
        map.put("2073", new String[] { "ru", "", "" } );
        map.put("3098", new String[] { "sr", "", "" } );
        map.put("2074", new String[] { "sr", "", "" } );
        map.put("1051", new String[] { "sk", "", "" } );
        map.put("1060", new String[] { "sl", "", "" } );
        map.put("11274", new String[] { "es", "", "" } );
        map.put("16394", new String[] { "es", "", "" } );
        map.put("13322", new String[] { "es", "", "" } );
        map.put("9226", new String[] { "es", "", "" } );
        map.put("5130", new String[] { "es", "", "" } );
        map.put("7178", new String[] { "es", "", "" } );
        map.put("12298", new String[] { "es", "", "" } );
        map.put("17418", new String[] { "es", "", "" } );
        map.put("4106", new String[] { "es", "", "" } );
        map.put("18442", new String[] { "es", "", "" } );
        map.put("3082", new String[] { "es", "", "" } );
        map.put("2058", new String[] { "es", "", "" } );
        map.put("19466", new String[] { "es", "", "" } );
        map.put("6154", new String[] { "es", "", "" } );
        map.put("15370", new String[] { "es", "", "" } );
        map.put("10250", new String[] { "es", "", "" } );
        map.put("20490", new String[] { "es", "", "" } );
        map.put("1034", new String[] { "es", "", "" } );
        map.put("14346", new String[] { "es", "", "" } );
        map.put("8202", new String[] { "es", "", "" } );
        map.put("1053", new String[] { "sv", "", "" } );
        map.put("2077", new String[] { "sv", "", "" } );
        map.put("1097", new String[] { "ta", "", "" } );
        map.put("1054", new String[] { "th", "", "" } );
        map.put("1055", new String[] { "tr", "", "" } );
        map.put("1058", new String[] { "uk", "", "" } );
        map.put("1056", new String[] { "ur", "", "" } );
        map.put("2115", new String[] { "uz", "", "" } );
        map.put("1091", new String[] { "uz", "", "" } );
        map.put("1066", new String[] { "vi", "", "" } );
        
        return map.get(code);
    }
        
}
