/**
 * An autosuggest textbox control.
 * 
 * @class
 * @scope public
 */
function AutoSuggestControl(oTextbox /* :HTMLInputElement */,
		oProvider /* :SuggestionProvider */, oLookUpBox, iframe) {

	/**
	 * The currently selected suggestions.
	 * 
	 * @scope private
	 */
	this.cur /* :int */= -1;

	/**
	 * Stores the text originally input by user when suggestions are selected so
	 * that the original text can be restored if the user arrows back up to the
	 * text field.
	 * 
	 * @scope private
	 */
	this.orig = null;

	/**
	 * The dropdown list layer.
	 * 
	 * @scope private
	 */
	this.layer = null;

	/**
	 * Suggestion provider for the autosuggest feature.
	 * 
	 * @scope private.
	 */
	this.provider /* :SuggestionProvider */= oProvider;

	/**
	 * The textbox to capture.
	 * 
	 * @scope private
	 */
	this.textbox /* :HTMLInputElement */= oTextbox;
	this.lookup = oLookUpBox;
	// initialize the control
	this.init();
	this.iframe = iframe;
}

/**
 * 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.
 * @param bTypeAhead
 *            If the control should provide a type ahead suggestion.
 */
AutoSuggestControl.prototype.autosuggest = function(aSuggestions /* :Array */,
		bTypeAhead /* :boolean */) {

	// make sure there's at least one suggestion
	if (typeof aSuggestions != "undefined") {
		if (aSuggestions.length > 0) {
			this.showSuggestions(aSuggestions);
		} else {
			this.hideSuggestions();
		}
	}
};

/**
 * Creates the dropdown layer to display multiple suggestions.
 * 
 * @scope private
 */
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";
	// this.layer.style.width = "100%";//this.textbox.offsetWidth;

	// 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") {
			if (typeof oTarget.firstChild.nodeValue != "undefined"
					|| oTarget.firstChild.nodeValue.length != 0)
				if (oTarget.innerHTML != "...")
					oThis.textbox.value = oTarget.firstChild.nodeValue;
			oThis.textbox.focus();
			// oThis.hideSuggestions();
		} else if (oEvent.type == "mouseover" && oTarget.innerHTML != "...") {
			oThis.highlightSuggestion(oTarget);
		} else {
			oThis.textbox.focus();
		}
		return;
	};

	document.body.appendChild(this.layer);
};

/**
 * Gets the left coordinate of the textbox.
 * 
 * @scope private
 * @return The left coordinate of the textbox in pixels.
 */
AutoSuggestControl.prototype.getLeft = function() /* :int */{

	var oNode = this.textbox;
	var iLeft = 0;
	while (oNode.tagName != "HTML" && oNode.tagName != "BODY") {
		iLeft += oNode.offsetLeft;
		oNode = oNode.offsetParent;
	}
	return iLeft;
};

/**
 * Gets the top coordinate of the textbox.
 * 
 * @scope private
 * @return The top coordinate of the textbox in pixels.
 */
AutoSuggestControl.prototype.getTop = function() /* :int */{

	var oNode = this.textbox;
	var iTop = 0;

	while (oNode.tagName != "HTML" && oNode.tagName != "BODY") {
		iTop += oNode.offsetTop;
		oNode = oNode.offsetParent;
	}

	return iTop;
};

/**
 * Handles three keydown events.
 * 
 * @scope private
 * @param oEvent
 *            The event object for the keydown event.
 */
AutoSuggestControl.prototype.handleKeyDown = function(oEvent /* :Event */) {

	switch (oEvent.keyCode) {
	case 38: // up arrow
		this.previousSuggestion();
		break;
	case 40: // down arrow
		this.nextSuggestion();
		break;
	case 13: // enter
		this.hideSuggestions();
		break;
	}

};

/**
 * Handles keyup events.
 * 
 * @scope private
 * @param oEvent
 *            The event object for the keyup event.
 */
AutoSuggestControl.prototype.handleKeyUp = function(oEvent /* :Event */) {

	var iKeyCode = oEvent.keyCode;
	// make sure not to interfere with non-character keys
	if (iKeyCode != 8 && iKeyCode != 46 && iKeyCode < 32
			|| (iKeyCode >= 33 && iKeyCode < 46)
			|| (iKeyCode >= 112 && iKeyCode <= 123)) {
	} else {
		if (this.textbox.value.length > 0) {
			// request suggestions from the suggestion provider with typeahead
			this.provider.requestSuggestions(this, true);
		} else {
			this.hideSuggestions();
		}
		this.cur = -1; // Reset selected suggestion when a user types
	}
};

/**
 * Hides the suggestion dropdown.
 * 
 * @scope private
 */
AutoSuggestControl.prototype.hideSuggestions = function( /* Array */) {
	this.layer.style.visibility = "hidden";
	this.iframe.style.visibility = "hidden";
	this.iframe.style.display = "none";
};

/**
 * Highlights the given node in the suggestions dropdown.
 * 
 * @scope private
 * @param 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 = "selected";
		} else if (oNode.className == "selected") {
			oNode.className = "";
		}
	}
};

/**
 * Initializes the textbox with event handlers for auto suggest functionality.
 * 
 * @scope private
 */
AutoSuggestControl.prototype.init = function( /* Array */) {

	// save a reference to this object
	var oThis = this;

	// 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 handleKeyUp() method with the event object
		oThis.handleKeyUp(oEvent);
	};

	// assign onkeydown event handler
	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 onblur event handler (hides suggestions)
	this.textbox.onblur = function() {
		oThis.hideSuggestions();
	};

	// create the suggestions dropdown
	this.createDropDown();
};

/**
 * Highlights the next suggestion in the dropdown and places the suggestion into
 * the textbox.
 * 
 * @scope private
 */
AutoSuggestControl.prototype.nextSuggestion = function() {
	var cSuggestionNodes = this.layer.childNodes;

	if (cSuggestionNodes.length > 0 && this.cur < cSuggestionNodes.length - 1) {
		if (this.cur == -1) {
			// Save original text entered by user before replacing it with first
			// suggestion
			this.orig = this.textbox.value;
		}
		var oNode = cSuggestionNodes[++this.cur];
		if (oNode.innerHTML != "...") {
			this.highlightSuggestion(oNode);
			this.textbox.value = oNode.firstChild.nodeValue;
		} else {
			this.cur--;
		}
	}
};

/**
 * Highlights the previous suggestion in the dropdown and places the suggestion
 * into the textbox.
 * 
 * @scope private
 */
AutoSuggestControl.prototype.previousSuggestion = function() {
	var cSuggestionNodes = this.layer.childNodes;

	if (cSuggestionNodes.length > 0) {
		if (this.cur > 0) {
			var oNode = cSuggestionNodes[--this.cur];
			this.highlightSuggestion(oNode);
			this.textbox.value = oNode.firstChild.nodeValue;
		} else if (this.cur == 0) {
			// User is arrowing back up to the text field, restore the original
			// value entered by the user and remove highlighting of suggestion
			this.textbox.value = this.orig;
			this.highlightSuggestion(null);
			this.cur--;
		}
	}
};

/**
 * 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 */) {

	// 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);
	}

	// set focus back to the textbox
	this.textbox.focus();
};

/**
 * Builds the suggestion layer contents, moves it into position, and displays
 * the layer.
 * 
 * @scope private
 * @param aSuggestions
 *            An array of suggestions for the control.
 */
AutoSuggestControl.prototype.showSuggestions = function(aSuggestions /* :Array */) {
	var oDiv = null;
	this.layer.innerHTML = ""; // clear contents of the layer

	for ( var i = 0; i < aSuggestions.length; i++) {
		oDiv = document.createElement("div");
		oDiv.appendChild(document.createTextNode(aSuggestions[i]));
		this.layer.appendChild(oDiv);
	}
	this.layer.style.left = this.getLeft() + "px";
	this.layer.style.top = (this.getTop() + this.textbox.offsetHeight) + "px";
	this.layer.style.visibility = "visible";
	this.iframe.style.width = "800px";
	this.iframe.style.zIndex = 5;
	this.iframe.style.visibility = "visible";
	this.iframe.style.display = "block";
	// this.iframe.style.border="1px solid blue";
	this.iframe.style.left = (this.getLeft()) + "px";
	this.iframe.style.height = "30em";// this.layer.style.height;
	this.iframe.style.top = (this.getTop() + this.textbox.offsetHeight) + "px";

};
