/*
To use this you will need to ensure that your page has references to this file, Slide.js, Helper.js and Validation.js.
You can then instanciate the class using the required valuess and then set the custom messages on the object.
*/

function Autocomplete(textbox, listbox, originalList, speechBubble, speechBubbleText, matchesFoundArrow, noMatchesFoundArrow, slidePanel, slideContent, slideHeight, validator) 
{
    var OnListBoxSlideCompleteCallBackMethod = null;
    var OnListBoxSlideCancelCallBackMethod = null;
    var OnListBoxBeginSlideCallBackMethod = null;

    this.ItemSelectedMethod = null;

    var me = this;

    this.Textbox = document.getElementById(textbox);
    this.Validator = document.getElementById(validator);
    this.Listbox = document.getElementById(listbox);
    this.OriginalList = document.getElementById(originalList);
    this.SpeechBubble = document.getElementById(speechBubble);
    this.SpeechBubbleText = document.getElementById(speechBubbleText);
    
    this.NoMatchesFoundSpeechBubble = null;
    this.NoMatchesFoundSpeechBubbleText = null;
    
    this.MatchesFoundArrow = null;
    this.NoMatchesFoundArrow = null;
    
    if (matchesFoundArrow != null)
	{
        this.MatchesFoundArrow = document.getElementById(matchesFoundArrow);
        this.NoMatchesFoundArrow = document.getElementById(noMatchesFoundArrow);
    }
    
    this.SlideContent = document.getElementById(slideContent);

    this.Slider = new Slide(slidePanel, slideContent, slideHeight)

    this.Slider.OnSlideComplete(Slider_OnSlideComplete);
    this.Slider.OnSlideCancel(Slider_OnSlideCancel);
    this.Slider.OnBeginSlide(Slider_OnBeginSlide);
    
    this.CurrentArrow;
    this.PreviousSearchTimeoutID = '';
    this.PreviousSearchText = '';
    this.PreviousSelectedValue = '';
    
    this.FocusElement;
    
    this.MatchOnValue = false;
    
    //Custom messages.
    this.NoValueFoundListText = "No value found.";
    this.ValueSelectedHelpText = "A value has been successfully selected from the list.";
    this.StartTypingHelpText = "Start typing your value to search our list of values.";
    this.NoMatchesFoundHelpText = "<div style='margin-bottom: 5px;' class='title'>0 matches found</div>  Try a less specific description and choose the value.";
    this.OneMatchFoundHelpText = "<div style='margin-bottom: 5px;' class='title'>1 match found</div>  Please select the value from the list.";
    this.MultipleMatchesFoundHelpText1 = "<div style='margin-bottom: 5px;' class='title'>";
    this.MultipleMatchesFoundHelpText2 = " matches found</div>  Please select the value from the list.";
    
    this.InPopUp = false;
    this.ListboxItemSelected = false;
    this.ShowNoMatches = false;
    this.HelpStaysOnBlurOverride = false;

    this.Textbox_OnFocus = function()
    {
        me.FocusElement = me.Textbox;
        if (me.Listbox.selectedIndex != -1)
        {
            me.SpeechBubbleText.innerHTML = me.ValueSelectedHelpText;
            me.displayHelp(null, this.InPopUp, false);
        }
        else if (me.Textbox.value.length < 3)
        {
            me.SpeechBubbleText.innerHTML = me.StartTypingHelpText;
            me.displayHelp(null, this.InPopUp, false);
        }
        else if (me.Listbox.options[0].text == me.NoValueFoundListText)
        {
            me.SpeechBubbleText.innerHTML = me.NoMatchesFoundHelpText;
            if (me.NoMatchesFoundSpeechBubbleText != null)
                me.NoMatchesFoundSpeechBubbleText.innerHTML = me.NoMatchesFoundHelpText;
            me.displayHelp(me.NoMatchesFoundArrow, this.InPopUp, true);
        }
        else if (me.Listbox.options.length == 1)
        {
            me.SpeechBubbleText.innerHTML = me.OneMatchFoundHelpText;
            me.displayHelp(me.MatchesFoundArrow, this.InPopUp, false);
        }
        else
        {
            me.Slider.BeginSlide();
            me.SpeechBubbleText.innerHTML = me.MultipleMatchesFoundHelpText1 + me.Listbox.options.length + me.MultipleMatchesFoundHelpText2;
            me.displayHelp(me.MatchesFoundArrow, this.InPopUp, false);
        }
    }
    
    this.Textbox_OnBlur = function()
    {
	    // Simply hide everything
	    if ((me.Listbox.options[0].text != me.NoValueFoundListText || me.ShowNoMatches == false) && me.HelpStaysOnBlurOverride == false)
	        me.hideHelp();	    
    }

    this.Textbox_OnKeyUp = function(e)
    {
		var characterCode;

		try
		{
			characterCode = (e.which) ? e.which : event.keyCode
		}
		catch ( ex )
		{
		}

	    // Allow Tab & Shift keys
	    if ((characterCode != 9) && (characterCode != 16))
		{
	        me.checkSlide(); 
	        me.filterList(false, 400, false, true);
		}
    }

    this.Listbox_OnFocus = function()
    {
		me.FocusElement = me.Listbox;
	    if (me.Listbox.selectedIndex == -1 && me.Listbox.length == 1)
	    {
		    // Select 1st row on focus if nothing selected and only one item
		    me.Listbox.selectedIndex = 0;
    		
		    // Populate the search box
		    me.populateSearchBox();
	    }
    }
    
    this.Listbox_OnBlur = function()
    {
        if (me.populateSearchBox())
            me.Slider.CancelSlide();
    }

    this.Listbox_OnClick = function()
    {
        if (me.populateSearchBox())
            me.Slider.CancelSlide();
    }

    this.Listbox_OnKeyUp = function()
    {
        me.populateSearchBox();
    }

    this.Listbox_OnKeyDown = function()
    {
        buttonClickEvent(me.Listbox.id);
    }
    
    // Sets the timeout for when the list filtering will begin,
    this.filterList = function(addAll, timeout, alwaysSelect, displayHelp)
    {
	    if (this.PreviousSearchTimeoutID != '')
		    clearTimeout(this.PreviousSearchTimeoutID);

	    if (alwaysSelect)
	    {
		    var me = this;
    		var timeoutFunc = new function () { me.performFilter(addAll, alwaysSelect, displayHelp) };
    		this.PreviousSearchTimeoutID = setTimeout(timeoutFunc, timeout);
    	}
	    else if (timeout == 0 || this.Textbox.value.length < 3)
		    this.performFilter(addAll, alwaysSelect, displayHelp);
	    else
	    {
		    if (this.Textbox.value.length >= 3)
			    if (this.Textbox.value != this.PreviousSearchText)
				    this.loadingList();
    		
    		var me = this;
    		this.PreviousSearchTimeoutID = setTimeout(function () { me.performFilter(addAll, alwaysSelect, displayHelp) }, timeout);
	    }
    	
	    this.PreviousSearchText = this.Textbox.value;
    }

    this.Initialise = function()
    {
        //Resize and scroll events.
        var me = this;
        var fn = new function() { if (me.SpeechBubble.style.display == 'block') me.displayHelp(me.CurrentArrow, me.InPopUp, false); };
        addEvent(window, "resize", fn);
        addEvent(window, "scroll", fn);

        if (me.NoMatchesFoundSpeechBubble != null)
        {
            var fn1 = new function() { if (me.NoMacthesFoundSpeechBubble.style.display == 'block') me.displayHelp(me.CurrentArrow, me.InPopUp, true); };
            addEvent(window, "resize", fn1);
            addEvent(window, "scroll", fn1);
        }

        //Text box events.
        addEvent(this.Textbox, "focus", this.Textbox_OnFocus);
        addEvent(this.Textbox, "blur", this.Textbox_OnBlur);
        addEvent(this.Textbox, "keyup", this.Textbox_OnKeyUp);

        //Listbox events.
        addEvent(this.Listbox, "focus", this.Listbox_OnFocus);
        addEvent(this.Listbox, "blur", this.Listbox_OnBlur);
        addEvent(this.Listbox, "click", this.Listbox_OnClick);
        addEvent(this.Listbox, "doubleclick", this.Listbox_OnClick);
        addEvent(this.Listbox, "keyup", this.Listbox_OnKeyUp);
        addEvent(this.Listbox, "keydown", this.Listbox_OnKeyDown);

        if (this.Listbox.toString().indexOf('popUp') != -1)
            this.InPopUp = true;
    }

    this.Initialised = this.Initialise();

    this.OnItemSelected = function(callBackMethod)
    {
        this.ItemSelectedMethod = callBackMethod;
    }
    
    this.OnBeginSlide = function(callBackMethod)
    {
        OnListBoxBeginSlideCallBackMethod = callBackMethod;
    }

    this.OnSlideComplete = function(callBackMethod)
    {
        OnListBoxSlideCompleteCallBackMethod = callBackMethod;
    }

    this.OnSlideCancel = function(callBackMethod)
    {
        OnListBoxSlideCancelCallBackMethod = callBackMethod;
    }       

    function Slider_OnSlideComplete() 
    {
        if (OnListBoxSlideCompleteCallBackMethod != null) OnListBoxSlideCompleteCallBackMethod();
    }   

    function Slider_OnSlideCancel() 
    {
        if (OnListBoxSlideCancelCallBackMethod != null) OnListBoxSlideCancelCallBackMethod();
    }     
    
    function Slider_OnBeginSlide() 
    {
        if (OnListBoxBeginSlideCallBackMethod != null) OnListBoxBeginSlideCallBackMethod();
    }      
}

Autocomplete.prototype.hideHelp = function()
{
	// Hide all help related images
	this.SpeechBubble.style.display = 'none';
	if (this.NoMatchesFoundSpeechBubble != null)
	    this.NoMatchesFoundSpeechBubble.style.display = 'none';    
	
	if (this.MatchesFoundArrow != null)
	{
	    this.MatchesFoundArrow.style.display = 'none';
	    this.NoMatchesFoundArrow.style.display = 'none';
    	
	    // Reset global variables
	    this.CurrentArrow = null;
	}
}

Autocomplete.prototype.checkSlide = function()
{
	if ((this.Textbox.value.length >= 3 || this.ListboxItemSelected) && this.SlideContent.style.display != 'block')
		this.Slider.BeginSlide();
}

// Populates the text box when an entry in the list is selected.
Autocomplete.prototype.populateSearchBox = function()
{
    if (this.Listbox.selectedIndex != -1)
    {
        var selectedText = this.Listbox[this.Listbox.selectedIndex].text;
        if (selectedText != null)
        {
            if (this.Listbox[this.Listbox.selectedIndex].value != '')
            {
                if (selectedText != this.NoValueFoundListText)
                {
                    this.Textbox.value = selectedText;
                    this.ListboxItemSelected = true;

                    var selectedValue = this.Listbox[this.Listbox.selectedIndex].value;

                    // As we've just selected an item, raise and event selected method
                    if (this.ItemSelectedMethod != null && this.PreviousSelectedValue != selectedValue)
                    {
                        this.ItemSelectedMethod(selectedValue);
                    }

                    this.PreviousSelectedValue = selectedValue;

                    if (this.Validator)
                        validatorValidate(this.Validator);
                    return true;
                }
                else
                {
                    this.Listbox.selectedIndex = -1;
                    this.ListboxItemSelected = false;
                    return false;
                }
            }
        }
    }
}

// Filters the list box.
Autocomplete.prototype.performFilter = function(addAll, alwaysSelect, displayHelp)
{
    if (this.Listbox.options.length != this.OriginalList.options.length || this.Textbox.value.length > 2 || this.OriginalList.options.length == 1)
    {
        var filterList = true;
        if (this.Listbox.selectedIndex != -1)
        {
            if (this.MatchOnValue == false)
            {
                if (this.Textbox.value == this.Listbox[this.Listbox.selectedIndex].text)
                    filterList = false;
            }
            else
            {
                if (this.Textbox.value == this.Listbox[this.Listbox.selectedIndex].value)
                    filterList = false;
            }
        }

        if (filterList)
        {
            for (var i = (this.Listbox.options.length - 1); i > -1; i--)
            {
                this.Listbox.options[i] = null;
            }

            if (this.Textbox.value.length > 2)
            {
                for (var i = 0; i < this.OriginalList.options.length; i++)
                {
                    if (this.MatchOnValue == false)
                    {
                        if ((this.OriginalList.options[i].text.toUpperCase().indexOf(this.Textbox.value.toUpperCase()) != -1) || addAll)
                        {
                            var y = document.createElement('option');
                            y.value = this.OriginalList.options[i].value;
                            y.text = this.OriginalList.options[i].text;

                            this.addListItem(y);
                        }
                    }
                    else
                    {
                        if ((this.OriginalList.options[i].value.toUpperCase().indexOf(this.Textbox.value.toUpperCase()) != -1) || addAll)
                        {
                            var y = document.createElement('option');
                            y.value = this.OriginalList.options[i].value;
                            y.text = this.OriginalList.options[i].text;

                            this.addListItem(y);
                        }
                    }
                }

                //Now select the right item if there's only one in the list or the onload event has called the function.
                if (this.Listbox.options.length == 1 || alwaysSelect)
                {
                    for (var x = (this.Listbox.options.length - 1); x > -1; x--)
                    {
                        if (this.MatchOnValue == false)
                        {
                            if (this.Listbox.options[x].text.toUpperCase() == this.Textbox.value.toUpperCase())
                            {
                                this.Listbox.options[x].selected = true;
                                this.Textbox.value = this.Listbox.options[x].text;
                                this.PreviousSearchText = this.Textbox.value;

                                this.ListboxItemSelected = true;

                                var selectedValue = this.Listbox.options[x].value;

                                // As we've just selected an item, raise and event selected method
                                if (this.ItemSelectedMethod != null && this.PreviousSelectedValue != selectedValue)
                                    this.ItemSelectedMethod(selectedValue);

                                this.PreviousSelectedValue = selectedValue;
                            }
                        }
                        else
                        {
                            if (this.Listbox.options[x].value.toUpperCase() == this.Textbox.value.toUpperCase())
                            {
                                this.Listbox.options[x].selected = true;
                                this.Textbox.value = this.Listbox.options[x].text;
                                this.PreviousSearchText = this.Textbox.value;

                                this.ListboxItemSelected = true;

                                var selectedValue = this.Listbox.options[x].value;

                                // As we've just selected an item, raise and event selected method
                                if (this.ItemSelectedMethod != null && this.PreviousSelectedValue != selectedValue)
                                    this.ItemSelectedMethod(selectedValue);

                                this.PreviousSelectedValue = selectedValue;
                            }
                        }

                    }
                }

                // Set up speech bubble and images
                if (this.Listbox.options.length == 0)
                {
                    this.SpeechBubbleText.innerHTML = this.NoMatchesFoundHelpText;
                    if (this.NoMatchesFoundSpeechBubbleText != null)
                        this.NoMatchesFoundSpeechBubbleText.innerHTML = this.NoMatchesFoundHelpText;
                }
                else if (this.Listbox.options.length == 1)
                    this.SpeechBubbleText.innerHTML = this.OneMatchFoundHelpText;
                else
                    this.SpeechBubbleText.innerHTML = this.MultipleMatchesFoundHelpText1 + this.Listbox.options.length + this.MultipleMatchesFoundHelpText2;

                if (this.Listbox.options.length != 0)
                {
                    if (this.FocusElement == this.Textbox)
                    {
                        if (displayHelp)
                            this.displayHelp(this.MatchesFoundArrow, this.InPopUp, false);
                    }
                }
                else
                {
                    // No values match the criteria so add a dummy record
                    var noMatch = document.createElement('option');
                    noMatch.value = '';
                    noMatch.text = this.NoValueFoundListText;

                    this.addListItem(noMatch);

                    if (displayHelp)
                        this.displayHelp(this.NoMatchesFoundArrow, this.InPopUp, true);

                }
            }
            else
            {
                for (var i = 0; i < this.OriginalList.options.length; i++)
                {
                    var y = document.createElement('option');
                    y.text = this.OriginalList.options[i].text;
                    y.value = this.OriginalList.options[i].value;

                    this.addListItem(y);
                }

                this.SpeechBubbleText.innerHTML = this.StartTypingHelpText;

                if (this.MatchesFoundArrow != null)
                {
                    this.MatchesFoundArrow.style.display = 'none';
                    this.NoMatchesFoundArrow.style.display = 'none';
                }

                this.ListboxItemSelected = false;

                if (displayHelp)
                    this.displayHelp(null, this.InPopUp, false);
            }
        }
    }
}

Autocomplete.prototype.displayHelp = function(arrowElement, inPopUp, noMatchesFound)
{
	// Hide everything first
	this.hideHelp();
	
	// Position speech bubble element and display
	var xPosTextbox = getAscendingLefts(this.Textbox);
	var yPosTextbox = getAscendingTops(this.Textbox);
	var xPosListbox = getAscendingLefts(this.Listbox);
	var yPosListbox = getAscendingTops(this.Listbox);
	
	var textContainer = this.SpeechBubble;
	if (noMatchesFound)
	{
	    if (this.NoMatchesFoundSpeechBubble != null)
            textContainer = this.NoMatchesFoundSpeechBubble;
    }
	
	textContainer.style.zIndex = '9999';
	textContainer.style.position = 'absolute';
	textContainer.style.display = 'block';
	
	var xPosBubble = 0;
	var yPosBubble = 0;
	
	if (this.MatchesFoundArrow != null)
	{
	    xPosBubble = parseInt(xPosTextbox, 10) + parseInt(this.Textbox.offsetWidth, 10);
	    yPosBubble = parseInt(yPosTextbox, 10) - parseInt(textContainer.offsetHeight, 10) + 20;
	    
	    // Need to slightly change if in a pop up?
	    if (inPopUp)
	    {
		    xPosBubble = parseInt(xPosBubble, 10) + 3;
		    yPosBubble = parseInt(yPosBubble, 10) + 2;
	    }
	}
	else
	{
	    if (this.Slider.IsVisible())
	    {
            xPosBubble = parseInt(xPosListbox, 10) - parseInt(textContainer.offsetWidth, 10) - 15;
	        yPosBubble = parseInt(yPosListbox, 10);
	    }
	    else
	    {
	        xPosBubble = parseInt(xPosTextbox, 10) - parseInt(textContainer.offsetWidth, 10) - 15;
	        yPosBubble = parseInt(yPosTextbox, 10);
	    }
	}
	
	textContainer.style.left = xPosBubble + 'px';
	textContainer.style.top = yPosBubble + 'px';
	
	// If we have an arrow element display it
	if (arrowElement != null)
	{
	    this.CurrentArrow = arrowElement;
	    
		var xPosArrow = parseInt(xPosTextbox, 10) + parseInt(this.Textbox.offsetWidth, 10);
		var yPosArrow = parseInt(yPosTextbox, 10) - 4;
		
		// Need to slightly change if in a pop up?
		if (inPopUp)
		{
			xPosArrow = parseInt(xPosArrow, 10) + 3;
			yPosArrow = parseInt(yPosArrow, 10) + 2;
		}
	
		arrowElement.style.left = xPosArrow + 'px';
		arrowElement.style.top = yPosArrow + 'px';
		arrowElement.style.zIndex = '9998';
		arrowElement.style.position = 'absolute';
		arrowElement.style.display = 'block';
	}
}

// Clears the items and indicates that the list is reloading.
Autocomplete.prototype.loadingList = function()
{
	this.Listbox.length = 0;
	
	var y = document.createElement('option');
	y.value = '';
	y.text = "Loading list...";
	
	this.addListItem(y);
}

// Adds a list item to a list
Autocomplete.prototype.addListItem = function(listItem)
{
	try
	{
		this.Listbox.add(listItem, null);
	}
	catch (e)
	{
		this.Listbox.add(listItem);
	}
}
