/* format of constructor is overloaded:
 * AC(<type>, <id>, <submit callback>)
 * AC(<type>, <id>)
 * AC(<id>)
 */

function AC(id) {
/* find search type */
	if (arguments.length > 1) {
		this.type = arguments[0];
		id = arguments[1];
	} else {
		this.type = id;
	}
	
	
/* input element we are autocompleting on */
	this.obj = document.getElementById(id);
	this.obj.value = '';

/* function to call when option selected */
	this.submit_callback = (arguments.length > 2) ? arguments[2] : null;


/* popup layer we will display results in */
	this.div = document.createElement('DIV');
	// EJFIX (3/3/06) - give the div an ID in case we need to clear it or ref. it later -ej-
	this.div.id = 'AJAXDivID';
	this.div.className = 'ac_menu';
	this.div.align = 'left';
	this.div.style.visibility = 'hidden';
	this.div.style.position = 'absolute';
	this.div.style.zIndex = 1;
	this.div.style.whiteSpace = 'nowrap';
	this.div.style.width = this.obj.offsetWidth - 2 + "px";

	this.div.style.left = this.total_offset(this.obj,'offsetLeft') + "px";
	this.div.style.top = this.total_offset(this.obj,'offsetTop') + this.obj.offsetHeight - 1 + "px";

/* tie to input element */
	this.obj.parentNode.insertBefore(this.div, this.obj.nextSibling);

/* install event handlers */
	this.obj.onkeydown = this.onkeydown;
	this.obj.onkeyup = this.onkeyup;
	this.obj.onkeypress = this.onkeypress;
	// (3/6/06) Added this for IE - now we support mousewheel! -ej-
	this.obj.onmousewheel = this.onmousewheel;
	this.obj.onblur = function() { this.AC.close_popup(); }

	this.obj.AC = this;		/* self reference */
	this.selected_option = null;	/* the currently selected option */
	
	this.request = null;		/* http request object */
	this.cache = new Array();	/* cache of results from server */
	this.typing = false;		/* whether user is still typing */
	this.typing_timeout = 10;

	this.search_term = null;	/* current search term  */
	this.previous_term = null;	/* previous search term */
	this.searched_term = null;	/* search from keyboard */

/* update extern mapping array for rpc reply */
        _ac_map_add(this);
}	

AC.prototype.total_offset = function(element, property) {
	var total = 0;
	while (element) {
		total += element[property];
		element = element.offsetParent;
	}
	return total;
}

/* hide popup and cleanup */
AC.prototype.close_popup = function() { 
	
	this.div.style.visibility = 'hidden'; 

/* no selected item, no typing, and close any pending request */
	this.selected_option = null;
	this.typing = false;
	this.search_term = null;
	this.previous_term = null;
}

/* create object for rpc call */
AC.prototype.XMLHttpRequest = function() {
	var request = null;
	if (typeof XMLHttpRequest != 'undefined') {
		request = new XMLHttpRequest();
	} else {
		try {
			request = new ActiveXObject('Msxml2.XMLHTTP')
		} catch(e) {
			try { 
				request = new ActiveXObject('Microsoft.XMLHTTP')
			} catch(e) { 
				request = null 
			}
		}
	}
	return request;
}

/* helper functions to process typing timer */
var _ac_thunk = new Array();
function _ac_thunk_call(id) {
	if (_ac_thunk[id]) {
		var ac = _ac_thunk[id][1];
		ac.typing = false;
		ac.send(_ac_thunk[id][2]);
		_ac_thunk[id] = null;
		for (var i = _ac_thunk.length; i > 0; i--)
			if (_ac_thunk[i] == null)
				_ac_thunk.length--;
	}
}

/* cancel a pending request */
function _ac_cancel(ac) {
	for (var i=_ac_thunk.length-1; i >= 0; i--)
		if (_ac_thunk[i] != null && _ac_thunk[i][0] == ac.obj.id) {
			clearTimeout(_ac_thunk[i][3]);
			_ac_thunk[i] = null;
		}
}

function _ac_add(ac,query,timeout) {
	var i = _ac_thunk.length;
	var handle = setTimeout("_ac_thunk_call("+i+")",timeout);
	_ac_thunk[i] = new Array(ac.obj.id,ac,query,handle);
}

/* helper functions for webserver rpc processing */
var _ac_map = new Array();
function _ac_map_add(ac) {
        _ac_map[ac.obj.id] = ac;
}

/* called to initiation suggestion process */
AC.prototype.suggest = function(query) {
/* cancel any existing http call */
	_ac_cancel(this);
	if (this.request && this.request.readyState != 0) {
		this.request.abort();
	}

/* check cache */
	// added (if(query)) to fix additional error when up/down arrow pressed = error thrown -ej-
	if(query){
		var lc = query.toLowerCase();
		for (var i = 0; i < this.cache.length; i++)
			if (this.cache[i][0] == lc) {
				var results = this.cache[i][1];
				this.search_term = query;
				this.onreply(results);
				return;
			}
	}

/* send call to server */
	this.typing = true;
	this.send(query);
}

/* called to send message to a server */
AC.prototype.send = function(query) {
/* check throttle timer */
	if (this.typing) {
		_ac_add(this,query,this.typing_timeout);
		return;
	}

/* (3/2/06) - I didn't want just a basic "Loading..." screen as it doesn't show that the database is actually being "searched" (IMO), so let's add some animation!! -ej- */
function threeDots() {
	var dSpan = document.getElementById('dotSpan');
	if(dSpan){
		if(dSpan.innerHTML == '' || dSpan.innerHTML == '.........'){
			dSpan.innerHTML = '.';
		} else if(dSpan.innerHTML == '.'){
			dSpan.innerHTML = '..';
		} else if(dSpan.innerHTML == '..'){
			dSpan.innerHTML = '...';
		} else if(dSpan.innerHTML == '...'){
			dSpan.innerHTML = '....';
		} else if(dSpan.innerHTML == '....'){
			dSpan.innerHTML = '.....';
		} else if(dSpan.innerHTML == '.....'){
			dSpan.innerHTML = '......';
		} else if(dSpan.innerHTML == '......'){
			dSpan.innerHTML = '.......';
		} else if(dSpan.innerHTML == '.......'){
			dSpan.innerHTML = '........';
		} else if(dSpan.innerHTML == '........'){
			dSpan.innerHTML = '.........';
		}
		setTimeout(threeDots,500);
	}
}

/* initiate new call */
	this.request = this.XMLHttpRequest();
	this.search_term = query;
	var AC = this;
	this.request.onreadystatechange = function() {
			/* (3/2/06) EJFIX: Let's show the users what's going on! -ej- */
			if (AC.request.readyState == 1) {
				AC.div.style.visibility = 'visible';
				AC.div.innerHTML = '<span class=\'ac_searching\'>Searching Database<span id=\'dotSpan\'></span></span>';
				threeDots();
			}
			if (AC.request.readyState == 4) {
				try {
					if (AC.request.status != 200 || AC.request.responseText.charAt(0) == '<') {
						/* some error */
					} else {
						//prompt('',unescape(AC.request.responseText));
						eval(unescape(AC.request.responseText));
					}
				} catch (e) {}
			}
	}
	//prompt('',"ajax/AJAXLookup.cfm?nostats=yes&inputID="+this.obj.id+"&searchType="+this.type+"&searchString="+query);
	this.request.open("GET", "ajax/AJAXLookup.cfm?inputID="+this.obj.id+"&searchType="+this.type+"&searchString="+query);
	
	this.request.send(null);
}

/* called with array of search results */
AC.prototype.onreply = function(results) {
	if (this.search_term != null && results != null && results.length > 0) {
/* remove currently listed options */
		while (this.div.hasChildNodes())
			this.div.removeChild(this.div.firstChild);

/* default to first result when adding characters */
		if (this.previous_term == null || this.search_term.length > this.previous_term.length) {
			this.selected_option = 0;
		} else {
/* remove selection when deleteing */
			this.selected_option = null;
		}
		this.previous_term = this.search_term;

		for (i = 0; i < results.length; i++) {
			var div = document.createElement('DIV');
			div.divid = results[i][2];
			div.AC = this;
			if (this.selected_option == div.divid)
				div.className = 'ac_highlight';
			else
				div.className = 'ac_normal';
			div.name = results[i][0];
			div.value = results[i][1];
			div.innerHTML = results[i][3];
			div.onmousedown = function() { this.AC.onselected(); }
			div.onmouseover = function() { 
				if (this.AC.selected_option != null)
					this.AC.div.childNodes[this.AC.selected_option].className = 'ac_normal';
				this.AC.selected_option = this.divid;
				this.AC.cabbage = this.AC.selected_option;
				this.className = 'ac_highlight';
			}
			div.onmouseout = function() { this.className = 'ac_normal'; }
			this.div.appendChild(div);
		}
		this.div.style.visibility = 'visible';

/* update cache */
		var found = false;
		var lc = this.search_term.toLowerCase();
		for (i = 0; i < this.cache.length; i++)
			if (this.cache[i][0] == lc) {
				found = true;
				break;
			}

		if (!found) {
			this.cache[this.cache.length] = new Array(lc, results);
		}

/* complete text box with selected text */
		if (this.selected_option == 0 && 
			(this.obj.createTextRange || this.obj.setSelectionRange) &&
			this.obj.value != results[0][1] &&
			results[0][1].substring(0,this.search_term.length).toLowerCase() == this.search_term.toLowerCase())
		{
			this.obj.value = results[0][1];
			if (this.obj.createTextRange) {
				var range = this.obj.createTextRange();
				range.moveStart('character',this.search_term.length);
				range.select();
			} else {
				this.obj.setSelectionRange(this.search_term.length,this.obj.value.length);
			}
		}
	} else {
		this.close_popup();
		
	}
}

/* update auto-compete input element with selected option */
AC.prototype.update_input = function() {
	// EJ FIX: .name was used but referred to second array value, .value grabs the correct one -ej-
	this.obj.value = this.div.childNodes[this.selected_option].value;
	
	/* submit form */
		if (this.submit_callback)
			//alert(this.obj.value);
			this.submit_callback(this.div.childNodes[this.selected_option].name);
}

/* when option is clicked with mouse, or entered with keyboard */
AC.prototype.onselected = function() {
//alert('variables are: cabbage='+this.cabbage+', selected_option='+this.selected_option);
	if (this.selected_option == null)
		if (this.cabbage == null)
			return;
		else
			this.selected_option = this.cabbage;	/* opera funky */

	this.update_input();

/* hide popup */
	this.close_popup();
}

/* capture up & down actions to prevent moving cursor left or right */
/* input.onkeypress() */
AC.prototype.onkeypress = function(e) {
		
	if (!e) e = window.event;
	var c = e.keyCode;
	if (c == 0) c = e.charCode;
	switch (c) {
	// EJFIX: (1/17/06) Added on keypress so that when selected_option is null (happens when the drop-down box does not exist), form can be submitted via 'enter' key, but otherwise, will do nothing.
	case 13:	/* enter - do nothing */
		if(this.AC.selected_option != null){
			this.AC.onselected();
			e.returnValue = false;
			if (e.preventDefault) e.preventDefault();
			break;
		} else {
			// keep on truckin big guy, help us give the user their data
			return true;
		}		
	case 38:	/* up */
	case 40:	/* down */
		// added if(!e.shiftKey) check to allow & and ( to be used in the input field -ej-
		// per W3C spec, returnValue and preventDefault must be together - added preventDefault to if block -ej- 
		if(!e.shiftKey){
			e.returnValue = false;
			if (e.preventDefault) e.preventDefault();
		};
		break;
	default: break;
	}
}

/* move cursor on down to allow repeating */
/* input.onkeydown() */
AC.prototype.onkeydown = function(e) {
	if (!e) e = window.event;
	var c = e.keyCode;
	//if(c != 13 && c != 16) - if the key isn't a SHIFT key, trying to debug the &,( issue -ej-
	//	alert(c);
	if (c == 0) c = e.charCode;
	var i = this.AC.selected_option == null ? -1 : this.AC.selected_option;
	switch (c) {
	case 38:	/* up */
		i--;
		e.returnValue = false;
		if (e.preventDefault) e.preventDefault();
		break;

	case 40:	/* down */
		i++;
		e.returnValue = false;
		if (e.preventDefault) e.preventDefault();
		break;

	default: break;
	}

	if (c == 38 || c == 40) {
		var length = this.AC.div.childNodes.length;
		if (i < 0) i = 0;
		if (i >= length) i = length-1;
		if (i != this.AC.selected_option) {
			for (j = 0; j < length; j++) {
				if (j == i) {
					this.AC.obj.value = this.AC.div.childNodes[j].value;
					this.AC.selected_option = this.AC.div.childNodes[j].divid;
					this.AC.div.childNodes[j].className = 'ac_highlight';
				} else {
					this.AC.div.childNodes[j].className = 'ac_normal';
				}
			}

/* update search term */
			// Added (if(this.AC.search_term)) to fix down/up arrow with 0 length throwing error -ej-
			if(this.AC.search_term!=null && (this.AC.div.childNodes[this.AC.selected_option])){
				this.AC.search_term = this.AC.div.childNodes[this.AC.selected_option].value;
			}

/* popup if hidden */
			if (this.AC.div.style.visibility == 'hidden') {
				this.AC.suggest (this.AC.searched_term);
			}
		}
	}
}





/* (3/6/06) Added onMouseWheel support to scrolling up/down in menu -ej- */
AC.prototype.onmousewheel = function(e) {
	if (!e) e = window.event.wheelDelta;
	var i = this.AC.selected_option == null ? -1 : this.AC.selected_option;
	
	if(e > 0) { // positive values mean the mousewheel is moved UP -ej-
		i--;
		e.returnValue = false;
		if (e.preventDefault) e.preventDefault();
	} else if (e < 0) { // negative values... well, you can guess -ej-
		i++;
		e.returnValue = false;
		if (e.preventDefault) e.preventDefault();
	}

	var length = this.AC.div.childNodes.length;
	if (i < 0) i = 0;
	if (i >= length) i = length-1;
	if (i != this.AC.selected_option) {
		for (j = 0; j < length; j++) {
			if (j == i) {
				this.AC.obj.value = this.AC.div.childNodes[j].value;
				this.AC.selected_option = this.AC.div.childNodes[j].divid;
				this.AC.div.childNodes[j].className = 'ac_highlight';
			} else {
				this.AC.div.childNodes[j].className = 'ac_normal';
			}
		}

/* update search term */
		// Added (if(this.AC.search_term)) to fix down/up arrow with 0 length throwing error -ej-
		if(this.AC.search_term!=null && (this.AC.div.childNodes[this.AC.selected_option])){
			this.AC.search_term = this.AC.div.childNodes[this.AC.selected_option].value;
		}

/* popup if hidden */
		if (this.AC.div.style.visibility == 'hidden') {
			this.AC.suggest (this.AC.searched_term);
		}
	}
	return false;
}



	
/* input.onkeyup() */
AC.prototype.onkeyup = function(e) {
	if (!e) e = window.event;
	var c = e.keyCode;
	if (c == 0) c = e.charCode;
	switch (c) {
/* prevent strange selections at top of option list */
	case 38:	/* up */
	case 40:	/* down */
		e.returnValue = false;
		if (e.preventDefault) e.preventDefault();
		break;

/* select highlighted option */
	case 13:	/* enter */
		this.AC.onselected();
		e.returnValue = false;
		if (e.preventDefault) e.preventDefault();
		break;

/* hide popup window */
	case 27:	/* escape */
		this.AC.close_popup();
		e.returnValue = false;
		if (e.preventDefault) e.preventDefault();
		break;

/* get new suggestion for new data */
	default:
		if (this.value.length > 0) {
			this.AC.searched_term = this.value;
			this.AC.suggest(this.value);
		} else {
			_ac_cancel(this.AC);
			this.AC.close_popup();
		}
		break;
	}
}

/* iframe or XmlHttpRequest() callback */
function _ac_rpc() {
        var id = arguments[0];
        if (_ac_map[id]) {
/* we cannot shift() arguments as it is an object :( */
                _ac_map[id].process_reply.apply(_ac_map[id],arguments);
        }
}

/* parse rpc results into html for the popup 

this is an example feed from the .cfm:
parent._ac_rpc("colour3","Red","#ff0000","Red1","#ff0000","Red2","#ee0000","Red3","#cd0000","Red4","#8b0000");

*/
AC.prototype.process_reply = function() {
	var results = new Array();
	var c = 0;
	var re = new RegExp('('+this.searched_term+')', "gi");
	var nt = '<font color="blue"><b>$1</b></font>';
	for (i = 1; i < arguments.length; i += 2) {
		var name = this.highlight ? arguments[i+1].replace(re, nt) : arguments[i+1];
		var value = this.highlight ? arguments[i].replace(re, nt) : arguments[i];
		var html = "<span class='d'>("+name+")</span><span class='a'>"+value+"<br></span>";
		results[c] = new Array(arguments[i+1], arguments[i], c, html);
		c++;
	}
	
	/* (3/2/06) - If no results are found, let's tell them so! -ej- */
	if(results.length == 0){
		this.div.innerHTML = '<span class=\'ac_noresults\' id=\'AJAXnrf\'>** No Results Found **</span>';
	} else {
		this.onreply(results);
	}
}




