/*
 * indic_support.js
 *
 * This file is part of indic_web_input-1.0 package
 *
 * This script transliterates the characters typed into indic characters
 * according to the language and keyboard layout selected.
 * To add support for new language, please read the file HACKING.
 *
 * Copyright (C) 2006 Khader Abbeb N <khader@users.sourceforge.net>
 * Copyright (C) 2006 Srinivas Raghavan <raghavan@servelots.com>
 * Copyright (C) 2006 Sunil Mohan Adapa <sunil@atc.tcs.com>
 * Copyright (C) 2006 Surekha Sastry <surekha@servelots.com>
 * Copyright (C) 2006 Vigneswaran R <vignesh@atc.tcs.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, 
 * Boston, MA 02111-1307, USA. 
 *
 */


//The following lang_ arrays contain not more than 20 entries per line
//(for convenience only)

//lang_vowel: key map for vowel characters for different languages - 
//contains vowels, numerals, other symbols etc.,
//follow standards like rts,itrans etc
var lang_vowel = 
    new Array(
/*00*/  "a aa A a' i ii ee ia i' I u uu oo U ua u' e E ea ae " +
        "e' ai o O oe oa o' au ou aq q Q sri ^",

/*01*/  "a aa A a' i ii ee ia i' I u uu oo U ua u' r'u Ru e E " +
        "ea ae e' ai o O oe oa o' au ou ~l ~L M @h @H @M q @2 oM " +
        "OM | || ^",

/*02*/  "a aa A a' i ii ee ia i' I u uu oo U ua u' R r' r'u Ru " +
        "e E ea ae e' ai o O oe oa o' au ou ~l ~L M @h @H @M @2 i" +
        "| || ^",

/*03*/  "` ~ ) _ + q Q w W e E r R t T y Y u U i " +
        "I o O p P [ { ] } a A s S d D f F g G h " +
        "H j J k K l L \\ | z Z x X c C v V b B n " +
        "N m M < . > /",

/*04*/  "a A b B c C d D e E f F g G h H i I j J " +
        "k K l L m M n N o O p P q Q r R s S t T " +
        "u U v V w W x X y Y z Z [ ] { } \" ' ; : " +
        "` < > / \\ ~ | ? ! @ # $ _ + = & ^ * %",

/*05*/  "a aA ai aI au aU ae aE aY ao aO aOU aq A i I u U e E " +
        "Y o O OU k g f c F t N d n p b m y r l w " +
        "v l' L R n' s S h j _",

/*06*/  "a aA ai aI au aU aeV ae aE aEY aoV ao aO aOY aq aQ A i I u " +
        "U eV e E EY oV o O OY M H z q Q k K g G f c " +
        "C j J F t T d D N w W x X n p P b B m y " +
        "r l v S s R h lY k_R w_r j_F S_r _ ^",

/*07*/  "a A b B c C d D e E f F g G h H i I j J " +
        "k K l L m M n N o O p P q Q r R s S t T " +
        "u U v V w W x X y Y z Z [ { } \" ' ; : < " +
        "/ ` ~ # $ _ + = & ^ * % > ] ? \\ |",

/*08*/  "a A b B c C d D e E f F g G h H i I j J " +
        "k K l L m M n N o O p P q Q r R s S t T " +
        "u U v V w W x X y Y z Z [ ] { } \" ' ; : " +
        "< > ? \\ | / ~ ` # $ _ + = & ^ * %",

/*09*/  "a A b c C d D e E f F g G h H i I j J k " +
        "K l L m M n N o O p P q Q r R s S t T u " +
        "U v w W x X y Y z Z [ ] { } \" ' ; : ` < " +
        "> / \\ ~ | ? ! @ # $ _ + = & ^ * %",

/*10*/  "A b B c C D E F G h H i I J K L m M n N " +
        "o O p P q R S T U v V W x X Y Z ; : \\ | " +
        "] } [ { _ = + ` ~ , < > @ % ^ * / ? "
    );

//lang_vowel_code: unicode value for independent vowel - when added with the 
//starting unicode value of a particular indic language will produce the indic 
//character
var lang_vowel_code = 
    new Array(
/*00*/  "5,6,6,6,7,8,8,8,8,8,9,10,10,10,10,10,14,15,15,15," +
        "15,16,18,19,19,19,19,20,20,3,3,3,56 77 48 64,5260",

/*01*/  "5,6,6,6,7,8,8,8,8,8,9,10,10,10,10,10,11,96,15,15," +
        "15,15,14,16,19,19,19,19,18,20,20,12,97,2,3,3,1,60,61,80," +
        "80,100,101,5900",

/*02*/  "5,6,6,6,7,8,8,8,8,8,9,10,10,10,10,10,11,11,96,96," +
        "14,15,15,15,15,16,18,19,19,19,19,20,20,12,97,2,3,3,1,-707," +
        "-668,-667,5132",

/*03*/  "61,60,112,82,5900,31,32,76,20,71,72,48,67,36,37,47,30,65,66,63," +
        "64,75,19,42,43,7,8,15,16,62,6,56,54,38,39,9,10,23,24,57," +
        "5,28,29,21,22,50,51,80,3,55,11,33,34,27,28,53,1,44,45,40," +
        "35,46,2,25,100,101,77",

/*04*/  "75,19,53,52,46,35,77,5,62,6,63,7,65,9,42,,,,48,49," +
        "21,,36,,56,,50,51,,,28,,76,20,64,8,71,15,66,10," +
        "57,25,40,41,72,16,77,,,,70,14,,,,30,,31,26,," +
        "74,55,,47,,18,,,,,,48 77,3,,,21 77 55,36 77 48,,28 77 30",

/*05*/  "5,6,7,8,9,10,14,15,16,18,19,20,3,62,63,64,65,66,70,71," +
        "72,74,75,76,21,21,25,26,30,36,35,31,40,42,42,46,47,48,50,53," +
        "53,52,51,49,41,56,55,57,28,77",

/*06*/  "5,6,7,8,9,10,14,15,16,13,18,19,20,17,11,96,62,63,64,65," +
        "66,70,71,72,69,74,75,76,73,2,3,1,67,68,21,22,23,24,25,26," +
        "27,28,29,30,31,32,33,34,35,36,37,38,39,40,42,43,44,45,46,47," +
        "48,50,53,54,56,55,57,51,21 77 55,36 77 48,28 77 30,54 77 48,77,4877",

/*07*/  "75,19,53,52,46,35,77,5,62,6,63,7,65,9,42,43,23,24,48,49," +
        "21,22,36,37,56,54,50,51,38,39,28,29,87,20,64,8,71,15,66,10," +
        "57,25,40,,72,16,2,,44,45,70,14,33,34,30,32,31,26,27,55," +
        "47,74,18,77 48,48 77,3,11,67,21 77 55,36 77 48,54 77 48,28 77 30," +
                                              "4877,,,",

/*08*/  "75,19,53,,46,35,77,5,62,6,63,7,65,9,42,43,23,24,48,49," +
        "21,22,36,37,56,54,50,51,38,39,28,29,76,20,64,8,71,15,66,10," +
        "57,25,40,,72,16,2,,44,45,70,14,33,5004,34,30,32,31,26,27," +
        "55,,,,,47,18,74,77 48,48 77,3,11,67,21 77 55,36 77 48,54 77 48,28 77 30",

/*09*/  "75,19,53,46,35,77,5,62,6,63,7,65,9,42,43,23,24,48,49,21," +
        "22,36,37,56,54,50,51,38,39,28,29,76,20,64,8,71,15,66,10,57," +
        "25,40,72,16,2,1,44,45,70,14,33,,34,30,32,31,26,27,74,55," +
        ",47,,18,,,,,,48 77,3,11,67,21 77 55,36 77 48,,28 77 30",

/*10*/  "49 62,70,41 62,9,10,41 65,40 65,21 65,52 65,62,52,72,16,36 65," +
                                                "46 65,31 65,5,6,71,26 66," +
        "31 63,31 64,63,64,35 65,26 65,51 65,21 66,48 65,14,15,49 65,18," +
                                "19,50 65,35 62,77,77,46 66,55 77 48 64," +
        "72,65,65,66,-2897,52 66,48 66,3,51 66,7,8,-2881,77,65,66,-2911," +
                                                            "-2900,31 66"
    );

//lang_vowel_symbol_code: unicode value for dependent vowel - when added with  
//the starting unicode value of a particular indic language will produce the  
//indic character
var lang_vowel_symbol_code = 
    new Array(
/*00*/  ",62,62,62,63,64,64,64,64,64,65,66,66,66,66,66,70,71,71,71," +
        "71,72,74,75,75,75,75,76,76,,,,,",

/*01*/  ",62,62,62,63,64,64,64,64,64,65,66,66,66,66,66,67,68,71,71," +
        "71,71,70,72,75,75,75,75,74,76,76,98,99,2,3,3,1,60,61,," +
        ",,,",

/*02*/  ",62,62,62,63,64,64,64,64,64,65,66,66,66,66,66,67,67,68,68," +
        "70,71,71,71,71,72,74,75,75,75,75,76,76,,,2,3,3,1,," +
        ",,"
    );

//lang_consonant: key map for consonant characters for different languages - 
//should follow the same keymap standard as vowel characters.
var lang_consonant = 
    new Array(
/*00*/  "ksh k kh K Kh g gh G Gh ~m ng c ch C Ch c' j jh J Jh " +
        "~n ny T t' Th th' D d' Dh dh' N nh t th d dh n n' p ph " +
        "f P Ph b bh B Bh m y r R r' l L lh Lh l' z Z zh " +
        "Zh v w S s' sh Sh s h H",

/*01*/  "k kh kH K Kh KH g gh gH G Gh GH ~m c ch cH C Ch CH c' " +
        "j jh jH J Jh JH ~n T t' Th TH th' tH' D d' Dh DH dh' dH' N " +
        "nh nH n' t th tH d dh dH n p ph pH f P Ph PH b bh bH " +
        "B Bh BH m y r l L lh v w S s' sh sH Sh SH s h H",

/*02*/  "k kh kH K Kh KH g gh gH G Gh GH ~m c ch cH C Ch CH c' " +
        "j jh jH J Jh JH ~n T t' Th TH th' tH' D d' Dh DH dh' dH' N " +
        "nh nH n' t th tH d dh dH n p ph pH f P Ph PH b bh bH " +
        "B Bh BH m y r l v w S s' sh sH Sh SH s h H L lh " +
        "Lh l' r\" ~r",

/*03*/  "a d e f g j k l Q r s t u w y z ' \" ! # " +
        "$ &"
    );

//lang_consonant_code: unicode value for consonants - when added with the 
//starting unicode value of a particular indic language will produce the indic 
//character
var lang_consonant_code = 
    new Array(
/*00*/  "21 77 55,21,21,21,21,21,21,21,21,25,25,26,26,26,26,26,28,28,28,28," +
        "30,30,31,31,31,31,31,31,31,31,35,35,36,36,36,36,40,41,42,42," +
        "42,42,42,42,42,42,42,46,47,48,49,49,50,51,52,51,51,52,52,52," +
        "52,53,53,55,55,55,55,56,57,57",

/*01*/  "21,22,22,22,22,22,23,24,24,24,24,24,25,26,26,26,27,27,27,27," +
        "28,29,29,29,29,29,30,31,31,32,32,32,32,33,33,34,34,34,34,35," +
        "35,35,35,36,37,37,38,39,39,40,42,43,43,43,43,43,43,44,45,45," +
        "45,45,45,46,47,48,50,51,52,53,53,54,54,55,55,55,55,56,57,57",

/*02*/  "21,22,22,22,22,22,23,24,24,24,24,24,25,26,26,26,27,27,27,27," +
        "28,29,29,29,29,29,30,31,31,32,32,32,32,33,33,34,34,34,34,35," +
        "35,35,35,36,37,37,38,39,39,40,42,43,43,43,43,43,43,44,45,45," +
        "45,45,45,46,47,48,50,53,53,54,54,55,55,55,55,56,57,57,51,51," +
        "51,51,49,49",

/*03*/  "47,41,40,21,42,36,46,31,57,26,51,53,48,49,50,35,25,30,56,55," +
        "28,21 77 55"
    );

/* lang_left_hand_matras - stores the left hand matras (dependent vowel symbol) 
 * for various languages
 */
var lang_left_hand_matras =
    new Array(
/*00*/  "70,71,72"
    );

/* lang_both_side_matras - stores the matras (dependent vowel symbol) which
 * will be rendered both sides of the consonant, for various languages
 */
var lang_both_side_matras =
    new Array(
/*00*/  "70 62,71 62,70 51"
    );

/* replacement characters for the previous array*/
var lang_both_side_matras_replacement =
    new Array(
/*00*/  "74,75,76"
    );


//details stored in lang_details are in the following order
//"keyboard_label","vowel_index","vowel_code_index","vowel_symbol_code_index",
//"consonant_index","consonant_code_index","codebase_value","halant_value",
//"left_hand_matras_index","both_side_matras_index"

var lang_details = 
    new Array(
        new Array("tamil_rts",     "0", "0", "0", "0",  "0",   "0x0b80", "77","-1","-1"),
        new Array("hindi_rts",     "1", "1", "1", "1",  "1",   "0x0900", "77","-1","-1"),
        new Array("marathi_rts",   "1", "1", "1", "1",  "1",   "0x0900", "77","-1","-1"),
        new Array("malayalam_rts", "1", "1", "1", "1",  "1",   "0x0d00", "77","-1","-1"),
        new Array("telugu_rts",    "2", "2", "2", "2",  "2",   "0x0c00", "77","-1","-1"),
        new Array("nepali_xkb",    "3", "3", "-1", "-1", "-1", "0x0900", "-1","-1","-1"),
        new Array("bengali_ins",   "4", "4", "-1", "-1", "-1", "0x0980", "-1","-1","-1"),
        new Array("gujarathi_ins", "4", "4", "-1", "-1", "-1", "0x0a80", "-1","-1","-1"),
        new Array("gurumukhi_ins", "4", "4", "-1", "-1", "-1", "0x0a00", "-1","-1","-1"),
        new Array("hindi_ins",     "4", "4", "-1", "-1", "-1", "0x0900", "-1","-1","-1"),
        new Array("kannada_ins",   "8", "8", "-1", "-1", "-1", "0x0c80", "-1","-1","-1"),
        new Array("malayalam_ins", "7", "7", "-1", "-1", "-1", "0x0d00", "-1","-1","-1"),
        new Array("marathi_ins",   "4", "4", "-1", "-1", "-1", "0x0900", "-1","-1","-1"),
        new Array("oriya_ins",     "4", "4", "-1", "-1", "-1", "0x0b00", "-1","-1","-1"),
        new Array("tamil_ins",     "4", "4", "-1", "-1", "-1", "0x0b80", "-1","-1","-1"),
        new Array("telugu_ins",    "9", "9", "-1", "-1", "-1", "0x0c00", "-1","-1","-1"),
        new Array("tamil_wx",      "5", "5", "-1", "-1", "-1", "0x0b80", "-1","-1","-1"),
        new Array("bengali_wx",    "6", "6", "-1", "-1", "-1", "0x0980", "-1","-1","-1"),
        new Array("gujarathi_wx",  "6", "6", "-1", "-1", "-1", "0x0a80", "-1","-1","-1"),
        new Array("gurumukhi_wx",  "6", "6", "-1", "-1", "-1", "0x0a00", "-1","-1","-1"),
        new Array("hindi_wx",      "6", "6", "-1", "-1", "-1", "0x0900", "-1","-1","-1"),
        new Array("kannada_wx",    "6", "6", "-1", "-1", "-1", "0x0c80", "-1","-1","-1"),
        new Array("malayalam_wx",  "6", "6", "-1", "-1", "-1", "0x0d00", "-1","-1","-1"),
        new Array("marathi_wx",    "6", "6", "-1", "-1", "-1", "0x0900", "-1","-1","-1"),
        new Array("oriya_wx",      "6", "6", "-1", "-1", "-1", "0x0b00", "-1","-1","-1"),
        new Array("telugu_wx",     "6", "6", "-1", "-1", "-1", "0x0c00", "-1","-1","-1"),
        new Array("tamil_typewriter","10", "10", "-1", "3", "3", "0x0b80", "-1","0","0")
    );

var keyboard_layout_names =
    new Array(
        new Array("bengali_ins",   "Bengali - Inscript"),
        new Array("bengali_wx",    "Bengali - WX"),
        new Array("gujarathi_ins", "Gujarathi - Inscript"),
        new Array("gujarathi_wx",  "Gujarathi - WX"),
        new Array("gurumukhi_ins", "Gurumukhi - Inscript"),
        new Array("gurumukhi_wx",  "Gurumukhi - WX"),
        new Array("hindi_ins",     "Hindi - Inscript"),
        new Array("hindi_rts",     "Hindi - RTS"),
        new Array("hindi_wx",      "Hindi - WX"),
        new Array("kannada_ins",   "Kannada - Inscript"),
        new Array("kannada_wx",    "Kannada - WX"),
        new Array("malayalam_ins", "Malayalam - Inscript"),
        new Array("malayalam_rts", "Malayalam - RTS"),
        new Array("malayalam_wx",  "Malayalam - WX"),
        new Array("marathi_ins",   "Marathi - Inscript"),
        new Array("marathi_rts",   "Marathi - RTS"),
        new Array("marathi_wx",    "Marathi - WX"),
        new Array("nepali_xkb",    "Nepali - Bolnagari"),
        new Array("oriya_ins",     "Oriya - Inscript"),
        new Array("oriya_wx",      "Oriya - WX"),
        new Array("tamil_ins",     "Tamil - Inscript"),
        new Array("tamil_rts",     "Tamil - RTS"),
        new Array("tamil_typewriter", "Tamil - Typewriter"),
        new Array("tamil_wx",      "Tamil - WX"),
        new Array("telugu_ins",    "Telugu - Inscript"),
        new Array("telugu_rts",    "Telugu - RTS"),
        new Array("telugu_wx",     "Telugu - WX")
    );

//isMenu: flag tells us the display status of hint menu
var isMenu = false;

//iexplore: this will be set if the browser is Internet Explorer
var iexplore = !window.opera && (window.event || document.all); 

//needed to get the value for the class attribute for any tag
var classAttrib = (iexplore)? "className":"class";

var vowel;
var vowel_code;
var vowel_symbol_code;
var consonant;
var consonant_code;
var left_hand_matras;
var both_side_matras;
var both_side_matras_replacement;
var charlist;
var halant;
var codebase;
var map_type;

//prevChar - previously typed character(s), may form another indic character
//while adding new character(s)
var prevChar = "";
var prevPrevChar = "";
var prevLen = 0;    //length of the string stored in 'prevChar'
var seqChar = "";   //character sequence

//isInBothSet - tells us whether the last character typed present in both
//vowel and consonant character set
var isInBothSet = false;

//strInBothSet - contains the previously typed string which is present in
//vowel and consonant character set
var strInBothSet = "";

//lastIndicLayout - contains index of the previously selected Indic keyboard
//layout, so that we can toggle between this and English keyboard layout(0)
lastIndicLayout = "";

document.onclick = freeze;

/* the following events are assigned with some other handlers at the end of
 * this file
 */
//document.onkeyup = splEvent;
//document.onkeypress = eventCaptured;

/*
Specific funtion for Internet Explorer. It is called in response to keyup event.
It handles the event for some specific keys.
*/
function splEvent(event)
{
    var key;
    if(iexplore) {
        key = window.event.keyCode;
        switch(key) {
            case 8:   // backspace
            case 17:  // control
            case 18:  // alt
            case 33:  // page up
            case 34:  // page down
            case 35:  // end
            case 36:  // home
            case 37:  // left arrow
            case 38:  // up arrow
            case 39:  // right arrow
            case 40:  // down arrow
            case 45:  // insert
            case 46:  // delete
                //characters typed upto now won't be changed by
                //the addition of characters
                freeze();
        }
    }
}

/*
This function is called in response to keypress event.
*/
function eventCaptured(evt)
{
//alert("eventCaptured");

    var target, event, selectCombo, selectedIndex, keyboardLabel;

    if(iexplore) {  //if the browser is IE
        event = window.event;
        target = window.event.srcElement;
    }
    else { //for other browsers
        event = evt;
        target = evt.target;
    }

    selectCombo     = document.getElementsByName("selectLanguage").item(0);
    selectedIndex   = selectCombo.selectedIndex;
    keyboardLabel   = selectCombo.options[selectedIndex].label;

    if(keyboardLabel == "english")  //English is selected
        return;

    eventHandler(target,event);
}

/*
This function calls the function 'getUniChar' by sending the text to be transliterated
and gets back the transliterated text. Then inserts the transliterated text into the
appropriate place in the editable area.
*/
function eventHandler(textControl,event)
{
//alert("eventHandler");
    var selectionStart,selectionEnd,result,scrollTop,scrollLeft;
    var keyCode=0,str="";

    //events originated from the specified target only be processed
   
    if((textControl.type != "textarea" && textControl.type != "text") ||
      textControl.getAttribute(classAttrib) != "indic_wi")
        return;

    if(iexplore)
        keyCode = event.keyCode;
    else if(event.which)
        keyCode = event.which;

    var key = String.fromCharCode(keyCode);

    if(event.ctrlKey || event.altKey) {
        //don't process the key if control or alt pressed
    }
    else if(charlist.indexOf(key) != -1) {
        //if the key is mapped fully or partially to any indic character

        if(event.preventDefault) //prevent its default functionality
            event.preventDefault();
        else
            event.returnValue = false;
    
        //map_type = one2many means, the key map is framed such that single
        //key will produce one or more indic characters, ie., no combinations are defined.
        //map_type = many2many means, the key map is framed such that more than
        //one keys can combinely produce one or more indic characters.
        //map_type = many2many_woh means, the key map is similar to many2many type 
        //except the halant character will not be added automatically to the consonant 
        //character.

        if(map_type == "one2many") {
            key = getUniCharSimple(key);
        }
        else if(map_type == "many2many" || map_type == "many2many_woh") {
            //str - english character(s) to be converted into Indic character(s)
            str = prevChar + key;
            result = getUniChar(str);
            key = result.str;       //the converted indic character(s)
        }
        else {
            return;
        }

        /********** left_hand_matras_reversal starts here ************/

        if (map_type == "one2many" && typeof left_hand_matras != 'undefined' && 
            left_hand_matras.length > 0) {
             
             /* in typewriter layout, the left hand matras will be typed before typing the
             * consonant. but in unicode, all matras should be put after the consonant
             * being typed. so the following block of code will interchange the positions
             * of the left hand matras and the consonant.
             *
             * Assumption(s):
             * --------------
             * 1. all the consonants for which the left hand matras should be interchanged
             * will be in consonant charset (lang_consonant array). everything else will be 
             * in vowel charset (lang_vowel array).
             */

            if (prevChar && is_left_hand_matra(prevChar) && is_consonant(key)) {
                key += prevChar; //add matra to the right
                prevLen = 1; //previously added matra's length
            }
        }

        /*********** left_hand_matras_reversal ends here *************/

        /********* both_side_matras_replacement starts here **********/

        if (map_type == "one2many" && typeof both_side_matras != 'undefined' && 
            both_side_matras.length > 0 &&
            typeof both_side_matras_replacement != 'undefined' && 
            both_side_matras.length == both_side_matras_replacement.length) {
             
             /* in typewriter layout, the matra which will be rendered both sides
              * of the consonant will be produced by typing the left hand matra and
              * the right hand matra.
              * but in unicode, these will be replaced with a single matra
              * so the following block of code will do the replacement
              *
              * prevPrevChar - contains left hand matra
              * prevChar - contains consonant
              * key - contains right hand matra
              *
              * the above variables will have the mentioned values, eventhough the
              * left hand matra and the consonant has be interchanged
              */

            if (prevPrevChar) {
                var both_side_matras_index = get_both_side_matras_index(prevPrevChar, key);
                if (both_side_matras_index != -1) {
                    //replace new matra
                    key = String.fromCharCode(
                            parseInt(both_side_matras_replacement[both_side_matras_index]) + codebase
                        );
                    prevLen = 1; //previously added matra's length
                }
            }
        }

        /********** both_side_matras_replacement ends here ************/

        if(map_type == "many2many" || map_type == "many2many_woh") {
            prevChar = str.substring(result.freezpos);
            //freezpos is a position within the english string. 
            //While converting english string into indic, the characters starting
            //from freezpos won't affect the characters upto freezpos.

            isInBothSet = result.bothcharset;  
            //isInBothSet is true if recently typed character(s) can form combinations
            //both in vowel and consonant charset
            //eg., 'r' can be a consonant or it can be changed to vowel by the addition
            //of a single quote(r')
        
            strInBothSet = result.charsetstr;
            //recently typed character(s) that form combinations both in vowel and
            //consonant charset
        }
        else { // for one2many keyboard type
            /* useful to identify the previous character in typewriter
             * keyboard layout
             */
            prevPrevChar = prevChar;
            prevChar = key.substring(0, 1);
        }

        if(document.selection) {    // for IE
            textControl.focus();
            sel = document.selection.createRange();
            sel.moveStart('character',-prevLen);
            sel.text = key; //replace the selected text by the character(s) in 'key'
            
            sel.select();       //deselect, if selected
        }
        else if(textControl.setSelectionRange) {    // for Netscape, Firefox
            selectionStart = textControl.selectionStart - prevLen;
            selectionEnd = textControl.selectionEnd;
            scrollTop = textControl.scrollTop;
            scrollLeft = textControl.scrollLeft;
            
            textControl.value = textControl.value.substring(0,selectionStart) +  key +
                                textControl.value.substring(selectionEnd);
            textControl.setSelectionRange(selectionStart + key.length,selectionStart +
                                key.length);

            textControl.scrollTop = scrollTop;   //set the top line to be displayed
            textControl.scrollLeft = scrollLeft; //set the left column to be displayed
        }
        else {      // for other Browsers - opera
            textControl.value = textControl.value.substring(0,textControl.value.length
                                                                    -prevLen) + key;
        } 
        //I don't know how to find the cursor position in a Textarea in Opera, So I
        //just append the converted characters at the end
        
        if(map_type == "many2many" || map_type == "many2many_woh") {
            prevLen = result.indic.length;  
            //prevLen - contains the length of the indic charcter(s) which can be affected
            //by the addition of new characters
        }
        else {
            prevLen = 0;
        }
        
        var dispChar = '';
        if (map_type == 'many2many') {
            dispChar = prevChar;
        }
        else if (map_type == "many2many_woh") {
            dispChar = (prevChar) ? prevChar : seqChar;
        }
//alert(prevChar+"#"+seqChar);

        //show or hide hint menu for typing indic character
        if(dispChar && document.getElementsByName("chk_show_hint").item(0).checked) {
            showMenu(dispChar,textControl);
        }
        else {
            hideMenu();
        }
            
        return;     //return without calling freeze
        
    }//end of code for converting the key
    
    else if((keyCode >=65 && keyCode <= 90) || (keyCode >= 97 && keyCode <= 122)) {
        //prevent displaying of alphabets not mapped
        if(event.preventDefault)
            event.preventDefault();
        else           
            event.returnValue = false;
    }

    freeze();
}

/*
This function is able to transliterate the english string into indic equivalent
for one_to_many type of key map.
*/
function getUniCharSimple(key)
{
//alert("getUniCharSimple");
    var i,resultstr;

    resultstr = "";
    for(i=0;i<vowel.length;i++) {    //searching the vowel character set
        if(vowel[i] == key) {
            resultstr = fromCharCodeArray(parseAndAdd(vowel_code[i],codebase));
            break;
        }
    }
    if (resultstr) {
        return resultstr;
    }

    for(i=0;i<consonant.length;i++) {    //searching the consonant character set
        if(consonant[i] == key) {
            resultstr = fromCharCodeArray(parseAndAdd(consonant_code[i],codebase));
            break;
        }
    }
    return resultstr;
}

/*
This function is able to transliterate the english string into indic equivalent
for many2many[_woh] type of key map.
*/
function getUniChar(str, mode)
{
//alert("getUniChar " + str);
    var i,result="",prevresult="",resultstr="",temp="",match="",indic="",start=0;
    var charsetstr="",newstr="",isPrevHalf=false,freezpos=0,tempresult="";
    var bothcharset=false;
    
    for(i=0;i<str.length;i++) {
        //take characters one by one from incoming string 'str' and form a string
        //which needs to be compared with the key map
        newstr = str.substring(start,i+1); 
        
        //find the charset in which the combination exists
        result = findCharSet(newstr);
        match = "";

        if(result.match == "nomatch")     //no matching combinations found
            match = "nomatch";
        else if(result.match == "notexact") {
            match = "notexact";
            //one or more combinations start with the string but not exactly
            //equals to the string
        }
        else if(result.charset == "vowel") {
            //string exactly matches with one combination in vowel charset
            if((result.vowcount + result.conscount) == 1) {
                //the string matches with only one combination in vowel charset only -
                //won't be affected by the addition of new characters               
                match = "vowel";
            }
            else {
                //many combinations in vowel charset(and consonant charset may also)
                //start with the string - can be changed to another vowel or even 
                //consonant by the addition of new characters
                match = "vowel*";
            }
        }
        else if(result.charset == "consonant") { 
            //string exactly matches with one combination in consonant charset
            if((result.vowcount + result.conscount) == 1) {
                //the string matches with only one combination in consonant
                //charset only
                match = "consonant";
            }
            else {
                //many combinations in consonant charset(and vowel charset may also)
                //start with the string
                match = "consonant*";
            }
        }

//alert(match+" "+result.index+" "+result.vowcount);

        if(match == "vowel") {
            if(isPrevHalf && 
            (vowel_code[result.index]=="5"||vowel_symbol_code[result.index]!="")) {
                //the previous character is a consonant and (the current vowel
                //character has dependent vowel sign or it is the first vowel of
                //indic language(don't have dependent vowel sign))
                
                resultstr = resultstr.substring(0,resultstr.length-1);
                //remove the halant from the result string
                
                if(vowel_code[result.index] != "5") {
                    //the current letter is not the first vowel, add dependent
                    //vowel sign
                    resultstr += fromCharCodeArray(
                                    parseAndAdd(vowel_symbol_code[result.index],
                                    codebase)
                                 );
                }
            }
            else {
                //previous character is not a consonant, add the independent vowel
                //to the result
                resultstr += fromCharCodeArray(
                                parseAndAdd(vowel_code[result.index],
                                codebase)
                             );
            }
            start = i+1; indic = ""; isPrevHalf = false;
            freezpos = start;
            //string upto current character can be freezed
        }
        else if(match == "consonant") {
            if (map_type == "many2many") {
                //add the consonant character and halant to the result
                indic = 
                    fromCharCodeArray(
                        parseAndAdd(consonant_code[result.index]+" "+halant,codebase)
                    );
                isPrevHalf = true;
            }
            else {
                indic = 
                    fromCharCodeArray(
                        parseAndAdd(consonant_code[result.index],codebase)
                    );
            }
            resultstr += indic;
            freezpos = start; start = i+1;
        }
        else if(match == "nomatch" || (result.match == "notexact" && mode == "inner"))
        {
            //mode will be having the value 'inner' at the time of recursive call
            //inner mode & notexact string is treated as nomatch string
            
            if(newstr.length == 1) {
                //only one mapped character that doesn't match with any combination
                //as it is.
                
                if(isSplChar(newstr.charAt(0))) {
                    //it is a special character like single quote as in r'
                    resultstr += newstr;    //add it to the result
                }
                //'else' is not needed, because a single alphabet with
                //match = nomatch will not have indic equivalent.

                start = i+1; isPrevHalf = false; indic = "";
                freezpos = start;
                //string upto current character can be freezed since the newcoming
                //character can't affect the last special character
            }
            else if(prevresult.match == "notexact") {
                if(newstr.length == 2) {
                    //one character, match = notexact(at the previous iteration)
                    
                    if(isSplChar(newstr.charAt(0))) {
                        //eg., tilde character(~)
                        resultstr += newstr.charAt(0);
                    }
                    //'else' is not needed, because a single alphabet with
                    //match = notexact will not have indic equivalent
                    
                    indic = ""; start = i; freezpos = start; i--; isPrevHalf = false;
                }
                else {
                    //more than one character, match = notexact
                    //(at the previous iteration) eg., r' in Hindi
                    
                    resultstr = resultstr.substring(0,resultstr.length-indic.length);
                    
                    //following is a recursive call to function 'getUniChar'
                    tempresult = getUniChar(
                                    str.substring(freezpos,start)+
                                    newstr.substring(0,newstr.length - 1),"inner"
                                 );
                    indic = tempresult.indic;
                    freezpos += tempresult.freezpos;
                    start = i;   
                    //start from the last character, since addition of this failed to
                    //make any combinations
                    
                    i--;
                    //to compensate the i++ in for loop so that last character
                    //will be used as start
                    
                    resultstr += tempresult.str;
                    
                    if(map_type == "many2many" && 
                       indic.charCodeAt(indic.length-1) == halant + codebase)
                    {
                        //the last character of returned indic string is halant -
                        //so the last character is a consonant
                        isPrevHalf = true;
                    }
                    else
                        isPrevHalf = false;
                }
            }
            else if(prevresult.charset == "vowel" && 
                    (prevresult.vowcount + prevresult.conscount) > 1) {
                //previous match = vowel* - similar to the case match = vowel
                
                if(isPrevHalf && (vowel_code[prevresult.index]=="5"||
                                  vowel_symbol_code[prevresult.index]!="")) {
                    
                    resultstr = resultstr.substring(0,resultstr.length-1);
                    
                    if(vowel_code[prevresult.index] != "5") {
                        resultstr += fromCharCodeArray(
                            parseAndAdd(vowel_symbol_code[prevresult.index],codebase)
                        );
                    }
                }
                else {
                    resultstr += fromCharCodeArray(
                                    parseAndAdd(vowel_code[prevresult.index],
                                    codebase)
                                 );
                }
                start = i; freezpos = start; i--; isPrevHalf = false;
                indic = "";
            }
            else if(prevresult.charset == "consonant" && 
                    (prevresult.vowcount + prevresult.conscount) > 1) {
                //consonant* - similar to the case match = "consonant"
                if (map_type == "many2many") {
                    indic = fromCharCodeArray(
                        parseAndAdd(consonant_code[prevresult.index]+" "+halant,codebase)
                    );
                    isPrevHalf = true;
                }
                else {
                    indic = fromCharCodeArray(
                        parseAndAdd(consonant_code[prevresult.index],codebase)
                    );
                }
                resultstr += indic;
                freezpos = start; start = i; i--;
            }
        }//else if(match=="nomatch" ...) ends here
        else if(i == str.length - 1) {
            //if the final character of the incoming string reached
            
            if(match == "vowel*") {
                //splvowcount - no. of combinations(in vowel charset) which start with
                //the formed string, having no dependent vowel symbol
                //(except 'a') eg., '|' in hindi,telugu
                //so they won't affect the previous character
                
                if(result.splvowcount == result.vowcount) {
                    //all combinations are vowels without dependent vowel sign -
                    //the input string can be freezed upto the previous character
                    freezpos = start;
                    
                    indic = "";
                }
                
                //similar to the case match = vowel
                resultstr = resultstr.substring(0,resultstr.length - indic.length);
                
                if(isPrevHalf && (vowel_code[result.index]=="5"||
                                  vowel_symbol_code[result.index]!="")) {
                    
                    indic = indic.substring(0,indic.length - 1);
                    if(vowel_code[result.index] != "5") {
                        //for 'a' no symbol is needed
                        
                        indic += fromCharCodeArray(
                                    parseAndAdd(vowel_symbol_code[result.index],
                                    codebase)
                                 );
                    }
                }
                else {
                    indic += fromCharCodeArray(
                                parseAndAdd(vowel_code[result.index],
                                codebase)
                             );
                }
                resultstr += indic;
            }
            else if(match == "consonant*") {
                temp = fromCharCodeArray(
                        parseAndAdd(consonant_code[result.index]+" "+halant,codebase)
                    );
                if(result.vowcount == 0 || result.splvowcount == result.vowcount) {
                    //the formed string won't be changed into
                    //a vowel when adding character(s)
                    //so previous characters won't be affected, can be freezed
                    
                    freezpos = start;
                    indic = temp;
                }
                else {
                    //the formed string may be changed into a vowel when adding
                    //one or more characters
                    //eg., consonant character r can be changed to vowel(r')
                    //by adding single quote in telugu_rts keymap
                    
                        bothcharset = true;   
                        charsetstr = newstr;
                        indic += temp;
                }
                resultstr += temp;
            }
            else if(match == "notexact") {
                if(newstr.length == 1) {
                    //only one character in the formed string 'newstr'
                    
                    if(isSplChar(newstr.charAt(0))) {
                        //special character (not an alphabet)  eg., tilde(~)
                        
                        indic += newstr;
                        resultstr += newstr;
                    }
                    //'else' is not needed, because a single alphabet with
                    //match = notexact will not have indic equivalent
                    
                    if(map_type == "many2many") {
                        if(result.vowcount == 0) {
                            indic = newstr;
                            freezpos = start;
                        }
                        else {
                            bothcharset = true;   
                            //indicates the formed string is in both character set
                        
                            charsetstr = newstr;  //the string
                        }
                    }
                }
                else {
                    //the formed character contains more than one character
                    
                    //similar to the case prevresult.match = notexact
                    resultstr = 
                            resultstr.substring(0,resultstr.length - indic.length);
                    
                    tempresult = 
                            getUniChar(str.substring(freezpos,start)+newstr,"inner");
                            
                    indic = tempresult.str;
                    resultstr += tempresult.str;
                }
            }
        }//else if(i == str.length - 1) ends here
        
        prevresult = result;
    }
    return {str:resultstr,indic:indic,freezpos:freezpos,
            bothcharset:bothcharset,charsetstr:charsetstr};
}

/*
finds the character set in which the string exists
*/
function findCharSet(str)
{
//alert("findCharSet " + str);
    var i,match="nomatch",index=-1,charset="",consmatch="",vowmatch="";
    var conscount=0,consindex=-1,vowcount=0,vowindex=-1,splvowcount=0;
    var tempindex,tempstr,exp,tempcount=0;

    str = new Array(str); //for IE

    //escape the characters which have special meanings in regular expression
    tempstr = "";
    for (i=0; i<str.length; i++) {
        if (str[i] == "|" ||
            str[i] == "+" ||
            str[i] == "[")
        {
            tempstr += "\\";
        }
        tempstr += str[i];
    }
//alert(tempstr);

    exp = new RegExp(tempstr,"i");
    seqChar = "";

    for(i=0;i<vowel.length;i++) {    //searching the vowel character set
    
        tempindex = vowel[i].indexOf(str);

        if(tempindex != -1 && (tempindex == 0 || 
           isSplChar(vowel[i].charAt(0))))
        {
            vowcount++;
            
            if(map_type == "many2many" && vowel_symbol_code[i] == "" && 
               vowel_code[i] != "5")
            {
                splvowcount++;
            }
      
            if(str == vowel[i]) {
                vowmatch = "exact";
                vowindex = i;
            }
        }

        tempindex = vowel[i].search(exp);

        if(tempindex != -1 && (tempindex == 0 || 
           isSplChar(vowel[i].charAt(0))))
        {
            tempcount++;
        }
    }
    if(vowcount > 0 && vowmatch == "")
        vowmatch = "notexact";
 
/*    if(map_type == "many2many_woh") {
        if(vowmatch != "")
            match = vowmatch;

        if(tempcount > 1)
            seqChar = str;

        return {match:match,index:vowindex,vowcount:vowcount,conscount:conscount,
                charset:"vowel",splvowcount:vowcount};
    }*/

    for(i=0;i<consonant.length;i++) {   //searching the consonant character set
        
        tempindex = consonant[i].indexOf(str);

        if(tempindex != -1 && (tempindex == 0 || 
           isSplChar(consonant[i].charAt(0))))
        {
            conscount++;
            
            if(str == consonant[i]) {
                consmatch = "exact";
                consindex = i;
            }
        }

        tempindex = consonant[i].search(exp);

        if(tempindex != -1 && (tempindex == 0 || 
           isSplChar(consonant[i].charAt(0))))
        {
            tempcount++;
        }
    }
    if(conscount > 0 && consmatch == "")
        consmatch = "notexact";
  
    //determine which character set to return according to different conditions
    if((vowmatch == "" && (consmatch == "notexact" || consmatch == "exact")) ||
       (vowmatch == "notexact" && consmatch == "exact")) {
        
        charset = "consonant";
        match = consmatch;
        index = consindex;
    }
    else if(vowmatch != "") {
        charset = "vowel";
        match = vowmatch;
        index = vowindex;
    }

    if(tempcount > 1)
        seqChar = str;

    return {match:match,index:index,vowcount:vowcount,conscount:conscount,
            charset:charset,splvowcount:splvowcount};
}

/*
sets there is no character which will be affected by the forecoming characters
*/
function freeze()
{
//alert("freeze");

    prevChar     = "";
    prevPrevChar = "";
    seqChar      = "";
    prevLen      = 0;
    isInBothSet  = false;
    strInBothSet = "";

    hideMenu();
}

/*
makes arrangement for displaying hint menu. calls init() function
to initialize some global variables.
*/
function load(keyboardLabel)
{
//alert("load " + keyboardLabel);
    var i,parent,style,txtarea,txtfield;
    var body,div,table,tr,td,tbody;
  
    freeze();

    txtarea = document.getElementsByTagName('textarea');
    if(txtarea) {
        for(i=0;i<txtarea.length;i++) {
            if(txtarea[i].getAttribute(classAttrib) == "indic_wi")
                txtarea[i].onblur = freeze;
        }
    }
    
    txtfield = document.getElementsByTagName('input');
    if(txtfield) {
        for(i=0;i<txtfield.length;i++) {
            if(txtfield[i].type == "text" && 
               txtfield[i].getAttribute(classAttrib) == "indic_wi")
            {
                txtfield[i].onblur = freeze;
            }
        }
    }
    
    div = document.getElementById("menudiv");
    firstChild = div.firstChild;
    if (firstChild) {
        div.removeChild(firstChild);
    }
    div.style.position = "absolute";
    div.style.display = "none";
  
    table = document.createElement('table');
    table.id = 'outertbl';
    style = 'width:82px;border-collapse:separate;border-spacing:0px;' +
            'padding:0;background-color:gray';
    table.setAttribute('style',style);

    table.style.backgroundColor = 'gray';    //for IE

    div.appendChild(table);
    tbody = document.createElement('tbody');
    table.appendChild(tbody);
    tr = document.createElement('tr');
    tbody.appendChild(tr);
    td = document.createElement('td');
    tr.appendChild(td);

    for(i=0;i<lang_details.length;i++) {      //determine wihch language is selected
//alert("#"+keyboardLabel+"#"+lang_details[i][0]+"#");
        if(keyboardLabel == lang_details[i][0])
            break;
    }
//alert(i);
  
    if(i < lang_details.length) {
        init(i);
    }
    else {
        alert(
            "Error: This language keymap has some problem. Please use other keymaps"
            );
    }
}

/*
initializes the global variables with character sets and
other language specific details
*/
function init(index)
{
//alert("init " + index);
    
    var i,j,div_keymap_hint, vowel_symbol_code_index;
    var vowel_index,vowel_code_index,consonant_index,consonant_code_index;
    var left_hand_matras_index, both_side_matras_index;
    var both_side_matras_replacement_index;
    
    vowel_index       = parseInt(lang_details[index][1]);
    vowel_code_index  = parseInt(lang_details[index][2]);
    vowel_symbol_code_index  = parseInt(lang_details[index][3]);
    consonant_index   = parseInt(lang_details[index][4]);
    consonant_code_index = parseInt(lang_details[index][5]);
    codebase = parseInt(lang_details[index][6]);
    halant   = parseInt(lang_details[index][7]);
    left_hand_matras_index = parseInt(lang_details[index][8]);
    both_side_matras_index = parseInt(lang_details[index][9]);

    vowel = "";
    vowel_code = "";
    left_hand_matras = "";
    vowel_symbol_code = "";
    consonant = "";
    consonant_code = "";
    both_side_matras = "";
    both_side_matras_replacement = "";
  
    if(vowel_index >= 0 && vowel_index < lang_vowel.length) {
        vowel = lang_vowel[vowel_index].split(" ");
    }

    if(vowel_code_index >= 0 && vowel_code_index < lang_vowel_code.length) {
        vowel_code = lang_vowel_code[vowel_code_index].split(",");
    }

    if(left_hand_matras_index >= 0 && left_hand_matras_index < lang_left_hand_matras.length) {
        left_hand_matras = lang_left_hand_matras[left_hand_matras_index].split(",");
    }

    if(both_side_matras_index >= 0 && both_side_matras_index < lang_both_side_matras.length &&
       both_side_matras_index < lang_both_side_matras_replacement.length) {
        both_side_matras = lang_both_side_matras[both_side_matras_index].split(",");
        both_side_matras_replacement = 
                lang_both_side_matras_replacement[both_side_matras_index].split(",");
    }

    map_type = "one2many";
    charlist = "";
    for(i=0;i<vowel.length;i++)
    {
        if(vowel[i].length > 1) {
            map_type = "many2many";
        }
        for(j=0;j<vowel[i].length;j++)
            if(charlist.indexOf(vowel[i].charAt(j)) == -1)
                charlist += vowel[i].charAt(j);
    }
    if(map_type == "many2many" && (consonant_index == "-1" ||
       consonant_code_index == "-1" || halant == "-1"))
    {
        map_type += "_woh";
    }
//alert(map_type);

    freeze();

    div_keymap_hint = document.getElementById("div_keymap_hint");
    div_keymap_hint.style.display = 'none';

    div_keymap_hint.style.display = '';
        
    if(vowel_symbol_code_index >= 0 && vowel_symbol_code_index < lang_vowel_symbol_code.length) {
        vowel_symbol_code = lang_vowel_symbol_code[vowel_symbol_code_index].split(",");
    }

    if(consonant_index >= 0 && consonant_index < lang_consonant.length) {
        consonant = lang_consonant[consonant_index].split(" ");

        for(i=0;i<consonant.length;i++)
        {
            for(j=0;j<consonant[i].length;j++)
                if(charlist.indexOf(consonant[i].charAt(j)) == -1)
                    charlist += consonant[i].charAt(j);
        }
    }
        
    if(consonant_code_index >= 0 && consonant_code_index < lang_consonant_code.length) {
        consonant_code = lang_consonant_code[consonant_code_index].split(",");
    }
}

/*
 * returns true if the value in key is a left_hand_matra else false
 */
function is_left_hand_matra(key)
{
    return value_exists(key, left_hand_matras);
}

/*
 * returns true if the value in key is in consonant character set
 */
function is_consonant(key)
{
    return value_exists(key, consonant_code);
}

/*
 * returns true if prevChar and key combination is present in 
 * both_side_matras array
 */
function get_both_side_matras_index(prevChar, key)
{
    return value_exists(prevChar + "" + key, both_side_matras, true);
}

/*
 * returns true if the value 'val' is found in the array 'arr'
 * array elements can have space separated values. before comparing
 * it with val, parse them and add codebase value to each space
 * separated value.
 * retindex - specifies whether a boolean value will be returned of
 * the index will be returned
 */
function value_exists(val, arr, retindex)
{
    var i, str;

    for(i = 0; i < arr.length; i++) {
        str = fromCharCodeArray(parseAndAdd(arr[i], codebase));
        if (val == str) {
            if (retindex) {
                return i;
            }
            return true;
        }
    }
    if (retindex) {
        return -1;
    }
    return false;
}

/*
parses the string 'str' at space and adds the value 'val' to each string
and returns as an integer array
*/
function parseAndAdd(str,val)
{
//alert("parseAndAdd");
    
    var i,arr;
   
    if (str == "") {
        return "";
    } 
    str = str.split(" ");
    arr = new Array(str.length);
    
    for(i=0;i<str.length;i++)
    {
        arr[i] = parseInt(str[i]) + val;
    }
    return arr;
}

/*
returns the character string for the character codes given as an array
*/
function fromCharCodeArray(arr)
{
//alert("fromCharCodeArray");
    
    var i,str;
    
    str = "";
    
    for(i=0;i<arr.length;i++)
    {
        str += String.fromCharCode(arr[i]);    
    }
    return str;
}

/*
used to hide the hint menu
*/
function hideMenu()
{
//alert("hideMenu");
    var div;
    var innertbl,parent;
    if(isMenu) {    
        isMenu = false;
        div = document.getElementById("menudiv");
        div.style.display = "none";
        innertbl = document.getElementById('innertbl');
        parent = innertbl.parentNode;
        parent.removeChild(innertbl);    
    }
}

/*
used to show the hint menu
*/
function showMenu(str,textControl)
{
//alert("showMenu");
    var div,resultcomb="",result="",style="",tempresult="";
    var innertbl,parent,tr,td,exactindex,tbody;
    var i,keyCode,engkey="",indickey="",count=0,obj;
    
    if(isMenu)
        hideMenu();
  
    if(isInBothSet && str != strInBothSet) {
        result = findSubSet(strInBothSet,true);        //case sensitive
        tempresult = findSubSet(str.substring(0,str.lastIndexOf(strInBothSet)),true);
    }
    else
        result = findSubSet(str,false);               //case insensitive
  
    exactindex = result.exactindex != -1 ? result.exactindex : 0;

    if(map_type == "many2many") {
        if(result.exactindex == -1 && tempresult == "")
        {// for Ru etc.
            resultcomb = makeCharSet(str);
            if((result.vowindex.length + result.consindex.length +
                resultcomb.indic.length) <= 0)
            {
                return;
            }
        }
        else if(result.match == "consonant")
            resultcomb = makeCharSet(consonant[result.consindex[exactindex]]);
    }
    
    parent = document.getElementById('outertbl');
  
    parent = parent.getElementsByTagName('td').item(0);
  
    innertbl = document.createElement('table');
    innertbl.id = "innertbl";
    innertbl.width = 80;
    innertbl.cellspacing = 0;
    innertbl.cellpadding = 0;
    style = 'background-color:#cccccc;font-family:sans-serif;font-size:15px;' +
            'font-size-adjust:0.58;line-height:20px;';
    innertbl.setAttribute('style',style);

    innertbl.style.backgroundColor = '#cccccc';    //for IE
    
    parent.appendChild(innertbl);
    tbody = document.createElement('tbody');
    innertbl.appendChild(tbody);

    count += result.vowindex.length + result.consindex.length;

    for(i=0;i<result.vowindex.length;i++) {
        tr = document.createElement('tr');
        if(i == exactindex && result.match == "vowel")
            tr.style.backgroundColor = 'gray';
    
        if(isInBothSet && str != strInBothSet) {
            engkey = consonant[tempresult.consindex[tempresult.exactindex]] +
                     vowel[result.vowindex[i]];
            keyCode = 
                parseAndAdd(
                    consonant_code[tempresult.consindex[tempresult.exactindex]],
                    codebase
                );
                
            indickey = fromCharCodeArray(keyCode);
            keyCode = parseAndAdd(vowel_symbol_code[result.vowindex[i]],codebase);
                      
            indickey += fromCharCodeArray(keyCode);
        }
        else {
            engkey = vowel[result.vowindex[i]];
            keyCode = parseAndAdd(vowel_code[result.vowindex[i]],codebase);
            indickey = fromCharCodeArray(keyCode);
        }
        
        td = document.createElement('td');
        td.appendChild(document.createTextNode(engkey));
        td.setAttribute('align',"center");
        tr.appendChild(td);
        td = document.createElement('td');
        td.appendChild(document.createTextNode(indickey));
        td.setAttribute('align',"center");
        tr.appendChild(td);
        tbody.appendChild(tr);
        
    }//for loop ends here
   
    for(i=0;i<result.consindex.length;i++) {
        tr = document.createElement('tr');
        
        if(i == exactindex && result.match == "consonant")
            tr.style.backgroundColor = "gray";
            
        td = document.createElement('td');
        td.appendChild(document.createTextNode(consonant[result.consindex[i]]));
        td.setAttribute('align',"center");
        tr.appendChild(td);
        td = document.createElement('td');

        keyCode = 
            parseAndAdd(consonant_code[result.consindex[i]] + " " + halant, codebase);
            
        td.appendChild(
                document.createTextNode(fromCharCodeArray(keyCode))
            );
           
        td.setAttribute('align',"center");
        tr.appendChild(td);
        tbody.appendChild(tr);
    }
    
    if(resultcomb != "") {
        count += resultcomb.indic.length;
        
        for(i=0;i<resultcomb.indic.length;i++) {
            tr = document.createElement('tr');
            
            if(result.consindex.length <= 0 ) {
                if(resultcomb.exactindex != -1) {
                    if(i == resultcomb.exactindex)
                        tr.style.backgroundColor = "gray";
                }
                else if(i == 0)
                    tr.style.backgroundColor = "gray";          
            }
            
            td = document.createElement('td');
            td.appendChild(document.createTextNode(resultcomb.eng[i]));
            td.setAttribute('align',"center");
            tr.appendChild(td);
            td = document.createElement('td');
            td.appendChild(document.createTextNode(resultcomb.indic[i]));
            td.setAttribute('align',"center");
            tr.appendChild(td);
            tbody.appendChild(tr);
        }
    }
  
    var charHeight,menuHeight,menuLeft,menuTop,offsetLeft,offsetTop,scrollTop;
    var scrollBot,midMenu,midObjPos,menuBot;
    
    offsetLeft = 0;
    offsetTop = 0;
    obj = textControl;
    
    if(obj.offsetParent) {
        while (obj.offsetParent) {
            offsetLeft += obj.offsetLeft;
            offsetTop += obj.offsetTop;
            obj = obj.offsetParent;
        }
    }
    
    if (self.pageYOffset)   // all except Explorer
        scrollTop = self.pageYOffset;
    else if (document.documentElement && document.documentElement.scrollTop) {
        // Explorer 6 Strict
        scrollTop = document.documentElement.scrollTop;
    }
    else if (document.body) // all other Explorers
        scrollTop = document.body.scrollTop;
    
    charHeight = 15 * 0.58 * 2 + 2;
    menuHeight = charHeight * count + count;
  
    scrollBot = scrollTop;
    if (self.innerHeight) // all except Explorer
        scrollBot += self.innerHeight;
    else if (document.documentElement && document.documentElement.clientHeight) {
        // Explorer 6 Strict Mode
        scrollBot += document.documentElement.clientHeight;
    }
    else if (document.body) // other Explorers
        scrollBot += document.body.clientHeight;
  
    midMenu = menuHeight / 2;
    midObjPos = offsetTop + textControl.clientHeight / 2;
  
    menuLeft = offsetLeft - 5 * charHeight;
    if(menuLeft < 0)
        menuLeft = offsetLeft + textControl.clientWidth + 2;
    if(menuHeight <= textControl.clientHeight) {
        menuTop = offsetTop;
        menuBot = offsetTop + menuHeight;
    }
    else {
        menuTop = midObjPos - midMenu;
        menuBot = midObjPos + midMenu;
    }
  
    if(menuBot > scrollBot)
        menuTop = scrollBot - menuHeight;
    if(menuTop < scrollTop)
        menuTop = scrollTop;

    div = document.getElementById('menudiv');
    div.style.left = menuLeft+'px';
    div.style.top = menuTop+'px';
    div.style.display = "";
    isMenu = true;
}

/*
used to find the combinations that start with the character(s) in 'str'
*/
function findSubSet(str,isCaseSensitive)
{
//alert("findSubSet " + str);
    var i,j,vowindex,consindex,exp,tempstr,match="",exactindex=-1;
    var tempindex,vowexact,consexact;
    
    str = new Array(str);  //if str has a single character, IE is not treating as string
    vowindex = new Array();
    consindex = new Array();
    vowexact = -1;
    consexact = -1;

    //escape the characters which has special meanings in regular expression
    tempstr = "";
    for (i=0; i<str.length; i++) {
        if (str[i] == "|" ||
            str[i] == "+" ||
            str[i] == "[")
        {
            tempstr += "\\";
        }
        tempstr += str[i];
    }
//alert(tempstr);

    if(isCaseSensitive)
        exp = new RegExp(tempstr);
    else
        exp = new RegExp(tempstr,"i");
  
    for(i=0,j=0;i<vowel.length;i++) {
    
        tempindex = vowel[i].search(exp);
        if(tempindex != -1 && (tempindex ==0 ||
           isSplChar(vowel[i].charAt(0))) &&
          (!isCaseSensitive || vowel_symbol_code[i] != "")) {
          
            if(vowel[i] == str)
                vowexact = j;
            vowindex[j++] = i;
        }
    }
    
    if(vowexact != -1) {
        match = "vowel";
        exactindex = vowexact;
    }

    for(i=0,j=0;i<consonant.length;i++) {
    
        tempindex = consonant[i].search(exp);
        if(tempindex != -1 && (tempindex == 0 ||
           isSplChar(consonant[i].charAt(0)))) {
          
            if(consonant[i] == str)
                exact = j;
            consindex[j++] = i;
        }
    }
    
    if(consexact != -1) {
        match = "consonant";
        exactindex = consexact;
    }
//alert(match);

    return {vowindex:vowindex,consindex:consindex,match:match,exactindex:exactindex};
}

/*
used to find the consonant + vowel combinations
*/
function makeCharSet(str)
{
//alert("makeCharSet");
    var i,j,exp,index=0,eng,indic,exactindex=-1,vowkey,conskey,temp;
  
    eng = new Array();
    indic = new Array();
  
    for(i=0;i<consonant.length;i++) {
    
        if(str.indexOf(consonant[i]) == -1)
            continue;
            
        for(j=0;j<vowel.length;j++) {
        
            if((vowel_symbol_code[j] != "" || vowel_code[j] == "5") &&
               (consonant[i]+vowel[j]).indexOf(str) == 0) {
               
                conskey = parseAndAdd(consonant_code[i],codebase);
                temp = fromCharCodeArray(conskey);
                
                if(vowel_code[j] != "5") {
                    vowkey = parseAndAdd(vowel_symbol_code[j],codebase);
                    temp += fromCharCodeArray(vowkey);
                }
                
                if(index == 0 || temp != indic[index-1]) {
                    eng[index] = consonant[i]+vowel[j];
                    indic[index] = temp;
                    if(str == eng[index])
                        exactindex = index;
                    index++;
                }
            }
        }
    }
    return {eng:eng,indic:indic,exactindex:exactindex};
}

function togHintMenu()
{
//alert("togHintMenu");

    var selectedIndex;
    var showHint,selectCombo;

    selectCombo     = document.getElementsByName("selectLanguage").item(0);

    selectedIndex   = selectCombo.selectedIndex;
    showHint        = document.getElementsByName("chk_show_hint").item(0).checked;

    writeCookie(selectedIndex,showHint);
}

function selectLang(selectCombo)
{
//alert("selectLang");

    var selectedIndex, keyboardLabel;
    var showHint, div_keymap_hint;

    div_keymap_hint = document.getElementById("div_keymap_hint");
    div_keymap_hint.style.display = 'none';

    selectedIndex = selectCombo.selectedIndex;
    showHint        = document.getElementsByName("chk_show_hint").item(0).checked;

    writeCookie(selectedIndex,showHint);

    keyboardLabel = selectCombo.options[selectCombo.selectedIndex].label;

    if(keyboardLabel != "english")
        load(keyboardLabel);
}

function writeCookie(selectedIndex,showHint) {
//alert("writeCookie");

    var today   = new Date();
    var expires = new Date(today.getTime() + (56 * 86400000));
    var indexes = new String();
    indexes     = selectedIndex +"-"+ showHint;
    
    if (selectedIndex != 0) {
        lastIndicLayout = selectedIndex;
    }
    else {
        indexes += "-" + lastIndicLayout;
    }

    setCookie("indicLangCookie",indexes,expires);
}

function loadKeyboardLayouts() {
//alert("loadKeyboardLayouts");

    var languageIndex, cookieValue, indicCookie;
    var showHint,div_keymap_hint, keyboardLabel;
    var form_element,selectCombo, i;

    languageIndex = 0;
    showHint = "";
    keyboardLabel = "";
    indicCookie   = getCookie("indicLangCookie");
    if(indicCookie) {

        cookieValue     = new String(indicCookie);
        languageIndex   = cookieValue.substring(0,cookieValue.indexOf("-"));
        cookieValue     = cookieValue.substring(cookieValue.indexOf("-")+1,cookieValue.length);

        var next = (cookieValue.indexOf("-") == -1) ? cookieValue.length : cookieValue.indexOf("-");
        showHint        = cookieValue.substring(0,next);

        if (next != cookieValue.length) {
            lastIndicLayout = cookieValue.substring(next + 1,cookieValue.length);
        }
        else {
            lastIndicLayout = languageIndex;
        }

        showHint = (showHint == "true")? "checked" : "";
    }

    form_element =
        "<div title=\"Language and Keyboard Layout\">" +
        "<select name=\"selectLanguage\" onChange=\"selectLang(this);\">" +
        "<option label=\"english\">English</option>";

    for(i=0;i<keyboard_layout_names.length;i++)
    {
        select_str = "";
        if (i == languageIndex - 1) {  //English is added directly, not from keyboard_layout_names
            select_str = "selected";   //select the language
            keyboardLabel = keyboard_layout_names[i][0];
        }
        form_element += "<option label='" + keyboard_layout_names[i][0] + "' " + select_str +
                        ">" + keyboard_layout_names[i][1] + "</option>";
    }

    form_element += 
        "</select>" +
        "</div>" +
        "<div title=\"Show keyboard layout hint while typing\"" +
             "id=\"div_keymap_hint\" style=\"display:none\">" +
        "<small>Show Hint</small>" +
        "<input type=\"checkbox\" name=\"chk_show_hint\" " + showHint + 
            " onClick=\"togHintMenu();\">" +
        "</div>" +
        "<div id=\"menudiv\"></div>";

    document.write(form_element);

    if(keyboardLabel != "english" && keyboardLabel != "")
        load(keyboardLabel);
}

/* 
Functions to Set and Get Cookies
*/

function getCookie(name) {
//alert("getCookie");
    var start = document.cookie.indexOf(name+"=");
    var len = start+name.length+1;
    if ((!start) && (name != document.cookie.substring(0,name.length)))
        return null;
    if (start == -1)
        return null;
    var end = document.cookie.indexOf(";",len);
    if (end == -1)
        end = document.cookie.length;
    return unescape(document.cookie.substring(len,end));
}

function setCookie(name,value,expires,path,domain,secure) {
  document.cookie = name + "=" +escape(value) +
        ( (expires) ? ";expires=" + expires.toGMTString() : "") +
        ( (path) ? ";path=" + path : "") +
        ( (domain) ? ";domain=" + domain : "") +
        ( (secure) ? ";secure" : "");
}

/*
used to find whether the character is an alphabet or not
*/
function isSplChar(chr)
{
//alert("isSplChar");
    var ascii = chr.charCodeAt(0);
    if(ascii < 65 || (ascii > 90 && ascii < 97) || ascii > 122)
        return true;
    return false;
}

/* this function will capture the event first and if the keys ctrl+space is
 * pressed, this will toggle the keyboard layout between between English and
 * the selected one
 */
function handle_event_keypress(event)
{
    var keyCode;

    if(!iexplore) {  //if the browser is not IE
        keyCode = (event.which) ? event.which : 0;

        if (event.ctrlKey && keyCode == 32)
        {
            switch_lang();
            
            if(event.preventDefault) //prevent its default functionality
                event.preventDefault();
            else
                event.returnValue = false;
        }
    }
    else if (window.event.ctrlKey) {
        if(window.event.preventDefault) //prevent its default functionality
            window.event.preventDefault();
        else
            window.event.returnValue = false;

        return;
    }

    //handle the key press
    eventCaptured(event);
}

/* IE recognises the ctrl+space combination at the time of keyup only */
function handle_event_keyup()
{
    var keyCode, event;
    
    if(iexplore) {  //if the browser is IE
        event = window.event;
        keyCode = event.keyCode;
    
        if (event.ctrlKey && keyCode == 32)
        {
            switch_lang();
            
            if(event.preventDefault) //prevent its default functionality
                event.preventDefault();
            else
                event.returnValue = false;
        }

        //this function will call freeze() which will do some cleanup 
        //of the state variables
        splEvent(event);
    }
}

//toggle between English keyboard layout and previously selected Indic
//keyboard layout
function switch_lang()
{
    var selectCombo, selectedIndex;

    selectCombo = document.getElementsByName("selectLanguage").item(0);
    if (selectCombo.selectedIndex == 0) {
        selectCombo.selectedIndex = lastIndicLayout;
    }
    else {
        selectCombo.selectedIndex = 0; //English
    }

    selectLang(selectCombo);  //to set the cookies
}

//this will take priority over the assignments at the top of this file, since these 
//are the latest assignment
document.onkeypress = handle_event_keypress;
document.onkeyup = handle_event_keyup;
