//
// Styling Functions
//

// calculates the total left offset

function AutoCompleteBox_getOffsetLeft()
{
  return this.cumulate(this.inputElement,"offsetLeft");
}

// calculates the total vertical offset

function AutoCompleteBox_getOffsetTop()
{
  return this.cumulate(this.inputElement,"offsetTop")+this.inputElement.offsetHeight-1;
}

// a function to add offsets together for the absolute position

function AutoCompleteBox_cumulate(element,attribute)
{
  var total=0;
  while (element){
    total += element[attribute];
    element = element.offsetParent;
  }
  return total;
}

// positions the box to align with the input field
function AutoCompleteBox_displayBox()
{
	this.hostElement.style.top = this.getOffsetTop()+"px";
	this.hostElement.style.left = this.getOffsetLeft()+"px";
	this.hostElement.style.width = this.inputElement.offsetWidth+"px";
	this.hostElement.style.visibility = "visible";
}

// show only a limited number of results at a time
function AutoCompleteBox_displayRows()
{
	for (var ctr=0; ctr<this.maxRows; ctr++) {
		this.suggestionNodes[ctr].style.display = (this.displayStartIndex <= ctr && ctr < this.displayStartIndex + this.displayLength)?"block":"none";
	}
}

// hides the box
function AutoCompleteBox_hideBox()
{
	this.hostElement.style.visibility = "hidden";
}

// highlights the row
function AutoCompleteBox_highlight(id)
{
	for (var ctr=0; ctr<this.maxRows; ctr++) {
		this.suggestionNodes[ctr].style.backgroundColor = this.rowColors[ctr];
		this.suggestionNodes[ctr].style.border = "none";
	}
	if (0<=this.suggestedIndex && this.suggestedIndex<this.maxRows) {
		this.suggestionNodes[this.suggestedIndex].style.backgroundColor = "#ccccff";
		this.suggestionNodes[this.suggestedIndex].style.borderTop = "1px solid";
		this.suggestionNodes[this.suggestedIndex].style.borderBottom = "1px solid";
	}
}


//
// XML Processing Functions
//

// returns a cross browser XMLHttpRequest object
function AutoCompleteBox_getXMLHTTP()
{
	var obj = null;
	try {
		obj = new ActiveXObject("Msxml2.XMLHTTP");
	} catch (e) {
		try {
			obj =new ActiveXObject("Microsoft.XMLHTTP");
		} catch (e2) {
			obj = null;
		}
	}
  if (!obj && typeof XMLHttpRequest != "undefined") {
    obj = new XMLHttpRequest();
  }
	return obj;
}

// create a cross browser XSLTProcessor object in xsltProcessor
function AutoCompleteBox_loadStylesheet()
{
	this.xsltProcessor = null;
	try{
		this.xsltProcessor = new ActiveXObject("Microsoft.XMLDOM");
		this.xsltProcessor.load(this.xslFileName);
	} catch (e) {
		this.xsltProcessor = null;
	}
	if (!this.xsltProcessor && typeof XSLTProcessor != "undefined") {
		this.xsltProcessor = new XSLTProcessor();
		var xslReq = this.getXMLHTTP();
		xslReq.open("GET", this.xslFileName, false);
		xslReq.send(null);
  	this.xsltProcessor.importStylesheet(xslReq.responseXML);
	}
}

// does XSLT transform in a cross browser way
function AutoCompleteBox_transform()
{
	if(typeof XSLTProcessor != "undefined"){
		var fragment = this.xsltProcessor.transformToFragment(this.xmlDoc, document);
		this.hostElement.innerHTML = "";
		this.hostElement.appendChild(fragment);
	}
	else{
		this.hostElement.innerHTML = this.xmlDoc.transformNode(this.xsltProcessor);
	}
}

//
// Document Processing Functions
//

// loads xmlFileName+param, and calls onLoadDocument when complete
function AutoCompleteBox_loadDocument(param)
{
	if (param) {
		this.xmlDocReq = this.getXMLHTTP();
		this.xmlDocReq.onreadystatechange = new Function(this.varName+".onLoadDocument()");
		this.xmlDocReq.open("GET", this.xmlFileName+param, true);
		this.xmlDocReq.send(null);
	}
	else{
		this.hostElement.innerHTML = "";
	}
}

// called by loadDocument, transforms the node, saves the results
// and displays the box
function AutoCompleteBox_onLoadDocument()
{
	if (!this.xmlDocReq) {
		return;
	}
	if (this.xmlDocReq.readyState == 4) {
		if(this.xmlDocReq.status == 200) {
			this.xmlDoc = this.xmlDocReq.responseXML;
			this.maxRows = this.xmlDoc.getElementsByTagName('row').length;

			if(this.maxRows >= 1){
				this.transform();
	
				this.suggestionNodes = new Array(this.maxRows);
				this.rowNodes = new Array(this.maxRows);
				this.rowColors = new Array(this.maxRows);
				this.rowChildren = new Array(this.maxRows);
				this.rowAttributes = new Array(this.maxRows);
				
				for (var ctr=0; ctr<this.maxRows; ctr++) {
					this.suggestionNodes[ctr] = document.getElementById("suggestion"+(ctr+1));
					this.rowNodes[ctr] = this.xmlDoc.getElementsByTagName('row')[ctr];
					this.rowColors[ctr] = this.suggestionNodes[ctr].style.backgroundColor;

					this.rowChildren[ctr] = new Array();
					var children = this.rowNodes[ctr].childNodes;
					for (var i=0; i<children.length; i++) {
						if (children[i].nodeType == 1) {/* ELEMENT_NODE */
							this.rowChildren[ctr].push(children[i]);
						}
					}
				}

				for (var ctr=0; ctr<this.maxRows; ctr++) {
					this.rowAttributes[ctr] = new Object();
					var row = this.rowNodes[ctr];
					var attributes = row.attributes;
					var children = this.rowChildren[ctr];
					
					for (var i=0; i<attributes.length; i++) {
						var name = attributes[i].name;
						var value = attributes[i].value;
						this.rowAttributes[ctr][name] = value;
					}
					for (var i=0; i<children.length; i++) {
						attributes = children[i].attributes;
						for (var j=0; j<attributes.length; j++) {
							var name = children[i].nodeName+"__"+attributes[j].name;
							var value = attributes[j].value;
							this.rowAttributes[ctr][name] = value;
						}
					}
					for(var i in this.rowAttributes[ctr]){
						this.formElementSeen[i] = '1';
					}
				}

				this.displayBox();
				this.displayRows();
				this.attachEvents();
			}
			else{
				this.hostElement.innerHTML = "";
				this.hostElement.style.visibility = "hidden";
			}
		} else {
				alert("There was a problem retrieving the XML data:\n" +
					this.xmlDocReq.statusText+","+this.xmlDocReq.status);
		}
	}
}

//
// Form Processing Functions
//

// fills the form with information from the current suggestedIndex
function AutoCompleteBox_fillForm(id,f)
{
	if (id>=0) {
 		var form = f || this.inputElement.form;
 		var rowAttribute = this.rowAttributes[id];
		for (var ctr=0; ctr<this.formElementNames.length; ctr++) {
			var name = this.formElementNames[ctr];
			if(this.formElementSeen[name]){
				this.fillInput(form[name], rowAttribute[name]);
			}
		}
	}
	else{
		this.hostElement.style.visibility = "hidden";
		this.inputElement.value = this.input;
 		var form = this.inputElement.form;
		for (var ctr=0; ctr<this.formElementNames.length; ctr++) {
			var name = this.formElementNames[ctr];
			if(this.formElementSeen[name] && form[name]!=this.inputElement){
				this.fillInput(form[name], '');
			}
		}
	}
}

// fill input based on element type
function AutoCompleteBox_fillInput(elem,value)
{
	if (!elem) {
		return;
	}
	if (elem.length && !elem.name){	//radio button
		for(var ctr=0; ctr<elem.length; ctr++){
			this.fillInput(elem[ctr],value)
		}
		return;
	}
	
	if(value == undefined){
		value = '';
	}
	switch (elem.type) {
		case 'text': case 'textarea': case 'hidden': case 'password':
			elem.value = value;
			break;
		
		case 'checkbox':
			elem.checked = value;
			break;

		case 'radio':
			elem.checked = value;
			break;

		case 'select-one': case 'select-multiple':
			for (var j = 0; j < elem.options.length; j++) {
				if(elem.options[j].value==value){
					elem.options[j].selected = true;
				}
			}
			break;
	}
}

// returns true if the input element has changed it value from the
// original value in the xml
function AutoCompleteBox_inputChanged(elem,value)
{
	if (!elem) {
		return;
	}
	if (elem.length && !elem.name){	//radio button
		for (var ctr=0; ctr<elem.length; ctr++) {
			if (this.inputChanged(elem[ctr],value)) {
				return true;
			}
		}
		return false;
	}
	if (value == undefined) {
		value = '';
	}
	switch (elem.type) {
		case 'text': case 'textarea': case 'hidden': case 'password':
			return elem.value && elem.value != value;
			break;
		
		case 'checkbox':
			return elem.checked != value;
			break;

		case 'radio':
			return elem.checked != value;
			break;

		case 'select-one': case 'select-multiple':
			for (var j = 0; j < elem.options.length; j++) {
				if(elem.options[j].value==value){
					return elem.options[j].selected != true;
				}
			}
			break;
	}
}

// returns the selected value with the attribute name
function AutoCompleteBox_getSelected(name)
{
	if(this.rowAttributes && this.rowAttributes[this.suggestedIndex]){
		return this.rowAttributes[this.suggestedIndex][name];
	}
}

// returns a list of form elements that have been changed
function AutoCompleteBox_changeList(original)
{
	var changeList = "";
	if (this.suggestedIndex>=0) {
 		var form = this.inputElement.form;
 		var rowAttribute = this.rowAttributes[this.suggestedIndex];
		if(original){
			var originalList = original.split(",");
			for (var ctr=0; ctr<this.originalList.length; ctr++) {
				var name = this.originalList[ctr];
				if(this.inputChanged(form[name],rowAttribute[name])){
					changeList = changeList==""?name:changeList+","+name;
				}
			}
		}
		else{
			for (var ctr=0; ctr<this.formElementNames.length; ctr++) {
				var name = this.formElementNames[ctr];
				if(this.formElementSeen[name] && this.inputChanged(form[name],rowAttribute[name])){
					changeList = changeList==""?name:changeList+","+name;
				}
			}
		}
	}
	return changeList;
}

//
// Events Processing Functions
//

// attach mouseUp/onClick to the rows in the table
function AutoCompleteBox_attachEvents()
{
	for (var ctr=0; ctr<this.maxRows; ctr++) {
		var node = this.suggestionNodes[ctr];
		node.onmouseover = new Function(this.varName+".onmouseover("+ctr+")");
		node.onclick = new Function(this.varName+".onclick("+ctr+")");
	}
}

function AutoCompleteBox_onkeyup(event)
{
	if(!event&&window.event) {
		event=window.event;
	}
	if (event.keyCode==40 || event.keyCode==38 || event.keyCode==33 || event.keyCode==34) {
		if(!this.savedTypedInput){
			this.savedTypedInput = this.inputElement.value;
		}
		if(this.hostElement.style.visibility != "hidden"){
				if (event.keyCode==40) {
					this.suggestedIndex++;
				}
				else if (event.keyCode==38) {
					this.suggestedIndex--;
				}
				else if (event.keyCode==33) {
					this.suggestedIndex-=10;
				}
				else if (event.keyCode==34) {
					this.suggestedIndex+=10;
				}
				this.suggestedIndex = Math.min(this.maxRows-1, this.suggestedIndex);
				this.suggestedIndex = Math.max(-1 , this.suggestedIndex);
				if (this.suggestedIndex != -1) {
					if (this.suggestedIndex < this.displayStartIndex){
						this.displayStartIndex = this.suggestedIndex;
					}
					else if (this.suggestedIndex >= this.displayStartIndex + this.displayLength){
						this.displayStartIndex = this.suggestedIndex - this.displayLength + 1;
					}
					this.displayRows();
				}
				else{
					if(this.savedTypedInput){
						this.inputElement.value = this.savedTypedInput;
						this.savedTypedInput = '';
						this.hostElement.style.visibility = "hidden";
						this.suggestedIndex = -1;
						this.displayStartIndex = 0;
					}
				}
				this.highlight(this.suggestedIndex);
				this.fillForm(this.suggestedIndex);
		}
		else if(event.keyCode==40){
			this.displayBox();
			this.displayRows();
		}
	}
	else if (event.keyCode==39 || event.keyCode==27) {		//right key, esc key
		if(this.savedTypedInput){
			this.inputElement.value = this.savedTypedInput;
			this.savedTypedInput = '';
		}
		this.hostElement.style.visibility = "hidden";
		this.suggestedIndex = -1;
		this.displayStartIndex = 0;
		this.fillForm(this.suggestedIndex);
	}
	else if (event.keyCode==13 || event.keyCode==3) {		//enter key
		this.select(this.suggestedIndex);
	}
	else if (event.keyCode==8){				//backspace
		if(this.savedTypedInput){
			this.inputElement.value = this.savedTypedInput.substring(0,this.savedTypedInput.length-1);
			this.savedTypedInput = '';
		}
		this.input = this.inputElement.value;
		this.suggestedIndex = -1;
		this.sendInput();
	}
	else {
		var letter = String.fromCharCode(event.keyCode).toLowerCase();
		if(this.savedTypedInput && letter.match(/[a-zA-Z0-9 ]/)){
			this.inputElement.value = this.savedTypedInput+letter;
			this.savedTypedInput = '';
		}
		this.input = this.inputElement.value;
		this.suggestedIndex = -1;
		this.sendInput();
	}
}

function AutoCompleteBox_onblur(event)
{
	this.select(this.suggestedIndex);
}

function AutoCompleteBox_onmouseover(id)
{
	this.suggestedIndex = id;
	this.highlight(id);
	this.fillForm(id);
}

function AutoCompleteBox_onclick(id)
{
	this.select(id);
}

// use the current suggestedIndex. Called with an enter or
// mouse click
function AutoCompleteBox_select(id)
{
	this.hideBox();
	this.fillForm(id);
}

// block form submission if box is not hidden
function AutoCompleteBox_onsubmit()
{
	return this.hostElement.style.visibility == "hidden";
}

//
// Input Processing Functions
//
	
// make a new query, employs timers to control the frequency
function AutoCompleteBox_sendInput()
{
	if (this.ready == true){
		this.loadDocument(this.input);
		this.ready = false;
		this.lastInput = this.input;
		setTimeout(this.varName+".checkInput()", 700);
	}
}

// retry query if the input has changed
function AutoCompleteBox_checkInput()
{
	if (this.ready == true) {
		this.loadDocument(this.input);
		this.ready = false;
		this.lastInput = this.input;
		setTimeout(this.varName+".checkInput()", 700);
	} else {
		this.ready = true;
		if (this.lastInput != this.input) {
			this.checkInput();
		}
	}
}

function AutoCompleteBox_blurInput()
{
	this.inputElement.blur();
}

// create a new AutoCompleteBox based on some data, fixed by some stylesheet
function AutoCompleteBox(xslFileName,xmlFileName,hostElementId,inputElement,varName)
{
	//
	// Styling Functions
	//
	this.getOffsetLeft = AutoCompleteBox_getOffsetLeft;
	this.getOffsetTop = AutoCompleteBox_getOffsetTop;
	this.cumulate = AutoCompleteBox_cumulate;
	this.displayBox = AutoCompleteBox_displayBox;
	this.displayRows = AutoCompleteBox_displayRows;
	this.hideBox = AutoCompleteBox_hideBox;
	this.highlight = AutoCompleteBox_highlight;

	//
	// XML Processing Functions
	//
	this.getXMLHTTP = AutoCompleteBox_getXMLHTTP;
	this.loadStylesheet = AutoCompleteBox_loadStylesheet;
	this.transform = AutoCompleteBox_transform;

	//
	// Document Processing Functions
	//
	this.loadDocument = AutoCompleteBox_loadDocument;
	this.onLoadDocument = AutoCompleteBox_onLoadDocument;

	//
	// Form Processing Functions
	//
	this.fillForm = AutoCompleteBox_fillForm;
	this.fillInput = AutoCompleteBox_fillInput;
	this.inputChanged = AutoCompleteBox_inputChanged;
	this.getSelected = AutoCompleteBox_getSelected;
	this.changeList = AutoCompleteBox_changeList;

	//
	// Events Processing Functions
	//
	this.attachEvents = AutoCompleteBox_attachEvents;
	this.onkeyup = AutoCompleteBox_onkeyup;
	this.onmouseover = AutoCompleteBox_onmouseover;
	this.onclick = AutoCompleteBox_onclick;
	this.select = AutoCompleteBox_select;
	this.onsubmit = AutoCompleteBox_onsubmit;
	this.onblur = AutoCompleteBox_onblur;

	//
	// Input Processing Functions
	//
	this.sendInput = AutoCompleteBox_sendInput;
	this.checkInput = AutoCompleteBox_checkInput;


	

	// set the xsl
	this.xslFileName = xslFileName;
	this.xsltProcessor = null;
	this.loadStylesheet();

	// set the xml
	this.xmlFileName = xmlFileName;
	this.xmlDocReq = undefined;
	this.xmlDoc = undefined;
	this.maxRows = 0;
	this.suggestionNodes = undefined;

	// set our reference name
	this.varName = varName;

	// set our main elements, the host and the input box
	this.displayStartIndex = 0;
	this.lastDisplayStartIndex = -1;
	this.displayLength = 10;

	this.inputElement = inputElement;
	this.inputElement.onkeyup = new Function("event",this.varName+".onkeyup(event)");
	this.inputElement.onblur = new Function("event",this.varName+".onblur(event)");
	this.formElement = inputElement.form;
	this.formElementNames = new Array(this.formElement.elements.length);
	for(var ctr=0; ctr<this.formElement.elements.length; ctr++){
		this.formElementNames[ctr] = this.formElement.elements[ctr].name;
	}
	this.formElementSeen = new Object();

	this.hostElement = document.getElementById(hostElementId);
	this.hostElement.style.position = "absolute";
	this.hostElement.style.visibility = "hidden";

	// set our variables for selection
	this.suggestedIndex = -1;
	this.ready = true;

	// set some input control
	this.lastInput = '';
	this.input = '';
	this.savedTypedInput = '';
}