var g_maxHits;
var g_hitBox;
var g_addrBox;
var g_regainFocus = false;
var g_candidateAddrs;
var g_candidateNames;
var g_cancelEvent;
var g_preferredDelimiter = ";";
var g_prevActiveChunk = "";
var g_prevTime = 0;

var KEY_BACKSPACE = 8;
var KEY_TAB = 9;
var KEY_ENTER = 13;
var KEY_ESC = 27;
var KEY_LEFT_ARROW = 37;
var KEY_UP_ARROW = 38;
var KEY_RIGHT_ARROW = 39;
var KEY_DOWN_ARROW = 40;

var HILITE_IDX = "hilite_idx";

function initAutoComplete (addrBox, hitBox, addrs, names) {
	g_maxHits=15;
	
	g_addrBox = addrBox;
	g_addrBox.onkeydown = handleKeyDown;
	g_addrBox.onkeypress = handleKeyPress;
	g_addrBox.onkeyup = handleKeyUp;
	g_addrBox.onblur = handleAddrBoxBlur;
	
	g_hitBox = hitBox;	
	g_hitBox.style.visibility = "hidden";

	g_candidateAddrs = addrs;
	g_candidateNames = names;
}

function handleAddrBoxBlur (evt) {
	if (g_regainFocus) {
		g_addrBox.focus();
		g_regainFocus = false;
	}
	else {
		hideHitBox();
		nixDanglingDelimiter();
	}
}

function handleItemMouseDown (evt) {
	g_regainFocus = true;
}

function handleItemMouseUp (evt) {
	var children = g_hitBox.childNodes;
	for (i=0; i<children.length; ++i) {
		if (this == children[i]) {
			g_hitBox.setAttribute(HILITE_IDX, i);
			break;
		}
	}
	commitSelection();
	hideHitBox();
}


function handleItemMouseOver (evt) {
	if (!hasClass(this, "autocomplete-hilite")) {
		replaceClass(this, "autocomplete-plain", "autocomplete-mousehilite");
	}
}

function handleItemMouseOut (evt) {
	if (hasClass(this, "autocomplete-mousehilite")) {
		replaceClass(this, "autocomplete-mousehilite", "autocomplete-plain");
	}
}

function handleKeyDown (evt) {
	if (!evt) evt = window.event;
	var keyCode = evt.keyCode;
	switch(keyCode) {
		case KEY_ESC:
			hideHitBox();
			cancelEvent(evt); // else IE will erase all text
			return;
		case KEY_UP_ARROW:
		case KEY_DOWN_ARROW:		
		case KEY_TAB:
		case KEY_ENTER:
			if (isHitBoxVisible()) cancelEvent(evt);	
			return;	
	}
}

function handleKeyPress (evt) {
	if (!evt) {
		evt = window.event;
	}
	var keyCode = evt.keyCode;
	switch(keyCode) {
		case KEY_LEFT_ARROW:
		case KEY_RIGHT_ARROW:
			hideHitBox();
			return;
		case KEY_ESC:
			hideHitBox();
			cancelEvent(evt); // else box reappears
			return;
		case KEY_UP_ARROW:
		case KEY_DOWN_ARROW:
		case KEY_TAB:
		case KEY_ENTER:
			if (isHitBoxVisible()) cancelEvent(evt);	
			return;	
	}
}

function handleKeyUp (evt) {
	if (!evt) {
		evt = window.event;
	}
	
	var keyCode = evt.keyCode;	
	switch(keyCode) {
		case KEY_LEFT_ARROW:
		case KEY_RIGHT_ARROW:
			hideHitBox();
			return;
		case KEY_ESC:
			cancelEvent(evt);
			return;
		case KEY_UP_ARROW:
			selectPrevious();
			return;
		case KEY_DOWN_ARROW:
			selectNext();
			return;
		case KEY_TAB:
		case KEY_ENTER:
			if (isHitBoxVisible()) {
				commitSelection();
				hideHitBox();
				cancelEvent(evt);
			}
			return;
	}

	searchAddressBook(getActiveChunk(g_addrBox));
}

function cancelEvent (evt) {
	evt.returnValue = false;
	if (evt.preventDefault) evt.preventDefault();
}

function getSelectionIdx () {
	return g_hitBox.getAttribute(HILITE_IDX);
}

function getSelectedItem () {
	return g_hitBox.childNodes[getSelectionIdx()];
}

function commitSelection () {
	var selectedItem = getSelectedItem();
	var newChunk = selectedItem.value + g_preferredDelimiter + " ";
	//if the g_candidateNames array is empty, we are only filling a single email address field, not multiple addresses
	// slight hack
	if (g_candidateNames.length == 0) {
		g_addrBox.value =  selectedItem.value;
	} else {
		replaceActiveChunk(g_addrBox, newChunk);
	}
	
	g_addrBox.focus();
	// put cursor at end
	g_addrBox.scrollTop = g_addrBox.scrollHeight;
}

function getActiveChunk (element) {
	if (!element) return;
	var val = element.value;
	var activeChunkIdx = getActiveChunkIdx(val);
	if (-1 == activeChunkIdx) return trimLeft(val);
	var newVal = val.substring(activeChunkIdx + 1, val.length);
	return trimLeft(newVal);
}

function replaceActiveChunk (element, newChunk) {
	if (!element) return;
	var oldVal = element.value;
	var activeChunkIdx = getActiveChunkIdx(oldVal);
	if (-1 == activeChunkIdx) {
		element.value = oldVal.substring(0, activeChunkIdx + 1) + newChunk;
	}
	else { // inactive chunk exists so pad a space btw prev delimiter
		element.value = oldVal.substring(0, activeChunkIdx + 1) + " " + newChunk;
	}
}

function getActiveChunkIdx (val) {
	if (!val) return -1;
	var commaidx = val.lastIndexOf(",");
	var semicolonidx = val.lastIndexOf(";");
	if (-1 == commaidx && -1 == semicolonidx) return -1;
	if (commaidx >=semicolonidx) {
		g_preferredDelimiter = ",";
		return commaidx;
	}
	else {
		g_preferredDelimiter = ";";
		return semicolonidx;
	}
}

function selectPrevious () {	
	if (!isHitBoxVisible()) return;
	var curIdx = g_hitBox.getAttribute(HILITE_IDX);
	if (curIdx - 1 >= 0) {
		replaceClass(g_hitBox.childNodes[curIdx], "autocomplete-hilite", "autocomplete-plain");
		replaceClass(g_hitBox.childNodes[curIdx - 1], "autocomplete-plain", "autocomplete-hilite");
		g_hitBox.setAttribute(HILITE_IDX, curIdx - 1);	
	}
}

function selectNext () {	
	if (!isHitBoxVisible()) return;
	// cast to number
	var curIdx = g_hitBox.getAttribute(HILITE_IDX) - 0;
	if (curIdx + 1 < g_hitBox.childNodes.length) {
		replaceClass(g_hitBox.childNodes[curIdx], "autocomplete-hilite", "autocomplete-plain");
		replaceClass(g_hitBox.childNodes[curIdx + 1], "autocomplete-plain", "autocomplete-hilite");
		g_hitBox.setAttribute(HILITE_IDX, curIdx + 1);	
	}
}

function hasClass (element, className) {
	if (!element) return;
	return element.className.indexOf(className) != -1;
}

function replaceClass (element, oldClass, newClass) {
	if (!element) return;
	removeClass(element, oldClass);
	addClass(element, newClass);
}

function removeClass (element, oldClass) {
	if (!element) return;
	var idx = element.className.indexOf(oldClass);
	if (-1 == idx) return;
	element.className = element.className.substring(0, idx) 
						+ element.className.substring(idx + oldClass.length + 1);
}

function addClass (element, newClass) {
	if (element && -1 == element.className.indexOf(newClass)) {
		element.className = element.className + " " + newClass;
	}
}

// hit class
function hit (idx, sortKey, name, decoratedName, addr, decoratedAddr) {
	this.idx = idx;
	// prefer to sort on name, email address if name is unavailable
	this.sortKey = sortKey.toLowerCase();
	this.name = name;
	// decorated name has the portion matching the search term bolded
	this.decoratedName = decoratedName;
	this.addr = addr;
	// decorated email address has angle brackets around it, 
	// and the portion matching the search term bolded
	this.decoratedAddr = decoratedAddr;
}

function sortHits (a, b) {
	if (a.sortKey > b.sortKey) return 1;
	if (a.sortKey < b.sortKey) return -1;
	return 0;
}

function searchAddressBook (searchTerm) {
	if (isBlank(searchTerm)) {
		hideHitBox();
		return;
	}
	// [^xxx] matches any character _not_ in xxx
	// \s matches any whitespace character
	// /gi means global, ignorecase
	var escapeNonAlphaNum = /([^a-zA-Z0-9\s])/gi
	// ^ anchors to beginning of string
	// $1 represents the 1st of a max of 9 parenthetical substring matches
	// \\ is an escaped backslash
	var pattern = "^" + searchTerm.replace(escapeNonAlphaNum, "\\$1");

	// so the upshot is that "try-out" becomes "^try\-out"

	var re = new RegExp(pattern,"gi");
	var showStr = new String();

	while (g_hitBox.hasChildNodes()) {
		g_hitBox.removeChild(g_hitBox.childNodes[0]);
	}

	var hits = new Array();
	var numHits = 0;
	
	var len = g_candidateNames.length;
	for (i=0; i<len; i++) {
        if (g_candidateNames[i].search(re) != -1) {
        	var name = g_candidateNames[i];
        	var decoratedName = emboldSubstring(capitalize(name), searchTerm.length);
        	var addr = g_candidateAddrs[i];
			var decoratedAddr = addr;
			if (addr != "") {
				if (addr.search(re) != -1) {
					decoratedAddr = emboldSubstring(addr, searchTerm.length);
				}
				decoratedAddr = "&lt;" + decoratedAddr + "&gt;";
				addr = "<" + addr + ">";
			}
        	hits[hits.length] = new hit(i, name.toLowerCase(), name, decoratedName, addr, decoratedAddr);
			if (++numHits == g_maxHits) break;
		}
	}

	if (numHits != g_maxHits) {
		len = g_candidateAddrs.length;
	    for (i=0; i<len; i++) {
	    SKIP_ITER:
	        if(g_candidateAddrs[i].search(re) != -1) {
	        	for (j=0; j<hits.length; j++) {
	        		if (hits[j].idx == i) break SKIP_ITER;
	        	}
	        	var addr = g_candidateAddrs[i];
	        	var decoratedAddr = emboldSubstring(addr, searchTerm.length);
				var thisName = g_candidateNames[i];
				var name="";
				// checks to see if the name is defined or not
				// if undefined then the email address is a single parameter, not a text area
				if (!isUndefined(thisName)) {
					name = thisName;
					addr = "<" + addr + ">";
					decoratedAddr = "&lt;" + decoratedAddr + "&gt;";
				}   			

	        	hits[hits.length] = new hit(i, addr.toLowerCase(), name, name, addr, decoratedAddr);
				if (++numHits == g_maxHits) break;
			}
		}
	}
	
	hits.sort(sortHits);
	
	var hitBoxWidth = getHitBoxWidth();
	len = hits.length;
	for (i=0; i<len; i++) {
		var item = document.createElement("DIV");
		// align right edges of itemlist & addrbox
		var itemWidth = hitBoxWidth - 4;
		item.style.width = itemWidth + "px";
		if (!is_safari) {
			item.onmousedown = handleItemMouseDown;
			item.onmouseup = handleItemMouseUp;
			item.onmouseover = handleItemMouseOver;
			item.onmouseout = handleItemMouseOut;
			if (!is_ie5) {			
				if (is_ie5_5) {
					item.style.cursor = 'hand';
				} else {
					item.style.cursor = 'pointer';  
				}
			}
			
		}
		
		var currHit = hits[i];

		var label = "<nobr>" + currHit.decoratedName + " " + currHit.decoratedAddr + "</nobr>";
		item.innerHTML = label;    	

		g_hitBox.appendChild(item);

		if (0==i) {
			item.className = "autocomplete-hilite";
			g_hitBox.setAttribute(HILITE_IDX, 0);
		}
		else {
			item.className = "autocomplete-plain";
		}
		if (currHit.name != "" && currHit.addr != "") {
			item.value = currHit.name + " " + currHit.addr;
		}
		else if ("" == currHit.name) {
			item.value = currHit.addr;
		}
		else {
			item.value = currHit.name;
		}
			
	}
	
	numHits > 0 ? showHitBox() : hideHitBox();
}

function emboldSubstring (val, splitidx) {
	if (null == val) return "";
	preChunk = val.substring(0, splitidx);
	postChunk = val.substring(splitidx, val.length);
	return "<strong>" + preChunk + "</strong>" + postChunk;
}

function isHitBoxVisible () {
	return "visible" == g_hitBox.style.visibility;
}

function hideHitBox () {
	g_hitBox.style.visibility = "hidden";
}

function showHitBox () {
	if (!(is_mac && is_ie)) {
		if (!isHitBoxVisible()) {
			updateHitBoxPos();
			g_hitBox.style.visibility = "visible";
		}
	}
}

function updateHitBoxPos () {
	g_hitBox.style.left = findPosX(g_addrBox) + "px";
	g_hitBox.style.top = findPosY(g_addrBox) + findHeight(g_addrBox) + "px";
}

function getHitBoxWidth () {
	var hitBoxWidth = findWidth(g_hitBox);
	var addrBoxWidth = findWidth(g_addrBox);
	if (addrBoxWidth > hitBoxWidth) {
		hitBoxWidth = addrBoxWidth;
	}
	return hitBoxWidth;
}

function nixDanglingDelimiter () {
	var i;
	for (i=g_addrBox.value.length-1; i>-1; i--) {
		var ch = g_addrBox.value.charAt(i);
		if (ch != "," && ch != ";" && ch != " ") break;
	}
	g_addrBox.value = g_addrBox.value.substring(0, i + 1);
}

function isUndefined(a) {
    return typeof a == 'undefined';
}

