////////////////////////////////////////////////////////////////////////////////
//
//  Licensed to the Apache Software Foundation (ASF) under one or more
//  contributor license agreements.  See the NOTICE file distributed with
//  this work for additional information regarding copyright ownership.
//  The ASF licenses this file to You under the Apache License, Version 2.0
//  (the "License"); you may not use this file except in compliance with
//  the License.  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////

package mx.resources
{

[ExcludeClass]

/**
 *  @private
 *  The APIs of the LocaleSorter class provides the sorting functionality 
 *  of application locales against system preferences.
 */
public class LocaleSorter
{
//    include "../core/Version.as";

	//--------------------------------------------------------------------------
	//
	//  Class methods
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
     *	Sorts a list of locales using the order specified
     *  by the user preferences.
	 * 
	 * 	@param appLocales An Array of locales supported by the application.
     *
	 * 	@param systemPreferences The locale chain of user-preferred locales.
     *
	 * 	@param ultimateFallbackLocale The ultimate fallback locale
     *  that will be used when no locale from systemPreference matches 
	 * 	a locale from application supported locale list.
     *
	 * 	@param addAll When true, adds all the non-matching locales
     *  at the end of the result list preserving the given order.
	 *
	 *	@return A locale chain that matches user preferences order. 
	 */ 
	public static function sortLocalesByPreference(
                                appLocales:Array, systemPreferences:Array,
                                ultimateFallbackLocale:String = null,
                                addAll:Boolean = false):Array
    {
		var result:Array = [];
		
        var hasLocale:Object = {};
		
		var i:int;
        var j:int;
		
		var locales:Array = trimAndNormalize(appLocales);
		var preferenceLocales:Array	= trimAndNormalize(systemPreferences);
		
		addUltimateFallbackLocale(preferenceLocales, ultimateFallbackLocale);
		
		var noLocales:int = locales.length;
		var noPreferenceLocales:int = preferenceLocales.length;
		
		// For better performance, save the locales in a lookup table.
		for (j = 0; j < noLocales; j++)
        {
			hasLocale[locales[j]] = j;
		}
		
		function promote(locale:String):void
        {
			if (typeof hasLocale[locale] != "undefined")
            {
				result.push(appLocales[hasLocale[locale]]);
				delete hasLocale[locale];
			}
		}
		
		var preferenceLocalesID:Vector.<LocaleID> = new Vector.<LocaleID>(noPreferenceLocales);
		for (i = 0; i < noPreferenceLocales; i++)
        {
        	preferenceLocalesID[i] = LocaleID.fromString(preferenceLocales[i]);
        }
		
		var localesID:Vector.<LocaleID> = new Vector.<LocaleID>(noLocales);
		for (i = 0; i < noLocales; i++)
        {
        	localesID[i] = LocaleID.fromString(locales[i]);
        }
		
		for (i = 0; i < noPreferenceLocales; i++)
        {
			var plocale:LocaleID = preferenceLocalesID[i].clone();
			
			// Step 1: Promote the perfect match.
			promote(preferenceLocales[i]);
	
			promote(plocale.toString());
		   
			// Step 2: Promote the parent chain.
			while (plocale.transformToParent())
            {
				promote(plocale.toString());
			}
			
			// Parse it again.
			plocale = preferenceLocalesID[i].clone();
			
            // Step 3: Promote the order of siblings from preferenceLocales.
			for (j = 0; j < noPreferenceLocales; j++)
            {
				if (plocale.isSiblingOf(preferenceLocalesID[j]))
					promote(preferenceLocales[j]);
			}
            				
			// Step 4: Promote all remaining siblings
            // (aka not in preferenceLocales)
			// eg. push en_UK after en_US and en if the user
            // doesn't have en_UK in the preference list
			for (j = 0; j < noLocales; j++)
            {
				if (plocale.isSiblingOf(localesID[j]))
					promote(locales[j]);
			}
		}
		
		if (addAll)
        {
			// Check what locales are not already loaded
            // and push them preserving the order.
			for (j = 0; j < noLocales; j++)
            {
				promote(locales[j]);
			}
		}

		return result;
	} 

	/**
     *  @private
     */
	private static function trimAndNormalize(list:Array):Array
    {
		var resultList:Array = []; 
		
		for (var i:int = 0; i < list.length; i++)
        {
			resultList.push(normalizeLocale(list[i]));
		}
		
        return resultList;
	}
		
	/**
     *  @private
     */
    private static function normalizeLocale(locale:String):String
    {
		return locale.toLowerCase().replace(/-/g, "_");
	}

	/**
     *  @private
     */
	private static function addUltimateFallbackLocale(
                                preferenceLocales:Array,
                                ultimateFallbackLocale:String):void
    {
		if (ultimateFallbackLocale != null && ultimateFallbackLocale != "")
        {
			var locale:String = normalizeLocale(ultimateFallbackLocale);
			
			if (preferenceLocales.indexOf(locale) == -1)
            {
				// Add the locale to the end of the chain.
				preferenceLocales.push(locale);
			}
		}
	}
}

}

////////////////////////////////////////////////////////////////////////////////
//
//  Helper class: LocaleID
//
////////////////////////////////////////////////////////////////////////////////

/**
 *  @private
 *  The APIs of the internal LocaleID class parse a locale string
 *  according to:
 *  RFC 4646: http://www.ietf.org/rfc/rfc4646.txt
 *  RFC 4647: http://www.ietf.org/rfc/rfc4647.txt
 *  IANA Language Subtag Registry:
 *  http://www.iana.org/assignments/language-subtag-registry
 */
class LocaleID
{
	//--------------------------------------------------------------------------
	//
	//  Class constants
	//
	//--------------------------------------------------------------------------

    /**
     *  @private
     */
	public static const STATE_PRIMARY_LANGUAGE:int = 0;
	
    /**
     *  @private
     */
    public static const STATE_EXTENDED_LANGUAGES:int = 1;
	
    /**
     *  @private
     */
    public static const STATE_SCRIPT:int = 2;
	
    /**
     *  @private
     */
    public static const STATE_REGION:int = 3;
	
    /**
     *  @private
     */
    public static const STATE_VARIANTS:int = 4;  
	
    /**
     *  @private
     */
    public static const STATE_EXTENSIONS:int = 5;
	
    /**
     *  @private
     */
    public static const STATE_PRIVATES:int = 6;

	//--------------------------------------------------------------------------
	//
	//  Class methods
	//
	//--------------------------------------------------------------------------

	/**
     *  @private
     */
	private static function appendElements(dest:Array, src:Array):void
	{
		for (var i:uint = 0, argc:uint = src.length; i < argc; i++)
		{
			dest.push(src[i]);
		}
	}
	
	/**
     *  @private
     */
    public static function fromString(str:String):LocaleID
    {
		var localeID:LocaleID = new LocaleID();
	
		var state:int = STATE_PRIMARY_LANGUAGE;
		var subtags:Array = str.replace(/-/g, '_').split('_');
		
		var last_extension:Array;
	
		for (var i:int = 0, l:int = subtags.length; i < l; i++)
        {
			var subtag:String = subtags[i].toLowerCase();
			
			if (state == STATE_PRIMARY_LANGUAGE)
            {
				if (subtag == "x")
                {
					localeID.privateLangs = true; 
                        // not used in our implementation,
                        // but makes the tag private
				}
                else if (subtag == "i")
                {
					localeID.lang += "i-";
                        // and wait the next subtag
                        // to complete the language name
				}
                else
                {
					localeID.lang += subtag;
					state = STATE_EXTENDED_LANGUAGES;
				}
			}
            else
            {
				// looking for:
                // an extended language - 3 chars
				// a script - 4 chars
				// a region - 2-3 chars
				// a variant - alpha with at least 5 chars
                //             or numeric with at least 4 chars
				// an extension/private singleton - 1 char
				
				var subtag_length:int = subtag.length;
				if (subtag_length == 0)
                    continue; // skip zero-lengthed subtags

				var firstChar:String = subtag.charAt(0).toLowerCase();
				
				if (state <= STATE_EXTENDED_LANGUAGES &&
                    subtag_length == 3)
                {
					localeID.extended_langs.push(subtag);
					if (localeID.extended_langs.length == 3)
                    {
                        // Allow a maximum of 3 extended langs.
						state = STATE_SCRIPT;
					}
				}
                else if (state <= STATE_SCRIPT &&
                         subtag_length == 4)
                {
					localeID.script = subtag;
					state = STATE_REGION;
				}
                else if (state <= STATE_REGION &&
                         (subtag_length == 2 || subtag_length == 3))
                {
					localeID.region = subtag;
					state = STATE_VARIANTS;
				}
                else if (state <= STATE_VARIANTS && 
						 ((firstChar >= "a" && firstChar <= "z" &&
                           subtag_length >= 5) || 
						  (firstChar >= "0" && firstChar <= "9" &&
                           subtag_length >= 4)))
                {
					// variant
					localeID.variants.push(subtag);
					state = STATE_VARIANTS;
				}
                else if (state < STATE_PRIVATES &&
                         subtag_length == 1)
                {
                    // singleton
					if (subtag == "x")
                    {
						state = STATE_PRIVATES;
						last_extension = localeID.privates;
					}
                    else 
                    { 
						state = STATE_EXTENSIONS;
						last_extension = localeID.extensions[subtag] || [];
						localeID.extensions[subtag] = last_extension;
					}
				}
                else if (state >= STATE_EXTENSIONS)
                {
					last_extension.push(subtag);
				}
			}
		}
		
        localeID.canonicalize();
		
        return localeID; 
	}
	
	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------

	/**
     *  Constructor.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function LocaleID()
	{
        super();
	}
	
	//--------------------------------------------------------------------------
	//
	//  Variables
	//
	//--------------------------------------------------------------------------

	/**
     *  @private
     */
	private var lang:String = "";
	
	/**
     *  @private
     */
    private var script:String = "";
	
	/**
     *  @private
     */
    private var region:String = "";
	
 	/**
     *  @private
     */
   private var extended_langs:Array = [];
	
 	/**
     *  @private
     */
   private var variants:Array = [];
	
	/**
     *  @private
     */
    private var extensions:Object = {};
	
	/**
     *  @private
     */
    private var privates:Array = [];
	
	/**
     *  @private
     */
    private var privateLangs:Boolean = false; 

	//--------------------------------------------------------------------------
	//
	//  Methods
	//
	//--------------------------------------------------------------------------

    /**
     *  @private
     */
    public function clone():LocaleID {

       var copy:LocaleID = new LocaleID();
       copy.lang = lang;
       copy.script = script;
       copy.region = region;
       copy.extended_langs = extended_langs.concat();
       copy.variants = variants.concat();
       copy.extensions = {};
       for (var i:String in extensions)
       {
           if (extensions.hasOwnProperty(i))
           {
           	    copy.extensions[i] = extensions[i].concat();
           }
       }
           
        copy.privates = privates.concat();
        copy.privateLangs = privateLangs;
    
        return copy;
    }

	/**
     *  @private
     */
	public function canonicalize():void
    {
		for (var i:String in extensions)
        {
			if (extensions.hasOwnProperty(i))
            {
				// Also clear zero length extensions.
				if (extensions[i].length == 0)
                    delete extensions[i];
				else
                    extensions[i] = extensions[i].sort();
			}
		}

		extended_langs = extended_langs.sort();
		variants = variants.sort();
		privates = privates.sort();
		
        if (script == "")
			script = LocaleRegistry.getScriptByLang(lang);
		
        // Still no script, check the region.
		if (script == "" && region != "")
			script = LocaleRegistry.getScriptByLangAndRegion(lang, region);
		
		if (region == "" && script != "")
        {
			region = LocaleRegistry.getDefaultRegionForLangAndScript(
                                            lang, script);
		}
	}

	/**
     *  @private
     */
	public function toString():String
    {
		var stack:Array = [ lang ];
		
        appendElements(stack, extended_langs);
		
        if (script != "")
            stack.push(script);
		
        if (region != "")
            stack.push(region);
		
        appendElements(stack, variants);
		
        for (var i:String in extensions)
        {
			if (extensions.hasOwnProperty(i))
            {
				stack.push(i);
				appendElements(stack, extensions[i]);
			}
		}
		
        if (privates.length > 0)
        {
			stack.push("x");
			appendElements(stack, privates);
		}

		return stack.join("_");
	} 
	
	/**
     *  @private
     */
	public function equals(locale:LocaleID):Boolean
    {
		return toString() == locale.toString();
	}
	
	/**
     *  @private
     */
	public function isSiblingOf(other:LocaleID):Boolean
    {
		return lang == other.lang && script == other.script;
	}
	
	/**
     *  @private
     */
    public function transformToParent():Boolean
    {
		if (privates.length > 0)
        {
			privates.splice(privates.length - 1, 1);
			return true;
		}
		
		var lastExtensionName:String = null;
		for (var i:String in extensions)
        {
			if (extensions.hasOwnProperty(i))
				lastExtensionName = i;
		}

		if (lastExtensionName)
        {
			var lastExtension:Array = extensions[lastExtensionName];
			if (lastExtension.length == 1)
            {
				delete extensions[lastExtensionName];
				return true;
			}
			lastExtension.splice(lastExtension.length - 1, 1);
			return true;
		}
		
		if (variants.length > 0)
        {
			variants.splice(variants.length - 1, 1);
			return true;
		}

		if (script != "")
        {
			// Check if we can suppress the script.
			if (LocaleRegistry.getScriptByLang(lang) != "")
            {
				script = "";
				return true;
			}
            else if (region == "")
            {
				// Maybe the default region can suppress the script.
				var defaultRegion:String =
                    LocaleRegistry.getDefaultRegionForLangAndScript(
                                        lang, script);
				if (defaultRegion != "")
                {
					region = defaultRegion;
					script = "";
					return true;
				}
			}
		}
		
		if (region != "")
        {
			if (!(script == "" && LocaleRegistry.getScriptByLang(lang) == ""))
            {
				region = "";
				return true;
			}
		}
		
		if (extended_langs.length > 0)
        {
			extended_langs.splice(extended_langs.length - 1, 1);
			return true;
		}
		
		return false;
	}
}

////////////////////////////////////////////////////////////////////////////////
//
//  Helper class: LocaleRegistry
//
////////////////////////////////////////////////////////////////////////////////

/**
 *  @private
 */
class LocaleRegistry
{
	//--------------------------------------------------------------------------
	//
	//  Class constants
	//
	//--------------------------------------------------------------------------

	/**
     *  @private
     *  A list of codes representing writing systems, in arbitrary order.
     *  For example, "latn" represents the Latin alphabet, used for
     *  writing languages such as English, French, Indonesian, and Swahili,
     *  and "arab" represents the Arabic script, used for writing
     *  Arabic, Persian, Pashto, and Urdu.
     */
	private static const SCRIPTS:Array =
    [
        "",     // 0
        "latn", // 1    Latin
        "ethi", // 2    Ethiopian
        "arab", // 3    Arabic
        "beng", // 4    Bengali
        "cyrl", // 5    Cyrillic
        "thaa", // 6    Thaana 
        "tibt", // 7    Tibetan
        "grek", // 8    Greek
        "gujr", // 9    Gujarati
        "hebr", // 10   Hebrew
        "deva", // 11   Devanagari
        "armn", // 12   Armenian
        "jpan", // 13   Japanese
        "geor", // 14   Georgian
        "khmr", // 15   Khmer
        "knda", // 16   Kannada
        "kore", // 17   Korean
        "laoo", // 18   Lao
        "mlym", // 19   Malayalam
        "mymr", // 20   Burmese
        "orya", // 21   Oriya
        "guru", // 22   Punjabi
        "sinh", // 23   Sinhalese
        "taml", // 24   Tamil
        "telu", // 25   Telugu
        "thai", // 26   Thai
        "nkoo", // 27   N'Ko
        "blis", // 28   Bliss
        "hans", // 29   Simplified Chinese
        "hant", // 30   Traditional Chinese
        "mong", // 31   Mongolian
        "syrc"  // 32   Syriac
    ];
	
	/**
     *  @private
     *  The inverse of the SCRIPT Array.
     *  Maps a script code (like "jpan" for Japanese)
     *  to its index in the SCRIPT Array.
     */
    private static const SCRIPT_BY_ID:Object =
    {
        latn: 1,
        ethi: 2,
        arab: 3,
        beng: 4,
        cyrl: 5,
        thaa: 6,
        tibt: 7,
        grek: 8,
        gujr: 9,
        hebr: 10,
        deva: 11,
        armn: 12,
        jpan: 13,
        geor: 14,
        khmr: 15,
        knda: 16,
        kore: 17,
        laoo: 18,
        mlym: 19,
        mymr: 20,
        orya: 21,
        guru: 22,
        sinh: 23,
        taml: 24,
        telu: 25,
        thai: 26,
        nkoo: 27,
        blis: 28,
        hans: 29,
        hant: 30,
        mong: 31,
        syrc: 32
    };
	
	/**
     *  @private
     *  This table maps a language and a script to the most
     *  prominent region where that combination is used.
     *
     *  Note: "is" must be quoted because it is a reserved word.
     */
    private static const DEFAULT_REGION_BY_LANG_AND_SCRIPT:Object =
    {
        bg: { 5: "bg" },            // Bulgarian / Cyrillic -> Bulgaria
        ca: { 1: "es" },            // Catalan / Latin -> Spain
        zh: { 30: "tw", 29: "cn" }, // Chinese / Traditional Chinese -> Taiwan
                                    // Chinese / Simplified Chinese -> China
        cs: { 1: "cz" },            // Czech / Latin -> Czech Republic
        da: { 1: "dk" },            // Danish / Latin -> Denmark
        de: { 1: "de" },            // German / Latin -> Germany
        el: { 8: "gr" },            // Greek / Greek -> Greece
        en: { 1: "us" },            // English / Latin -> United States
        es: { 1: "es" },            // Spanish / Latin -> Spain
        fi: { 1: "fi" },            // Finnish / Latin -> Finland
        fr: { 1: "fr" },            // French / Latin -> France         
        he: { 10: "il" },           // Hebrew / Hebrew -> Israel
        hu: { 1: "hu" },            // Hungarian / Latin -> Hungary
        "is": { 1: "is" },          // Icelandic / Latin -> Iceland
        it: {  1: "it" },           // Italian / Latin -> Italy
        ja: { 13: "jp" },           // Japanese / Japanese -> Japan
        ko: { 17: "kr" },           // Korean / Korean -> Korea
        nl: { 1: "nl" },            // Dutch / Latin -> Netherlands
        nb: { 1: "no" },            // Norwegian Bokmaal / Latin -> Norway
        pl: { 1: "pl" },            // Polish / Latin -> Poland
        pt: { 1: "br" },            // Portuguese / Latin -> Brazil
        ro: { 1: "ro" },            // Romanian / Latin -> Romania
        ru: { 5: "ru" },            // Russian / Cyrillic -> Russia
        hr: { 1: "hr" },            // Croatian / Latin -> Croatia
        sk: { 1: "sk" },            // Slovak / Latin -> Slovakia
        sq: { 1: "al" },            // Albanian / Latin -> Albania
        sv: { 1: "se" },            // Swedish / Latin -> Sweden
        th: { 26: "th" },           // Thai / Thai -> Thailand
        tr: { 1: "tr" },            // Turkish / Latin -> Turkey
        ur: { 3: "pk" },            // Urdu / Arabic -> Pakistan
        id: { 1: "id" },            // Indonesian / Latin -> Indonesia
        uk: { 5: "ua" },            // Ukrainian / Cyrillic -> Ukraine
        be: { 5: "by" },            // Byelorussian / Cyrillic -> Belarus
        sl: { 1: "si" },            // Slovenian / Latin -> Slovenia
        et: { 1: "ee" },            // Estonian / Latin -> Estonia
        lv: { 1: "lv" },            // Latvian / Latin -> Latvia
        lt: { 1: "lt" },            // Lithuanian / Latin -> Lithuania
        fa: { 3: "ir" },            // Persian / Arabic -> Iran
        vi: { 1: "vn" },            // Vietnamese / Latin -> Vietnam
        hy: { 12: "am"},            // Armenian / Armenian -> Armenia
        az: { 1: "az", 5: "az" },   // Azerbaijani / Latin -> Azerbaijan
                                    // Azerbaijani / Cyrillic -> Azerbaijan
        eu: { 1: "es" },            // Basque / Latin -> Spain
        mk: { 5: "mk" },            // Macedonian / Cyrillic -> Macedonia
        af: { 1: "za" },            // Afrikaans / Latin -> South Africa
        ka: { 14: "ge" },           // Georgian / Georgian -> Georgia
        fo: { 1: "fo" },            // Faeroese / Latin -> Faroe Islands
        hi: { 11: "in" },           // Hindi / Devanagari -> India 
        ms: { 1: "my" },            // Malay / Latin -> Malaysia
        kk: { 5: "kz" },            // Kazakh / Cyrillic -> Kazakhstan
        ky: { 5: "kg" },            // Kirghiz / Cyrillic -> Kyrgyzstan
        sw: { 1: "ke" },            // Swahili / Latin -> Kenya
        uz: { 1: "uz", 5: "uz" },   // Uzbek / Latin -> Uzbekistan
                                    // Uzbek / Cyrillic -> Uzbekistan
        tt: { 5: "ru" },            // Tatar / Cyrillic -> Russia
        pa: { 22: "in" },           // Punjabi / Punjabi -> India
        gu: { 9: "in" },            // Gujarati / Gujarati -> India
        ta: { 24: "in" },           // Tamil / Tamil -> India
        te: { 25: "in" },           // Telugu / Telugu -> India
        kn: { 16: "in" },           // Kannada / Kannada -> India
        mr: { 11: "in" },           // Marathi / Devanagari -> India
        sa: { 11: "in" },           // Sanskrit / Devanagari -> India
        mn: { 5: "mn" },            // Mongolian / Cyrillic -> Mongolia
        gl: { 1: "es" },            // Galician / Latin -> Spain
        kok: { 11: "in" },          // Konkani / Devanagari -> India
        syr: { 32: "sy" },          // Syriac / Syriac -> Syria
        dv: { 6: "mv" },            // Dhivehi / Thaana -> Maldives
        nn: { 1: "no" },            // Norwegian Nynorsk / Latin -> Norway
        sr: { 1: "cs", 5: "cs" },   // Serbian / Latin -> Serbia
                                    // Serbian / Cyrillic -> Serbia
        cy: { 1: "gb" },            // Welsh / Latin -> United Kingdom
        mi: { 1: "nz" },            // Maori / Latin -> New Zealand
        mt: { 1: "mt" },            // Maltese / Latin -> Malta
        quz: { 1: "bo" },           // Quechua / Latin -> Bolivia
        tn: { 1: "za" },            // Tswana / Latin -> South Africa
        xh: { 1: "za" },            // Xhosa / Latin -> South Africa
        zu: { 1: "za" },            // Zulu / Latin -> South Africa
        nso: { 1: "za" },           // Northern Sotho / Latin -> South Africa
        se: { 1: "no" },            // Northern Saami / Latin -> Norway
        smj: { 1: "no" },           // Lule Saami / Latin -> Norway
        sma: { 1: "no" },           // Southern Saami / Latin -> Norway
        sms: { 1: "fi" },           // Skolt Saami / Latin -> Finland
        smn: { 1: "fi" },           // Inari Saami / Latin -> Finland
        bs: { 1: "ba" }             // Bosnia / Latin -> Bosnia
    };
	
	/**
     *  @private
     *  This table maps a language to a script.
     *  It was derived from the entries at
     *  http://www.iana.org/assignments/language-subtag-registry
     *  which have a Suppress-Script property.
     *
     *  Note: "as", "in", and "is" must be quoted
     *  because they are reserved words.
     */
    private static const SCRIPT_ID_BY_LANG:Object =
    {
        ab: 5,      // Abkhazian -> Cyrillic
        af: 1,      // Afrikaans -> Latin
        am: 2,      // Amharic -> Ethiopian
        ar: 3,      // Arabic -> Arabic
        "as": 4,    // Assamese -> Bengali
        ay: 1,      // Aymara -> Latin
        be: 5,      // Belarusian -> Cyrillic
        bg: 5,      // Bulgarian -> Cyrillic
        bn: 4,      // Bengali -> Bengali
        bs: 1,      // Bosnian -> Latin
        ca: 1,      // Catalan / Valencian -> Latin
        ch: 1,      // Chamorro -> Latin
        cs: 1,      // Czech -> Latin
        cy: 1,      // Welsh -> Latin
        da: 1,      // Danish -> Latin
        de: 1,      // German -> Latin
        dv: 6,      // Dhivehi / Maldivian -> Thaana
        dz: 7,      // Dzongkha -> Tibetan
        el: 8,      // Modern Greek -> Greek
        en: 1,      // English -> Latin
        eo: 1,      // Esperanto -> Latin
        es: 1,      // Spanish / Castilian -> Latin
        et: 1,      // Estonian -> Latin
        eu: 1,      // Basque -> Latin
        fa: 3,      // Persian -> Arabic
        fi: 1,      // Finnish -> Latin
        fj: 1,      // Fijian -> Latin
        fo: 1,      // Faroese -> Latin
        fr: 1,      // French -> Latin
        frr: 1,     // Northern Frisian -> Latin
        fy: 1,      // Western Frisian -> Latin
        ga: 1,      // Irish -> Latin
        gl: 1,      // Galician -> Latin
        gn: 1,      // Guarani -> Latin
        gu: 9,      // Gujarati -> Gujarati
        gv: 1,      // Manx -> Latin
        he: 10,     // Hebrew -> Hebrew
        hi: 11,     // Hindi -> Devanagari
        hr: 1,      // Croatian -> Latin
        ht: 1,      // Haitian Creole -> Latin
        hu: 1,      // Hungarian -> Latin
        hy: 12,     // Armenian -> Armenian
        id: 1,      // Indonesian -> Latin
        "in": 1,    // Indonesian -> Latin
        "is": 1,    // Icelandic -> Latin
        it: 1,      // Italian -> Latin
        iw: 10,     // Hebrew -> Hebrew
        ja: 13,     // Japanese -> Japanese
        ka: 14,     // Georgian -> Georgian
        kk: 5,      // Kazakh -> Cyrillic
        kl: 1,      // Kalaallisut / Greenlandic -> Latin
        km: 15,     // Central Khmer -> Khmer
        kn: 16,     // Kannada -> Kannada
        ko: 17,     // Korean -> Korean
        la: 1,      // Latin -> Latin
        lb: 1,      // Luxembourgish -> Latin
        ln: 1,      // Lingala -> Latin
        lo: 18,     // Lao -> Lao
        lt: 1,      // Lithuanian -> Latin
        lv: 1,      // Latvian -> Latin
        mg: 1,      // Malagasy -> Latin
        mh: 1,      // Marshallese -> Latin
        mk: 5,      // Macedonian -> Cyrillic
        ml: 19,     // Malayalam -> Malayalam
        mo: 1,      // Moldavian -> Latin
        mr: 11,     // Marathi -> Devanagari
        ms: 1,      // Malay -> Latin
        mt: 1,      // Maltese -> Latin
        my: 20,     // Burmese -> Burmese
        na: 1,      // Nauru -> Latin
        nb: 1,      // Norwegian Bokmaal -> Latin
        nd: 1,      // North Ndebele -> Latin
        ne: 11,     // Nepali -> Devanagari
        nl: 1,      // Dutch / Flemish -> Latin
        nn: 1,      // Norwegian Nynorsk -> Latin
        no: 1,      // Norwegian -> Latin
        nr: 1,      // South Ndebele -> Latin
        ny: 1,      // Chichewa / Chewa / Nyanja -> Latin
        om: 1,      // Oromo -> Latin
        or: 21,     // Oriya -> Oriya
        pa: 22,     // Punjabi -> Punjabi
        pl: 1,      // Polish -> Latin
        ps: 3,      // Pashto -> Arabic
        pt: 1,      // Portuguese -> Latin
        qu: 1,      // Quechua -> Latin
        rn: 1,      // Rundi -> Latin
        ro: 1,      // Romanian -> Latin
        ru: 5,      // Russian -> Cyrillic
        rw: 1,      // Kinyarwanda -> Latin
        sg: 1,      // Sango -> Latin
        si: 23,     // Sinhalese -> Sinhalese
        sk: 1,      // Slovak -> Latin
        sl: 1,      // Slovenian -> Latin
        sm: 1,      // Samoan -> Latin
        so: 1,      // Somali -> Latin
        sq: 1,      // Albanian -> Latin
        ss: 1,      // Swati -> Latin
        st: 1,      // Southern Sotho -> Latin
        sv: 1,      // Swedish -> Latin
        sw: 1,      // Swahili -> Latin
        ta: 24,     // Tamil -> Tamil
        te: 25,     // Telugu -> Telugu
        th: 26,     // Thai -> Thai
        ti: 2,      // Tigrinya -> Ethiopian
        tl: 1,      // Tagalog -> Latin
        tn: 1,      // Tswana -> Latin
        to: 1,      // Tonga -> Latin
        tr: 1,      // Turkish -> Latin
        ts: 1,      // Tsonga -> Latin
        uk: 5,      // Ukrainian -> Cyrillic
        ur: 3,      // Urdu -> Arabic
        ve: 1,      // Venda -> Latin
        vi: 1,      // Vietnamese -> Latin
        wo: 1,      // Wolof -> Latin
        xh: 1,      // Xhosa -> Latin
        yi: 10,     // Yiddish -> Hebrew
        zu: 1,      // Zulu -> Latin
        cpe: 1,     // Creoles and pidgins -> Latin
        dsb: 1,     // Lower Sorbian -> Latin
        frs: 1,     // Eastern Frisian -> Latin
        gsw: 1,     // Swiss German / Alemaanic / Alsatian -> Latin
        hsb: 1,     // Upper Sorbian -> Latin
        kok: 11,    // Konkana -> Devanagari
        mai: 11,    // Maithili -> Devanagari
        men: 1,     // Mende -> Latin
        nds: 1,     // Low German -> Latin
        niu: 1,     // Niuean -> Latin
        nqo: 27,    // N'Ko -> N'Ko
        nso: 1,     // Northern Sotho / Pedi / Sepedi -> Latin
        son: 1,     // Songhai -> Latin
        tem: 1,     // Timne -> Latin
        tkl: 1,     // Tokelau -> Latin
        tmh: 1,     // Tamashek -> Latin
        tpi: 1,     // Tok Pisin -> Latin
        tvl: 1,     // Tuvalu -> Latin
        zbl: 28     // Bliss -> Bliss
    };
	
	/**
     *  @private
     *  This table maps a language-as-spoken-in-a-region
     *  to the script used to write it.
     *
     *  Chinese in China -> Simplified Chinese
     *  Chinese in Singapore -> Simplified Chinese
     *  Chinese in Taiwan -> Traditional Chinese
     *  Chinese in Hong Kong -> Traditional Chinese 
     *  Chinese in Macao -> Traditional Chinese 
     *  Mongolian in China -> Mongolian
     *  Mongolian in Singapore -> Cyrillic
     *  Punjabi in Pakistan -> Arabic
     *  Punjabi in India -> Punjabi
     *  Hausa in Ghana -> Latin
     *  Hausa in Niger -> Latin
     */
    private static const SCRIPT_ID_BY_LANG_AND_REGION:Object =
    {
        zh: { cn: 29, sg: 29, tw: 30, hk: 30, mo: 30 },
        mn: { cn: 31, sg: 5 },
        pa: { pk: 3, "in": 22 }, // "in" is reserved word
        ha: { gh: 1, ne: 1 }
    };

	//--------------------------------------------------------------------------
	//
	//  Class methods
	//
	//--------------------------------------------------------------------------

	/**
     *  @private
     *  Given a language and a region, returns the script system
     *  used to write the language there.
     *
     *  Examples:
     *  lang zh (Chinese), region cn (China) -> hans (Simplified Chinese)
     *  lang zh (Chinese), region tw (Taiwan) -> hast (Traditional Chinese)
     */
    public static function getScriptByLangAndRegion(
                                lang:String, region:String):String
    {
		var langRegions:Object = SCRIPT_ID_BY_LANG_AND_REGION[lang];
		if (langRegions == null)
            return "";
		
        var scriptID:Object = langRegions[region];
		if (scriptID == null)
            return "";

        return SCRIPTS[int(scriptID)].toLowerCase();
	}
	
	/**
     *  @private
     *  Given a language, returns the script generally used to write it.
     *
     *  Examples:
     *  lang bg (Bulgarian) -> cyrl (Cyrillic)
     */
	public static function getScriptByLang(lang:String):String
    {
		var scriptID:Object = SCRIPT_ID_BY_LANG[lang];
        if (scriptID == null)
		    return "";

		return SCRIPTS[int(scriptID)].toLowerCase();
	}
	
	/**
     *  @private
     *  Given a language and a script used for writing it,
     *  returns the most prominent region where that combination is used.
     *
     *  Examples:
     */
	public static function getDefaultRegionForLangAndScript(
                                lang:String, script:String):String
    {
		var langObj:Object = DEFAULT_REGION_BY_LANG_AND_SCRIPT[lang];
		var scriptID:Object = SCRIPT_BY_ID[script];
		if (langObj == null || scriptID == null)
            return "";
		
		return langObj[int(scriptID)] || "";
	} 
}
