// Autosuggest textbox control
// Param1: oTextbox	- HTMLInputElement
// Param2: oProvider - SuggestionProvider
function AutoSuggestControl(oTextbox, oProvider, oUrl, oType, oMinChars, oHidden, fOnblur, fOnfocus)
{
	this.keyPending = 0;		// Determine if another key is being processed
	this.lastKey = 0;			// Last key pressed - used to determine repeated keys
	this.cur = 0;				// currently selected suggestion
	this.layer = null;			// dropdown list layer
	this.provider = oProvider;
	this.textbox = oTextbox;	// which textbox we are suggesting for
	this.textbox.setAttribute("autocomplete", "off");	// Turn off the browser's autocomplete feature
	this.userInput = "";		// a copy of what is considered the user input
	this.retrievedAll = "";		// Contains the first char(s) of the user-entered text for which we
								// have retrieved a "complete" list of suggestions.  Therefore, if not empty,
								// then we know we don't have to make a server call to get suggestions
								// if the user input still starts with this char(s).
	this.suggestions = [];		// holds any "complete" list of suggestions from the server
	this.url = oUrl;			// the url that the request will call to perform query - include the "?" on the url
	this.type = oType;			// holds the type of request (e.g., "place", "name")
	this.hiddenField = oHidden;		// holds the hidden input field into which we will place the value of the selected item
	this.myOnblur = fOnblur;	// holds the method to be invoked when focus from this control is lost
	this.myOnfocus = fOnfocus;	// holds the method to be invoked when focus is set to this control
	this.minChars = oMinChars;
	this.init();

	this.ignoreBodyPosition = ( 8 < arguments.length && arguments[8] );
}

// Autosuggests one or more strings for what the user has typed so far
// Param1: aSuggestions - Array of suggested strings
// Param2: bTypeAhead - boolean (if true then provide a type-ahead suggestion
AutoSuggestControl.prototype.autosuggest = function (aSuggestions, bTypeAhead)
{
	// make sure there's at least one suggestion
	if (aSuggestions != null && aSuggestions.length > 0)
	{
		this.showSuggestions(aSuggestions);
		if (bTypeAhead)
		{
			// get the text & hidden portions of the suggestion
			var parts = aSuggestions[0].split("^");
			var text = parts[0];
			var hiddenValue = "";
			if (parts.length > 1)
			{
				hiddenValue = parts[1];
			}
			this.typeAhead(text, hiddenValue);
		}
	} else
	{
		this.populateHidden("");
		this.hideSuggestions();
	}
}

// Create the dropdown to display multiple suggestions
AutoSuggestControl.prototype.createDropDown = function ()
{
	var oThis = this;

	//create the layer and assign styles
	this.layer = document.createElement("div");
	this.layer.className = "suggestions";
	this.layer.style.visibility = "hidden";

    // when the user clicks on the a suggestion, get the text (innerHTML)
    // and place it into a textbox
	this.layer.onmousedown = this.layer.onmouseup = this.layer.onmouseover = function (oEvent)
	{
		oEvent = oEvent || window.event;
		oTarget = oEvent.target || oEvent.srcElement;

		if (oEvent.type == "mousedown")
		{
			oThis.textbox.value = oTarget.firstChild.nodeValue;
			var inputFields = oTarget.getElementsByTagName("input");
			if (inputFields.length > 0)
			{
				oThis.populateHidden (inputFields[0].value);
			}
			this.userInput = oThis.textbox.value;
			oThis.hideSuggestions();
		} else if (oEvent.type == "mouseover")
		{
			oThis.highlightSuggestion(oTarget);
		} else
		{
			oThis.textbox.focus();
		}
	}
	document.body.appendChild(this.layer);
}

// Populate the hidden property for this object
AutoSuggestControl.prototype.populateHidden = function (value)
{
	if (this.hiddenField != null)
	{
		this.hiddenField.value = value;
	}
}

// Get the "left" coordinate of the textbox
AutoSuggestControl.prototype.getLeft = function ()
{
	var oNode = this.textbox;
	var iLeft = 0;

	while ( oNode )
	{
		iLeft += ( this.ignoreBodyPosition && "body" == oNode.tagName.toLowerCase() ) ? 1 : oNode.offsetLeft;
		oNode = oNode.offsetParent;        
	}
	return iLeft;
}

// Get the "top" coordinate of the textbox
AutoSuggestControl.prototype.getTop = function ()
{
	var oNode = this.textbox;
	var iTop = 0;

	while ( oNode )
	{
		iTop += ( this.ignoreBodyPosition && "body" == oNode.tagName.toLowerCase() ) ? 1 : oNode.offsetTop;
		oNode = oNode.offsetParent;        
	}
	return iTop;
}

// Handle three keydown events
AutoSuggestControl.prototype.handleKeyDown = function (oEvent)
{
	// See if the key is being repeated
	if (this.lastKey != oEvent.keyCode && oEvent.keyCode != 9)
	{
		// Not being repeated - Synchronize keydown and keyup events
		this.keyPending++;
	}
	// Remember the last key pressed
	this.lastKey = oEvent.keyCode;

	switch(oEvent.keyCode)
	{
//		case 73: // i
//			alert("[handleKeyDown]");
//			break;
		case 38: // up arrow
			this.previousSuggestion();
			break;
		case 40: // down arrow 
			this.nextSuggestion();
			break;
		case 27: // escape
			this.textbox.value = "";
			this.populateHidden("");
			this.hideSuggestions();
			this.textbox.focus();
			break;
		case 13: //enter
			this.hideSuggestions();
		//	oEvent.keyCode = 9;	// just tab out of the drop-down, don't send a form submit
			break;
	}
}

// Handle keyup events
AutoSuggestControl.prototype.handleKeyUp = function (oEvent)
{
	// Clear this so we can distinguish key repeat events
	this.lastKey = 0;
	// Only handle the keyup if there are not already other keystrokes being handled
	this.keyPending--;
	if ( 0 > this.keyPending)
		this.keyPending = 0;

	if (18 == oEvent.keyCode)	// Handle Alt key up
    {
		this.provider.requestSuggestions(this, true);
	}
	else if (0 == this.keyPending
		|| (oEvent.ctrlKey && 86 == oEvent.keyCode)) // Handle ctrol-V specially
	{
		var iKeyCode = oEvent.keyCode;

		if (iKeyCode == 8 || iKeyCode == 46)
		{
			// for backspace (8) and delete (46), show suggestions without typeahead
			this.userInput = this.textbox.value;
			this.populateHidden("");
			if (this.userInput.length >= this.minChars)
			{
				this.provider.requestSuggestions(this, false);
			}
			else
				this.hideSuggestions();	// Hide the suggestions when there is nothing left in the textbox
		} else if (iKeyCode < 32 || (iKeyCode >= 33 && iKeyCode < 46) || (iKeyCode >= 112 && iKeyCode <= 123))
		{
			// ignore non-character keys
		} else
		{
			// request suggestions from the suggestion provider with typeahead
			this.userInput = this.textbox.value;
			this.provider.requestSuggestions(this, true);
		}
    }
}

// Hide the suggestion dropdown
AutoSuggestControl.prototype.hideSuggestions = function ()
{
    this.layer.style.visibility = "hidden";
}

// Highlight the given node in the suggestions dropdown
// Param1: oSuggestionNode - The node representing a suggestion in the dropdown
AutoSuggestControl.prototype.highlightSuggestion = function (oSuggestionNode)
{
	for (var i=0; i < this.layer.childNodes.length; i++)
	{
		var oNode = this.layer.childNodes[i];
		if (oNode == oSuggestionNode)
		{
			oNode.className = "current";
		} else if (oNode.className == "current")
		{
			oNode.className = "";
		}
	}
}

// Initialize the textbox with event handlers for auto suggest
AutoSuggestControl.prototype.init = function ()
{
    var oThis = this;
	// assign handler for the onkeyup event
	this.textbox.onkeyup = function (oEvent)
	{
		// check for the proper location of the event object
		if (!oEvent) oEvent = window.event;

		// call the handleKeyUp() method with the event object
		oThis.handleKeyUp(oEvent);
	}

	// assign handler for the onkeydown event
	this.textbox.onkeydown = function (oEvent)
	{
		// check for the proper location of the event object
		if (!oEvent) oEvent = window.event;

		// call the handleKeyDown() method with the event object
		oThis.handleKeyDown(oEvent);
	}

    // assign handler for the onblur event (hides suggestions)    
	this.textbox.onblur = function ()
	{
		oThis.hideSuggestions();
		oThis.keyPending = 0;
		oThis.lastKey = 0;
		if (oThis.myOnblur != null)
		{
			oThis.myOnblur(oThis.textbox);
		}
	}

    // assign handler for the onfocus event (resets keystroke counters)    
	this.textbox.onfocus = function ()
	{
		oThis.keyPending = 0;
		oThis.lastKey = 0;
		if (oThis.myOnfocus != null)
		{
			oThis.myOnfocus(oThis.textbox);
		}
	}

	// create the suggestions dropdown
	this.createDropDown();
}

// Highlight the next suggestion in the dropdown & put it in the textbox
AutoSuggestControl.prototype.nextSuggestion = function ()
{
	var cSuggestionNodes = this.layer.childNodes;

	if (cSuggestionNodes.length > 0 && this.cur < cSuggestionNodes.length-1)
	{
		var oNode = cSuggestionNodes[++this.cur];
		this.highlightSuggestion(oNode);
		this.textbox.value = oNode.firstChild.nodeValue;
		var inputFields = oNode.getElementsByTagName("input");
		if (inputFields.length > 0)
		{
			this.populateHidden (inputFields[0].value);
		}
		this.userInput = this.textbox.value;
	}
}

// Highlight the previous suggestion in the dropdown & put it in the textbox
AutoSuggestControl.prototype.previousSuggestion = function ()
{
	var cSuggestionNodes = this.layer.childNodes;

	if (cSuggestionNodes.length > 0 && this.cur > 0)
	{
		var oNode = cSuggestionNodes[--this.cur];
		this.highlightSuggestion(oNode);
		this.textbox.value = oNode.firstChild.nodeValue;
		var inputFields = oNode.getElementsByTagName("input");
		if (inputFields.length > 0)
		{
			this.populateHidden (inputFields[0].value);
		}
		this.userInput = this.textbox.value;
	}
}

// Select a range of text in the textbox
// Param1: iStart - The start index of the selection (base 0)
// Param2: iLength - The number of characters to select
AutoSuggestControl.prototype.selectRange = function (iStart, iLength)
{
	// use text ranges for Internet Explorer
	if (this.textbox.createTextRange)
	{
		var oRange = this.textbox.createTextRange(); 
		oRange.moveStart("character", iStart); 
		oRange.moveEnd("character", iLength - this.textbox.value.length);      
		oRange.select();

	// use setSelectionRange() for Mozilla
	} else if (this.textbox.setSelectionRange)
	{
		this.textbox.setSelectionRange(iStart, iLength);
	}     

	this.textbox.focus();
}

// Build the suggestion layer dropdown, position & displays it
// Param1: aSuggestions - An array of suggestions
AutoSuggestControl.prototype.showSuggestions = function (aSuggestions)
{
	var oDiv = null;
	this.layer.innerHTML = "";  // clear contents of the layer

	for (var i=0; i < aSuggestions.length; i++)
	{
		var parts = aSuggestions[i].split('^');
		oDiv = document.createElement("div");
		oDiv.appendChild(document.createTextNode(parts[0]));
		if (parts.length > 1 && parts[1].length > 0)
		{
			oHidden = document.createElement("input");
			oHidden.setAttribute('type','hidden');
			oHidden.setAttribute('value', parts[1]);
			oDiv.appendChild(oHidden);
		}
		this.layer.appendChild(oDiv);
	}

	this.layer.style.left = this.getLeft() + "px";
	this.layer.style.top = (this.getTop()+this.textbox.offsetHeight) + "px";
	this.layer.style.width = this.textbox.offsetWidth;
	this.layer.style.visibility = "visible";
	
	// highlight the first suggestion
	this.layer.childNodes[0].className = "current";
}

// Insert a suggestion into the textbox & highlight the suggested part of the text
// Param1: sSuggestion - The suggestion for the textbox
AutoSuggestControl.prototype.typeAhead = function (sSuggestion, sValue)
{
	// check for support of typeahead functionality
	if (this.textbox.createTextRange || this.textbox.setSelectionRange)
	{
//		var iLen = this.textbox.value.length;
//		var iLen = this.userInput.length;
//		this.textbox.value = sSuggestion;
//		this.populateHidden (sValue);
//		this.selectRange(iLen, sSuggestion.length);
	}
}
