/*--------------------------------------------------------------------*
*                                                                     *
*  autocompleter.js                                                   *
*                                                                     *
*  Implements 'auto-complete/suggestion behavior' on a text input.    *
*  Uses Ajax to get a list of possible completions to a given initial * 
*  input value and displays those in a dropdown-like way below the    *
*  text input.                                                        *
*                                                                     *
*  Usage: add 'onkeyup' attribute to the text input                   *
*                                                                     *
*  onkeyup="AutoCompleter.TextChange(this,                            *
*                                       <SCRIPT URL>,                 *
*                                       <PARAM LIST>,                 *
*                                       <TEXT PARAMNAME>,             *
*                                       <ID PARAMNAME>,               *
*                                       <THRESHOLD>,                  *
*                                       <VALUE INPUTID?>,             *
*                                       event)"                       *
*                                                                     *
*                                                                     *
* <SCRIPT URL> the (relative) URL of the server-side ajax script that *
* will return values <PARAM LIST> a list of (fixed) script params     *
* that the script may require <TEXT PARAMNAME> the name of the script *
* param that holds the text types sofar <ID PARAMNAME> the name of    *
* the script param that holds the id of the text input that the event *
* originates from (the callback needs that) <THRESHOLD> the minimum   *
* length at which autocomplete starts: this is where the ajax call    *
* will be done. For longer inputs, the script will use client-side    *
* filtering <VALUE INPUTID?> (optional, put in NULL if not needed)    *
* the id of the form element that will hold the ID of the selected    *
* value.                                                              *
*                                                                     *
* Format of the response expected:                                    *
*                                                                     *
* The response is a string of the form:                               *
*                                                                     *
* <INPUT_ID>/<PREFIX>:<TEXT_1>;<ID_1>|...|<TEXT_n>;<ID_n>             *
*                                                                     *
* <INPUT_ID> is the ID of the text element that the event handler is  *
* bound to (the value of the request param ,<ID PARAMNAME>) <PREFIX>  *
* is the text typed sofar (so, the 'cache key' for this list), which  *
* is the value of <TEXT PARAMNAME>                                    *
*                                                                     *
* The possible values are to be returned as a pipe-delimited list of  *
* semi-colon separated pairs.                                         *
*                                                                     *
*---------------------------------------------------------------------*/       

       var AutoCompleter = 
       {
            // Maximum nr of results
            // don't show the option list if there are more than this.
            
            StoreListPrefix : function(txtElmId,sValue)
            {
                 //Debug.Print( "StoreListPrefix( [" + txtElmId  + "], [" + sValue + "] )" );
                 eval('window.acpr_' + txtElmId + '=\'' + sValue.toLowerCase() + '\'');
            },

            GetListPrefix : function(txtElmId)
            {
                var result = typeof(eval('window.acpr_' + txtElmId)) != 'undefined' ? eval('window.acpr_' + txtElmId) : '';
                //Debug.Print( "GetListPrefix: [" + result + "]" );
                return result;
            },

            StoreValueInputId : function(txtElmId, sValue)
            {
                 eval('window.vali_' + txtElmId + '=\'' + sValue + '\'');
            },

            GetValueInputId : function(txtElmId)
            {
                return typeof(eval('window.vali_' + txtElmId)) != 'undefined' ? eval('window.vali_' + txtElmId) : null;
            },

            StoreSelectedIndex : function(txtElmId, intValue)
            {
                 eval('window.seli_' + txtElmId + '=' + intValue);
            },

            GetSelectedIndex : function(txtElmId)
            {
                return typeof(eval('window.seli_' + txtElmId)) != 'undefined' ? eval('window.seli_' + txtElmId) : -1;
            },

            StoreOptionsArray : function(txtElmId,optArray)
            {
                eval('acoa_' + txtElmId + '= optArray');
            },
            GetOptionsArray : function(txtElmId)
            {
                return eval('window.acoa_' + txtElmId);
            },
            GetOptionsList : function(txtElmId)
            {
                return $('#' + txtElmId + '_acoptlist').get(0);
            },

            HideOptionsList : function(txtElmId)
            {
                var optList = this.GetOptionsList(txtElmId);
                if (optList)
                {
                    Debug.Print( "Hide option list" );
                    $(optList).hide();
                }
            },

            ShowOptionsList : function(txtElmId)
            {
                var txtElm = $('#' + txtElmId).get(0);
                var arrOptions = this.GetOptionsArray(txtElmId);
                var sCurrentText = txtElm.value.toLowerCase()
                // insert the matching options list into the DL
                var sHTML = '';
                var optText = '';
                var optValue = '';
                var a;
                var intJ = 0;
                var optionsToShow = new Array();

                for (var intI = 0; intI < arrOptions.length; intI ++)
                {
                    a = arrOptions[intI].split(';');
                    optText = a[0];
                    optValue = a[1];
                    if (optText.toLowerCase().substring(0,sCurrentText.length) == sCurrentText)
                    {
                        optionsToShow[intJ++] = optText; 
                    };
                };

                Debug.Print( "Nr of options: " + intJ );

                if( intJ == 0 ) {
                    this.HideOptionsList(txtElmId);
                    return;
                }
                
                 // if optList (DL) does not exist, create the DL now
                var optList = this.GetOptionsList(txtElmId);
                if (!optList)
                {
                    var locSelect = document.createElement("select");
                    locSelect.setAttribute("style", "position:absolute;left:-1000px;top:0px;display:none;");
                    locSelect.setAttribute("id", txtElmId + '_acoptlist');
                    locSelect.setAttribute("size", "4");
                    locSelect.onchange = function(e) {
                        AutoCompleter.Select( txtElmId );
                    };

                    document.body.appendChild(locSelect);
                        optList = this.GetOptionsList(txtElmId);
                }

                // remove all existing options
                optList.options.length = 0;
                for( var i = 0; i < intJ; i++ ) { 
                    optList.options[i] = new Option( optionsToShow[i], optionsToShow[i] );
                }
                // do not choose anything, otherwise the onChange doesn't fire
                optList.selectedIndex = -1;
                // position the optionslist underneath the text element
                var txtPos = $(txtElm).offset();
                $(optList).css("left", txtPos.left + 'px').css('top', 23 + txtPos.top + 'px').css('display', 'block').css('position', 'absolute');
            },

            TextChange: function(txtElm, sScript, sParamList, sTextParamName, sIdParamName, iMinLength, strValueInputId, e)
              {
                var code;
                if (e.keyCode) code = e.keyCode;
                else if (e.which) code = e.which;

                var txtElmId = txtElm.id;

                if (strValueInputId != null)
                {
                    this.StoreValueInputId(txtElmId,strValueInputId);
                };

                if (txtElm.value.length >= iMinLength)
                {
                    // see if we have an option list that still matches the prefix
                    var sCurrentPrefix = this.GetListPrefix(txtElmId);
                    if ( sCurrentPrefix == '' ||  txtElm.value.substring(0,sCurrentPrefix.length).toLowerCase() != sCurrentPrefix )
                    {
                      // txtElm.disabled=true;
                        // send asynchronous request to get a new options array
                      $.get(sScript, sParamList + '&' + sTextParamName + '=' + txtElm.value + '&' + sIdParamName + '=' + txtElm.id, AutoCompleter.HandleAjaxResponse);
                      // stop executing on this thread: wait for the ajax callback
                      return 0;
                    };

                    var optList = this.GetOptionsList(txtElmId);
                    if( optList == null ) {
                        this.ShowOptionsList(txtElmId);
                        return 0;
                    }

                    //var lastOptIndex = optList.childNodes.length - 1;

                    var seli = this.GetSelectedIndex(txtElmId);
                    Debug.Print('keycode = '+ code);
                    // handle 'cursor' keys
                    switch(code)
                       {
                           case 27 : // esc
                                this.Exit(txtElmId, null);
                                break;
                           case 13 : // enter
                               if (seli != -1)
                                {
                                    this.Select(txtElmId);
                               };
                                break;
                           case 36 : // end
                           case 33 : // page up
                               if (seli != 0)
                                {
                                    //this.OptionHover(txtElmId, 0);
                                };
                                break;
                           case 35 : // home
                           case 34 : // page down
                               //if (seli != lastOptIndex)
                               // {
                                    //this.OptionHover(txtElmId, lastOptIndex);
                               // };
                                break;
                           case 38 : // arrow up
                               if (seli > 0)
                                {
                                    //this.OptionHover(txtElmId, seli - 1);
                                };
                                break;
                           case 40 : // arrow down
                               //if (seli < lastOptIndex)
                               // {
                                    //this.OptionHover(txtElmId, seli + 1);
                                //};
                                break;
                           default : 
                                this.StoreSelectedIndex(txtElmId,-1);
                                this.ShowOptionsList(txtElmId); 
                                break;
                    };
                }
                else
                 {
                   this.HideOptionsList(txtElmId)
                 }
            },

            HandleAjaxResponse : function(sResponse)
            {
                // split response on colon into form element id and options list
                if (sResponse.indexOf(':') != -1)
                {
                    var txtElmId = sResponse.substring(0,sResponse.indexOf('/')) ;
                    var optPrefix = sResponse.substring(sResponse.indexOf('/') + 1,sResponse.indexOf(':')) ;
                    $('#' + txtElmId).removeAttr("disabled");
                    AutoCompleter.StoreListPrefix(txtElmId, optPrefix);

                    var sOptArray = sResponse.substring(sResponse.indexOf(':') + 1, sResponse.length ); 
                    var optArray; 
                    if( sOptArray == "" ) {
                        optArray = new Array();
                    }
                    else {
                        optArray = sOptArray.split('|');
                    }

                    // save the options (for this input)
                    AutoCompleter.StoreOptionsArray(txtElmId,optArray);

                    // fill the options list with these new options
                    AutoCompleter.ShowOptionsList(txtElmId);
                }
            },

            Exit: function(txtElmId, e)
            {
                var code;
                if (e)
                {
                    if (e.keyCode) code = e.keyCode;
                    else if (e.which) code = e.which;
                };
                var optList = this.GetOptionsList(txtElmId);
                var seli = this.GetSelectedIndex(txtElmId);
                if (seli != -1)
                {
                    optList.childNodes[seli].firstChild.className  = 'acOut';
                };
                seli = -1;
                eval('seli_' + txtElmId + '=' + seli);
                $(optList).hide();
           },

            Select: function(txtElmId)
            {
                var select = $('#' + txtElmId + '_acoptlist').get(0);
                Debug.Print( "Selected index: [" + select.options.selectedIndex + "]");
                var option = select.options[select.selectedIndex];
                Debug.Print( "option: [" + option + "]" );
                $('#' + txtElmId).val(option.text);
                var vali = typeof (eval('window.vali_' + txtElmId)) != 'undefined' ? eval('window.vali_' + txtElmId) : null;
                if (vali != null)
                {
                    $('#' + vali).val(option.value);
                };
                this.Exit(txtElmId, null);
           }
       }; 

    var Debug = 
    {
        Enabled : false,
        Print : function(sMessage)
        {
            if (!this.Enabled)
            {
                return 0;
            };
            if (!$('#pipodebug').length)
            {
                var dta = $.create("textarea").attr("rows", "16").attr("cols", "80").attr("id", 'pipodebug')
                    .attr("style", 'position:absolute;top:400px;left:400px;background-color:black;color:#44ff44;font-weight:bold')
                    .css('position', 'absolute').css('top', '400px').css('left', '400px')
                    .css('background-color', 'black').css('color', '#44ff44');
                document.body.appendChild(dta.get(0));
            };
            $('#pipodebug').val(sMessage + '\n' + $('#pipodebug').val());
        }
    };