var typeAhead = null;
var optionClicked = false; // ie bug with focus being called after option is selected
var stopInitialMouseOver = false; // ie fires a mouse over event when the div is initially displayed if the mouse is over the div
var keyUpFlag = false; //Key event fires twice in Safari. This flag is for checking that 
var keyDownFlag = false;
var EvnKey = {TAB: 9, ENTER: 13, SHIFT: 16, CTRL: 17, ALT: 18, CAPLOCK: 20, LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40, NUMLOCK: 144, SCROLLLOCK: 145};

// override the enter, up arrow, or down arrow key being pressed when in the text box.
function typeAheadKeyPress(event) {
	if (browser.isIE && typeof event.keyCode == "undefined") {
		event = window.event;
	}
	return event.keyCode != EvnKey.ENTER && event.keyCode != EvnKey.LEFT && event.keyCode != EvnKey.UP && event.keyCode != EvnKey.RIGHT && event.keyCode != EvnKey.DOWN;
}

// when a key is released
function typeAheadKeyUp(element, event) {
	// some browsers (ie. Safari) produce two events for a single key up instance.  This detects it and skips this code for the second key up event.
	keyDownFlag = false;
	if (keyUpFlag) {
		keyUpFlag = false;
		return;
	}
	else {
		keyUpFlag = true;
	}
	
	if (browser.isIE && typeof event.keyCode == "undefined") {
		event = window.event;
	}
	switch (event.keyCode) {
		case EvnKey.TAB:   
		case EvnKey.ENTER:  
		case EvnKey.SHIFT:  
		case EvnKey.CTRL:  
		case EvnKey.ALT:  
		case EvnKey.CAPLOCK:  
		case EvnKey.NUMLOCK: 
		case EvnKey.SCROLLLOCK: 
			break;
		case EvnKey.LEFT:  
		case EvnKey.UP:  
		case EvnKey.RIGHT:  		
		case EvnKey.DOWN:  
			element.select();
			break;
		default:
			typeAhead.matchOptions(element, false);
			updateContent(element);	
	}
}
// when a key is initially hit
function typeAheadKeyDown(element, event) {
	keyUpFlag = false;
	if (keyDownFlag) {
		keyDownFlag = false;
		return;
	}
	else {
		keyDownFlag = true;
	}
	
	if (browser.isIE && typeof event.keyCode == "undefined") {
		event = window.event;
	}	
	switch (event.keyCode) {
		case EvnKey.TAB:  
		case EvnKey.ENTER: 
			if (!typeAhead.options[typeAhead.selectedIndex].isOptGroup) {
				element.value = typeAhead.options[typeAhead.selectedIndex].text;
				hideAllDiv();
				eval(element.getAttribute("onChangeJavascript"));
			}
			break;
		case EvnKey.LEFT: 
			typeAhead.decrementSelectedIndexByOneColumn();
			updateContentHilite(element);
			break;			
		case EvnKey.UP: 
			typeAhead.decrementSelectedIndex();
			updateContentHilite(element);
			break;
		case EvnKey.RIGHT: 
			typeAhead.incrementSelectedIndexByOneColumn();
			updateContentHilite(element);		
			break;			
		case EvnKey.DOWN: 
			typeAhead.incrementSelectedIndex();
			updateContentHilite(element);
			break;
	}
}
// when the input field receives focus
function typeAheadFocus(element) {
	element.select();
	// when you tab into the field on focus only fires so do the same thing as if you clicked.
	if (!optionClicked) {
		typeAheadClick(element);
	}
	optionClicked = false;
}

//Performs TypeAhead object instantiation
function typeAheadClick(element) {
	typeAhead = new TypeAhead(element, eval(element.getAttribute("options")));
	stopInitialMouseOver = true;
	setTimeout("stopInitialMouseOver = false", 100);
	updateContent(element);
	element.select();
}
// when the input field looses focus
function typeAheadBlur(element) {
	mouseOut();
}

function removeChildren(element) {
	for(i = 0; i < element.childNodes.length; i++) {
		element.removeChild(element.childNodes[i]);
	}
}

// Regenerate typahead option content based on the element value.
function updateContent(element) {
	var contentDiv = document.getElementById(element.getAttribute("contentDiv"));
	removeChildren(contentDiv);
	var displayOptions = getDisplayOptions();
	var maxOptionLength = getMaxOptionLength(element, displayOptions);
	var columns = Math.ceil(displayOptions.length/maxOptionLength);
	while (isRegionOrphened(maxOptionLength, columns, typeAhead.options)) {
		++maxOptionLength;
	}
	
	var tbody = document.createElement("tbody");
	for (i = 0; i < maxOptionLength; i++) {
		var tr = document.createElement("tr");
		for (j = 0; j < Math.ceil(displayOptions.length/maxOptionLength); j++) {
			var index = i + (j * maxOptionLength);
			var td = document.createElement("td");
			td.className = element.getAttribute("optionTdClassName");
			
			if (index < displayOptions.length) {
				var opt = typeAhead.options[displayOptions[index].index];
				if (opt.isOptGroup) {
					td.className = element.getAttribute("optionGroupTdClassName");
					td.onmouseover = function() { if (!stopInitialMouseOver) { mouseOverLink(1); typeAhead.selectedIndex = this.getAttribute("index"); updateContentHilite(element); } };
					td.onclick = function() { optionClicked = false; element.focus();};
				}
				else {
					if (displayOptions[index].index == typeAhead.selectedIndex) {
						td.className = element.getAttribute("optionTdHiliteClassName");
					}
					td.onmouseover = function() { if (!stopInitialMouseOver) { mouseOverLink(1); typeAhead.selectedIndex = this.getAttribute("index"); updateContentHilite(element); } };
					td.onclick = function() { optionClicked = true; hideAllDiv(); element.value = typeAhead.options[this.getAttribute("index")].text; element.select(); element.focus(); eval(element.getAttribute("onChangeJavascript")); };
				}
				td.setAttribute("index", displayOptions[index].index);
				td.appendChild(typeAhead.options[displayOptions[index].index].getDisplayNode());
			}
			else {
				td.onmouseover = function() { mouseOverLink(1); };
				td.appendChild(document.createTextNode(String.fromCharCode(160)));
			}
			td.onmouseout = function() { mouseOut(); };
			setTdLookAndFeel(element, td);
			tr.appendChild(td);
		}
		tbody.appendChild(tr);
	}
	typeAhead.numberOfDisplayColumns = Math.ceil(displayOptions.length/maxOptionLength);
	typeAhead.numberOfDisplayRows = maxOptionLength;
	
	var table = document.createElement("table");
	setTableLookAndFeel(element, table);
	table.appendChild(tbody);
	contentDiv.appendChild(table);
	mouseOverDivNew(element.getAttribute("contentDiv"), element, 0, (browser.isIE ? 9 : 22), 0);
}

function setTdLookAndFeel(element, td) {
	td.noWrap = "true";
	td.style.margin = 0;
}

function setTableLookAndFeel(element, table) {
	table.className = element.getAttribute("optionTableClassName");
	table.border = 0;
	table.cellSpacing = 0;
	table.cellPadding = 0;
	table.style.padding = 0;
	table.style.margin = 0;
}

// max option length minimizing blanks for display options array as specified by element attribute.
function getMaxOptionLength(element, displayOptionsArray) {
	// number of displayable options
	var maxOptionLength = displayOptionsArray.length; 
	if (element.getAttribute("maxOptionLength") != null) {
		var max = parseInt(element.getAttribute("maxOptionLength"));
		if (maxOptionLength > max) {
			maxOptionLength = max;
			if (displayOptionsArray.length % max != 0) {
				var fullColumns = Math.floor(displayOptionsArray.length/max);
				var blanks = max - (displayOptionsArray.length % max);
				while (blanks > fullColumns) {
					maxOptionLength--;
					blanks -= fullColumns + 1;
				}
			}
		}
	}
	return maxOptionLength;
}

// Performs filtering and return a new copy of all displayable typeahead options
function getDisplayOptions() {
	var displayOptions = new Array();
	for (i = 0; i < typeAhead.options.length; i++) {
		if (typeAhead.options[i].display) {
			displayOptions[displayOptions.length] = typeAhead.options[i];
		}
	}
	return displayOptions;
}

// update the content that is hilited
function updateContentHilite(element) {
	var contentDiv = document.getElementById(element.getAttribute("contentDiv"));
	for (i = 0; i < contentDiv.childNodes[0].childNodes[0].childNodes.length; i++) {
		for (j = 0; j < contentDiv.childNodes[0].childNodes[0].childNodes[i].childNodes.length; j++) {
			var td = contentDiv.childNodes[0].childNodes[0].childNodes[i].childNodes[j];
			td.className = element.getAttribute("optionTdClassName");
			var stdindex = td.getAttribute("index");
			var tdindex = undefined;
			var tao = undefined;
			if (typeof stdindex != 'undefined') {
				var tdindex = parseInt(stdindex);
				tao = typeAhead.options[tdindex];
			}
			if (typeof tao != 'undefined') {
				if (tao.isOptGroup) {
					td.className = element.getAttribute("optionGroupTdClassName");
				}
				else if (tdindex == typeAhead.selectedIndex) {
					td.className = element.getAttribute("optionTdHiliteClassName");
				}
			}
		}
	}

}

function isRegionOrphened(maxOptionLength, columns, options) {
	var orphenedRegion = false;
	for (i = 1; i < columns; i++) {
		if (options[i*maxOptionLength - 1].isOptGroup) {
			orphenedRegion = true
			break;
		}
	}
	return orphenedRegion;
}