
package railo.runtime.i18n;

import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import railo.runtime.exp.ExpressionException;
import railo.runtime.type.util.ListUtil;


/**
 * Factory to create Locales by CFML rules
 */
public final class LocaleFactory {
	//private static Pattern localePattern = Pattern.compile("^\\s*([^\\s\\(]+)\\s*(\\(\\s*([^\\s\\)]+)\\s*\\))?\\s*$");
	private static Pattern localePattern = Pattern.compile("^\\s*([^\\(]+)\\s*(\\(\\s*([^\\)]+)\\s*\\))?\\s*$");
	private static Pattern localePattern2 = Pattern.compile("^([a-z]{2})_([a-z]{2,3})$");
	private static Pattern localePattern3 = Pattern.compile("^([a-z]{2})_([a-z]{2,3})_([a-z]{2,})$");
	
	private static Map<String,Locale> locales=new LinkedHashMap<String,Locale>();
	private static Map<String,Locale> localeAlias=new LinkedHashMap<String,Locale>();
	
	private static String list;
	static {
		Locale[] ls = Locale.getAvailableLocales();
		
		
		String key;
		StringBuffer sb=new StringBuffer();
		for(int i=0;i<ls.length;i++) {
			key=ls[i].getDisplayName(Locale.US).toLowerCase();
			locales.put(key, ls[i]);
			if(key.indexOf(',')!=-1){
				key=ls[i].toString();
				//print.ln(key);
				
			}
			if(i>0)sb.append(",");
			sb.append(key);
		}
		list=sb.toString();
		
		
		setLocalAlias("albanian (albania)", LocaleConstant.ALBANIAN_ALBANIA);

		setLocalAlias("arabic (algeria)", LocaleConstant.ARABIC_ALGERIA);
		setLocalAlias("arabic (bahrain)", LocaleConstant.ARABIC_BAHRAIN);
		setLocalAlias("arabic (egypt)", LocaleConstant.ARABIC_EGYPT);
		setLocalAlias("arabic (iraq)", LocaleConstant.ARABIC_IRAQ);
		setLocalAlias("arabic (jordan)", LocaleConstant.ARABIC_JORDAN);
		setLocalAlias("arabic (kuwait)", LocaleConstant.ARABIC_KUWAIT);
		setLocalAlias("arabic (lebanon)", LocaleConstant.ARABIC_LEBANON);
		setLocalAlias("arabic (libya)", LocaleConstant.ARABIC_LIBYA);
		setLocalAlias("arabic (morocco)", LocaleConstant.ARABIC_MAROCCO);
		setLocalAlias("arabic (oman)", LocaleConstant.ARABIC_OMAN);
		setLocalAlias("arabic (qatar)", LocaleConstant.ARABIC_QATAR);
		setLocalAlias("arabic (saudi arabia)", LocaleConstant.ARABIC_SAUDI_ARABIA);
		setLocalAlias("arabic (sudan)", LocaleConstant.ARABIC_SUDAN);
		setLocalAlias("arabic (syria)", LocaleConstant.ARABIC_SYRIA);
		setLocalAlias("arabic (tunisia)", LocaleConstant.ARABIC_TUNISIA);
		setLocalAlias("arabic (united arab emirates)", LocaleConstant.ARABIC_UNITED_ARAB_EMIRATES);
		setLocalAlias("arabic (yemen)", LocaleConstant.ARABIC_YEMEN);
			
		setLocalAlias("chinese (china)", Locale.CHINA);
		setLocalAlias("chinese (hong kong)",LocaleConstant.CHINESE_HONG_KONG);
		setLocalAlias("chinese (singapore)",LocaleConstant.CHINESE_SINGAPORE);
	    setLocalAlias("chinese (taiwan)",LocaleConstant.CHINESE_TAIWAN);
	    setLocalAlias("dutch (belgian)",LocaleConstant.DUTCH_BELGIUM);
	    setLocalAlias("dutch (belgium)",LocaleConstant.DUTCH_BELGIUM);
	    setLocalAlias("dutch (standard)",LocaleConstant.DUTCH_NETHERLANDS);
	    setLocalAlias("english (australian)",LocaleConstant.ENGLISH_AUSTRALIA);
	    setLocalAlias("english (australia)",LocaleConstant.ENGLISH_AUSTRALIA);
	    setLocalAlias("english (canadian)",LocaleConstant.ENGLISH_CANADA);
	    setLocalAlias("english (canada)",LocaleConstant.ENGLISH_CANADA);
	    setLocalAlias("english (new zealand)",LocaleConstant.ENGLISH_NEW_ZEALAND);
	    setLocalAlias("english (uk)",LocaleConstant.ENGLISH_UNITED_KINDOM);
	    setLocalAlias("english (united kingdom)",LocaleConstant.ENGLISH_UNITED_KINDOM);
	    setLocalAlias("english (gb)",LocaleConstant.ENGLISH_UNITED_KINDOM);
	    setLocalAlias("english (great britan)",LocaleConstant.ENGLISH_UNITED_KINDOM);
	    setLocalAlias("english (us)",LocaleConstant.ENGLISH_UNITED_STATES);
	    setLocalAlias("english (united states)",LocaleConstant.ENGLISH_UNITED_STATES);
	    setLocalAlias("english (united states of america)",LocaleConstant.ENGLISH_UNITED_STATES);
	    setLocalAlias("english (usa)",LocaleConstant.ENGLISH_UNITED_STATES);
	    setLocalAlias("french (belgium)",new Locale("fr","BE"));
	    setLocalAlias("french (belgian)",new Locale("fr","BE"));
		setLocalAlias("french (canadian)",new Locale("fr","CA"));
		setLocalAlias("french (canadia)",new Locale("fr","CA"));
	    setLocalAlias("french (standard)",new Locale("fr","FRA"));
	    setLocalAlias("french (swiss)",new Locale("fr","CH"));
	    setLocalAlias("german (austrian)",new Locale("de","AT"));
	    setLocalAlias("german (austria)",new Locale("de","AT"));
	    setLocalAlias("german (standard)",new Locale("de","DE"));
	    setLocalAlias("german (swiss)",new Locale("de","CH"));
	    setLocalAlias("italian (standard)",new Locale("it","IT"));
	    setLocalAlias("italian (swiss)",new Locale("it","CH"));
	    setLocalAlias("japanese",new Locale("ja","JP"));  
	    setLocalAlias("korean",Locale.KOREAN);
	    setLocalAlias("norwegian (bokmal)",new Locale("no","NO"));
	    setLocalAlias("norwegian (nynorsk)",new Locale("no","NO"));
	    setLocalAlias("portuguese (brazilian)",LocaleConstant.PORTUGUESE_BRASIL);
	    setLocalAlias("portuguese (brazil)",LocaleConstant.PORTUGUESE_BRASIL);
		setLocalAlias("portuguese (standard)",LocaleConstant.PORTUGUESE_PORTUGAL);
	    setLocalAlias("rhaeto-romance (swiss)",new Locale("rm","CH"));
	    locales.put("rhaeto-romance (swiss)",new Locale("rm","CH"));
	    setLocalAlias("spanish (modern)",new Locale("es","ES"));
	    setLocalAlias("spanish (standard)",new Locale("es","ES"));
	    setLocalAlias("swedish",new Locale("sv","SE"));
	}
	
	private LocaleFactory(){}
	
    private static void setLocalAlias(String name, Locale locale) {
    	if(!localeAlias.containsKey(name))localeAlias.put(name, locale);
	}

	/**
     * @param strLocale
     * @param defaultValue 
     * @return return locale match to String
     */
    public static Locale getLocale(String strLocale, Locale defaultValue) {
        try {
            return getLocale(strLocale);
        } catch (ExpressionException e) {
            return defaultValue;
        }
    }
	
	
	/**
	 * @param strLocale
	 * @return return locale match to String
	 * @throws ExpressionException
	 */
	public static Locale getLocale(String strLocale) throws ExpressionException {
		String strLocaleLC = strLocale.toLowerCase().trim();
		Locale l=locales.get(strLocaleLC);
		if(l!=null) return l;
		
		l=localeAlias.get(strLocaleLC);
		if(l!=null) return l;

		Matcher matcher = localePattern2.matcher(strLocaleLC);
		if(matcher.find()) {
			int len=matcher.groupCount();
			if(len==2) {
				String lang=matcher.group(1).trim();
				String country=matcher.group(2).trim();
				Locale locale=new Locale(lang,country);
				
				try {
					locale.getISO3Language();
					setLocalAlias(strLocaleLC, locale);
					return locale;
				}
				catch(Exception e) {}
			}
		}
		
		matcher = localePattern3.matcher(strLocaleLC);
		if(matcher.find()) {
			int len=matcher.groupCount();
			if(len==3) {
				String lang=matcher.group(1).trim();
				String country=matcher.group(2).trim();
				String variant=matcher.group(3).trim();
				Locale locale=new Locale(lang,country,variant);
				
				try {
					locale.getISO3Language();
					setLocalAlias(strLocaleLC, locale);
					return locale;
				}
				catch(Exception e) {}
			}
		}
			
		
		matcher=localePattern.matcher(strLocaleLC);
		if(matcher.find()) {
			int len=matcher.groupCount();

			if(len==3) {
				
				String lang=matcher.group(1).trim();
				String country=matcher.group(3);
				if(country!=null)country=country.trim();
				 Object objLocale=null;
				 
				if(country!=null) objLocale=locales.get(lang.toLowerCase()+" ("+(country.toLowerCase())+")");
				else objLocale=locales.get(lang.toLowerCase());
				if(objLocale!=null)return (Locale)objLocale;
				
				Locale locale;
				if(country!=null)locale=new Locale(lang.toUpperCase(),country.toLowerCase());
				else locale=new Locale(lang);
				
				try {
					locale.getISO3Language();
				}
				catch(Exception e) {
					if(strLocale.indexOf('-')!=-1) return getLocale(strLocale.replace('-', '_'));
					throw new ExpressionException("unsupported Locale ["+strLocale+"]","supported Locales are:"+getSupportedLocalesAsString());
				}
				setLocalAlias(strLocaleLC, locale);
				return locale;

			}
		}
		

		throw new ExpressionException("can't cast value ("+strLocale+") to a Locale","supported Locales are:"+getSupportedLocalesAsString());
	}
	

	private static String getSupportedLocalesAsString() {
		//StringBuffer sb=new StringBuffer();
	    // TODO chnge from ArryObject to string
		String[] arr = locales.keySet().toArray(new String[locales.size()]);
		Arrays.sort(arr);
		return ListUtil.arrayToList(arr,",");
		
	}

	/**
	 * @param locale
	 * @return cast a Locale to a String 
	 */
	public static String toString(Locale locale) {
		String lang=locale.getLanguage();
		String country=locale.getCountry();
		
		synchronized(localeAlias){
			Iterator<Entry<String, Locale>> it = localeAlias.entrySet().iterator();
			Map.Entry<String, Locale> entry;
			while(it.hasNext()) {
				entry= it.next();
				//Object qkey=it.next();
				Locale curr=entry.getValue();
				if(lang.equals(curr.getLanguage()) && country.equals(curr.getCountry())) {
					return entry.getKey().toString();
				}
			}
		}
		return locale.getDisplayName(Locale.ENGLISH);
	}
	
    /**
     * @return Returns the locales.
     */
    public static Map getLocales() {
        return locales;
    }
    public static String getLocaleList() {
        return list;
    }
}