var oTextbox;


/**
 * An autosuggest textbox control.
 * @class
 * @scope public
 */
function AutoSuggestControl(oTextbox /*:HTMLInputElement*/,
                            oPopup /*:HTMLDivElement*/,
                            oProvider /*:SuggestionProvider*/) {
    /**
     * Suggestion provider for the autosuggest feature.
     * @scope private.
     */
    this.provider /*:SuggestionProvider*/ = oProvider;
    /**
     * The textbox to capture.
     * @scope private
     */
    this.textbox /*:HTMLInputElement*/ = oTextbox;
    /**
     * The popup to display suggestions.
     * @scope private
     */
    this.popup /*:HTMLDivElement*/ = oPopup;
    /**
     * A boolean indicating if the textbox has a suggestion.
     * @scope private
     */
    this.hasSuggestion = false;
    //initialize the control
    this.init();
}

/**
 * Autosuggests one or more suggestions for what the user has typed.
 * If no suggestions are passed in, then no autosuggest occurs.
 * @scope private
 * @param aSuggestions An array of suggestion strings.
 */
AutoSuggestControl.prototype.autosuggest = function (aSuggestions /*:Array*/,maxOptions/*:int*/) {
    //make sure there's at least one suggestion
    if (aSuggestions.length > 0) {
        this.hasSuggestion = true;
        this.typeAhead(aSuggestions[0]);
        this.showPopup(aSuggestions,maxOptions);
    } else {
        this.hasSuggestion = false;
        this.hidePopup();
    }
};

/**
 * Inserts a suggestion into the textbox, highlighting the 
 * suggested part of the text.
 * @scope private
 * @param sSuggestion The suggestion for the textbox.
 */
AutoSuggestControl.prototype.typeAhead = function (sSuggestion /*:String*/) {
    //check for support of typeahead functionality
    if (this.textbox.createTextRange || this.textbox.setSelectionRange){
        var iLen = this.textbox.value.length; 
        this.textbox.value = sSuggestion; 
        this.selectRange(iLen, sSuggestion.length);
    }
};

/**
 * Selects a range of text in the textbox.
 * @scope public
 * @param iStart The start index (base 0) of the selection.
 * @param iLength The number of characters to select.
 */
AutoSuggestControl.prototype.selectRange = function (iStart /*:int*/, iLength /*:int*/) {
    if (this.textbox.createTextRange) {
        //use text ranges for Internet Explorer
        var oRange = this.textbox.createTextRange(); 
        oRange.moveStart("character", iStart); 
        oRange.moveEnd("character", iLength - this.textbox.value.length);      
        oRange.select();
    } else if (this.textbox.setSelectionRange) {
        //use setSelectionRange() for Mozilla
        this.textbox.setSelectionRange(iStart, iLength);
    }     
    //set focus back to the textbox
    this.textbox.focus();      
}; 

/**
 * Handles textbox keyup events.
 * @scope private
 * @param oEvent The event object for the keyup event.
 */
AutoSuggestControl.prototype.handleTextboxKeyUp = function (oEvent /*:Event*/) {
    var iKeyCode = oEvent.keyCode;
    if (iKeyCode == 40 && this.popup.style.visibility == "") {
        //set focus to the select list in the popup on the down arrow
        this.popup.firstChild.focus();
    } else if (iKeyCode == 13) {
        //clear and set the focus to the textbox
        this.textbox.focus();
        //hide any popup
        this.hidePopup();
    } else if (iKeyCode == 8){
        //only remove additional character if a selection is present
        if(this.hasSuggestion){
            this.textbox.value = this.textbox.value.slice(0,this.textbox.value.length-1);
        }
        //request suggestions from the suggestion provider
        this.provider.requestSuggestions(this);
    } else if (iKeyCode == 46){
        //request suggestions from the suggestion provider
        this.provider.requestSuggestions(this);
    } else if (iKeyCode < 32 || (iKeyCode >= 33 && iKeyCode <= 45) || (iKeyCode >= 112 && iKeyCode <= 123)) {
        //ignore non-character keys
    } else if (iKeyCode == this.textbox.lastKeyCode) {
        //request suggestions from the suggestion provider
        this.provider.requestSuggestions(this);
        //clear the lastKeyCode on successful suggestion
        this.textbox.lastKeyCode = null;
    }
};

/**
 * Handles popup select key up events.
 * @scope private
 * @param oEvent The event object for the keyup event.
 */
AutoSuggestControl.prototype.handlePopupKeyup = function (oEvent /*:Event*/) {
    var iKeyCode = oEvent.keyCode;
    if (oEvent.keyCode == 38 && this.popup.firstChild.lastSelectedIndex == 0) {
        //set focus to the textbox when in the popup on the up arrow
        this.textbox.focus();
        this.textbox.select();
    } else if (iKeyCode == 13) {
        //add the item to the list, hide any popup and clear the textbox
        this.textbox.value = this.popup.firstChild.options[this.popup.firstChild.selectedIndex].text;
        //hide any popup
        this.hidePopup();
        this.textbox.focus();
    }
};

/**
 * Handles popup select mouse click events.
 * @scope private
 * @param oEvent The event object for the keyup event.
 */
AutoSuggestControl.prototype.handlePopupMouseClick = function (oEvent /*:Event*/) {
    //add the item to the list, hide any popup and clear the textbox
    this.textbox.value = this.popup.firstChild.options[this.popup.firstChild.selectedIndex].text;
    //hide any popup
    this.hidePopup();
    this.textbox.focus();
};

/**
 * Initializes the textbox with event handlers for
 * auto suggest functionality.
 * @scope private
 */
AutoSuggestControl.prototype.init = function () {
    //save a reference to this object
    var oThis = this;
    try
    {
        //textbox
        this.textbox.focus();
        this.textbox.lastKeyCode = null;
        //assign the onkeydown event handler
        this.textbox.onkeydown = function (oEvent) {
            //check for the proper location of the event object
            if (!oEvent) {
                oEvent = window.event;
            }    
            oThis.popup.firstChild.selectedIndex = 0;
            //store the down keyCode for comparison to the up keyCode
            oThis.textbox.lastKeyCode = oEvent.keyCode;
        };
        //assign the onkeyup event handler
        this.textbox.onkeyup = function (oEvent) {
            //check for the proper location of the event object
            if (!oEvent) {
                oEvent = window.event;
            }    
            //call the handleTextboxKeyUp() method with the event object
            oThis.handleTextboxKeyUp(oEvent);
        };
        //popup
        this.popup.firstChild.lastSelectedIndex = -1;
        //assign the onfocus event handler
        this.popup.firstChild.onfocus = function (oEvent) {
            //check for the proper location of the event object
            if (!oEvent) {
                oEvent = window.event;
            }    
            oThis.popup.firstChild.lastSelectedIndex = oThis.popup.firstChild.selectedIndex;
        }
        //assign the onblur event handler
        this.popup.firstChild.onblur = function (oEvent) {
            //check for the proper location of the event object
            if (!oEvent) {
                oEvent = window.event;
            }    
            oThis.popup.firstChild.selectedIndex = -1;
            oThis.popup.firstChild.lastSelectedIndex = -1;
        }
        //assign the onkeydown event handler
        this.popup.firstChild.onkeydown = function (oEvent) {
            //check for the proper location of the event object
            if (!oEvent) {
                oEvent = window.event;
            }    
            oThis.popup.firstChild.lastSelectedIndex = oThis.popup.firstChild.selectedIndex;
        }
        //assign the onkeyup event handler
        this.popup.firstChild.onkeyup = function (oEvent) {
            //check for the proper location of the event object
            if (!oEvent) {
                oEvent = window.event;
            }    
            //call the handleTextboxKeyUp() method with the event object
            oThis.handlePopupKeyup(oEvent);
        };
        //assign the onkeyup event handler
        this.popup.firstChild.onclick = function (oEvent) {
            //check for the proper location of the event object
            if (!oEvent) {
                oEvent = window.event;
            }    
            //call the handleTextboxKeyUp() method with the event object
            oThis.handlePopupMouseClick(oEvent);
        };
    }
    catch(error){}
};

/**
 * Removes suggestions from and hides the popup.
 * @scope private
 */
AutoSuggestControl.prototype.hidePopup = function () {
    var oSelect = this.popup.firstChild;
    this.clearSelect();
    this.popup.className  = "autosuggesthidden";
};

/**
 * Inserts suggestions into and shows the popup.
 * @scope private
 * @param aSuggestions An array of suggestion strings.
 */
AutoSuggestControl.prototype.showPopup = function (aSuggestions /*:Array*/,maxOptions/*:int*/) {
    //display complete suggestion list if there is more than one
    //if only one, it is represented in textbox
    if (aSuggestions.length > 0) {
        this.buildSelect(aSuggestions,maxOptions);
        this.popup.className  = "autosuggestvisible";
    } else {
        this.hidePopup();
    }
};

/**
 * Clears the select list in the popup.
 * @scope private
 */
AutoSuggestControl.prototype.clearSelect = function () {
    var oSelect = this.popup.firstChild;
    //spin through the select's options and remove them
    //using a while construct since the length changes on each remove
    while(oSelect.length > 0) {
        oSelect.remove(0);
    }
};

/**
 * Clears and insert new options into the select list.
 * @scope private
 * @param aSuggestions An array of suggestion strings.
 */
AutoSuggestControl.prototype.buildSelect = function (aSuggestions /*:Array*/,maxOptions/*:int*/) {
    var oSelect = this.popup.firstChild;
    var oOption = null;
    var optionLength;
    if (aSuggestions.length > maxOptions)
        optionLength = maxOptions;
    else
        optionLength = aSuggestions.length;
    //clear the select element
    this.clearSelect();
    //build the option list for the select
    for (var i=0; i < optionLength; i++) {
        oOption = document.createElement("option");
        oOption.className = "autosuggestoption";
        oOption.text = aSuggestions[i];
        try {
            oSelect.add(oOption, null); // standards compliant
        } catch(ex) {
            oSelect.add(oOption); // IE only
        }
    }
};