Ajax.xmlHttp = null;
Ajax.queue = new Array();
Ajax.prototype.xmlHttp = null;
Ajax.prototype.onqueueempty = function() {}
Ajax.prototype.charset = 'UTF-8';

Ajax.RESULT_OBJECT = 1;
Ajax.RESULT_XML = 2;
Ajax.RESULT_TEXT = 3;
Ajax.RESULT_HTML = 4;

function Ajax(args) {
	if (!Ajax.xmlHttp) Ajax.xmlHttp = Ajax.getXmlHttp();
	if (!args.queue) args.queue == true;
	if (!args.requesttype) args.requesttype = 'application/x-www-form-urlencoded';
	if (!args.method) args.method = 'POST';
	if (!args.resultMode) args.resultMode = Ajax.RESULT_OBJECT;
	if (args.params && args.params.tagName && args.params.tagName.toLowerCase() == 'form') args.params = this.getForm(args.params);

	
	
	//this.xmlHttp = (args.queue == false ? this.getXmlHttp() : Ajax.xmlHttp);
	

	if (args.xmlHttp) this.xmlHttp = args.xmlHttp;
	else this.xmlHttp = Ajax.xmlHttp;
	var self = this;
	
	//Check there's not a request in progress
	if (this.xmlHttp.readyState == 4 || this.xmlHttp.readyState == 0) {		
		if (args.requesttype == 'application/x-www-form-urlencoded') var paramstr = this.objectToPostArray(args.params);
		else paramstr = params;
		
		this.callback = args.callback ? args.callback : function() {};
		try {
			this.xmlHttp.open(args.method, args.url + ((args.method == 'GET') ? paramstr : ''), true);
		}
		catch (e) {}
		
		this.xmlHttp.onreadystatechange = function() {
			self.readyState = self.xmlHttp.readyState;
			
			if (self.xmlHttp.readyState == 4) {

				if (args.resultMode == Ajax.RESULT_TEXT) self.callback(self.xmlHttp.responseText);
				else if (args.resultMode == Ajax.RESULT_XML) self.callback((self.xmlHttp.responseXML) ? self.xmlHttp.responseXML.documentElement : null);
				else if (args.resultMode == Ajax.RESULT_OBJECT) self.callback(self.xmlHttp.responseXML == null ? null : self.responseObject(self.xmlHttp.responseXML.documentElement));
				else if (args.resultMode == Ajax.RESULT_HTML) self.callback(self.xmlToHtmlElement(self.xmlHttp.responseXML.documentElement));
				else alert('Ajax Error: unknown result mode');
				
				if (Ajax.queue.length > 0) new Ajax(Ajax.queue.shift());				
				else {
					//self.oncomplete();
					self.onqueueempty();
				}
			}
		}
		
		if (args.method == 'POST') {
			this.xmlHttp.setRequestHeader('Content-type', args.requesttype + '; charset=' + this.charset);
			this.xmlHttp.setRequestHeader('Content-length', paramstr.length);
			this.xmlHttp.setRequestHeader('Connection', 'close');
			
		}
		this.xmlHttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
		this.xmlHttp.send((args.method == 'POST') ? paramstr : null);
	}
	else Ajax.queue[Ajax.queue.length] = args; //there is already a request in progress, add the request to the queue
}

Ajax.prototype.abort = function() {
	this.xmlhttp.abort();
}

Ajax.reset = function() {
	Ajax.xmlHttp = Ajax.getXmlHttp();
}

Ajax.prototype.xmlToHtmlElement = function(xmlnode, oldnode) {
	//IE hacks. Can't just construct a table in IE for some reason.
	if (xmlnode.tagName.toLowerCase() == 'tr') {
		var newNode = oldnode.insertRow(-1);
		if (!newNode) {
			var table = document.createElement('table');
			var newNode = table.insertRow(-1);
		}
	}
	else if (xmlnode.tagName.toLowerCase() == 'td') var newNode = oldnode.insertCell(-1);
	else if (navigator.appName.indexOf('Explorer') > -1 && xmlnode.tagName == 'input' && xmlnode.getAttribute('type') == 'radio') {
		var newNode = document.createElement('<input type="radio" name="' + xmlnode.getAttribute('name') + '" />');
	}
	else var newNode = document.createElement(xmlnode.tagName);

	if (xmlnode.attributes) {
		for (var attribute = 0; attribute <= xmlnode.attributes.length-1; attribute++) {
			//No if-elses needed in FF, but IE's setAttrbibute doesn't seem to work on anything

			//For firefox + all other attributes

			if (navigator.appName.indexOf('Explorer') == -1) {
				try {
					newNode.setAttribute(xmlnode.attributes[attribute].nodeName, xmlnode.attributes[attribute].nodeValue);
				}
				catch (e) {
					//	alert(newNode + '\n\n' + oldnode.tagName + '\n\n' + xmlnode.tagName + '\n\n' + x);
				}
			}
			else {
				//Needed for IE. setAttribute doesn't work on a lot of attributes and they need to be set directly.
				//if (xmlnode.attributes[attribute].nodeName.toLowerCase() == 'class') newNode.setAttribute('className',xmlnode.attributes[attribute].nodeValue);
				switch (xmlnode.attributes[attribute].nodeName.toLowerCase()) {
					case 'class':  		newNode.setAttribute('className',xmlnode.attributes[attribute].nodeValue); newNode.className 		= xmlnode.attributes[attribute].nodeValue;	break;
					case 'style':  		newNode.style.cssText 	= xmlnode.attributes[attribute].nodeValue;		break;
					case 'colspan':		newNode.colSpan 		= xmlnode.attributes[attribute].nodeValue;		break;
					case 'rowspan':		newNode.rowSpan 		= xmlnode.attributes[attribute].nodeValue;		break;
					case 'onmousedown':	newNode.onmousedown		= new Function('e', 'var event = e || window.event;  ' + xmlnode.attributes[attribute].nodeValue); break;
					case 'onmousemove':	newNode.onmousemove 	= new Function('e', 'var event = e || window.event;  ' + xmlnode.attributes[attribute].nodeValue); break;
					case 'onmouseup':	newNode.onmouseup 		= new Function('e', 'var event = e || window.event;  ' + xmlnode.attributes[attribute].nodeValue); break;
					case 'onclick':		newNode.onclick 		= new Function('e', 'var event = e || window.event;  ' + xmlnode.attributes[attribute].nodeValue); break;
					case 'onchange':	newNode.onchange		= new Function('e', 'var event = e || window.event;  ' + xmlnode.attributes[attribute].nodeValue); break;
					case 'onkeyup':		newNode.onkeyup 		= new Function('e', 'var event = e || window.event;  ' + xmlnode.attributes[attribute].nodeValue); break;
					case 'onkeydown':	newNode.onkeydown 		= new Function('e', 'var event = e || window.event;  ' + xmlnode.attributes[attribute].nodeValue); break;
					case 'name':		newNode.name 			= xmlnode.attributes[attribute].nodeValue;		break;
					case 'value':		newNode.value 			= xmlnode.attributes[attribute].nodeValue;		break;
					default: newNode.setAttribute(xmlnode.attributes[attribute].nodeName, xmlnode.attributes[attribute].nodeValue);
				}				
			}		
		}
	}
	//Another IE hack, TABLE elements need TBODY elements if they dont have them already or the table is not displayed.
	var tbl = null;
	if (newNode.tagName.toLowerCase() == 'table') {
		//if there are tbody's or theads on the table already, don't add a default one.
		if (xmlnode.getElementsByTagName('tbody').length == 0 && xmlnode.getElementsByTagName('thead').length == 0) {
			//If there is no tbody, create one.
			if (!oldnode) oldnode = document.createElement('div');
			oldnode.appendChild(newNode);
			tbl = newNode;
			var tbody = document.createElement('tbody');
			newNode.appendChild(tbody);
			var newNode = tbody;
		}
	}
	else if (newNode.tagName.toLowerCase() == 'form' && newNode.name) {
		document.forms[newNode.name] = newNode;
	}
	
	
	if (xmlnode.childNodes.length == 0) {
		//newNode.appendChild(document.createTextNode(''));
	}
	else {
		for (var node=0; node <= xmlnode.childNodes.length-1; node++) {
			if (xmlnode.childNodes[node].nodeType === 3 || xmlnode.childNodes[node].nodeType === 1) { //to ignore comments
				newNode.appendChild((xmlnode.childNodes[node].nodeType === 3) ? document.createTextNode(xmlnode.childNodes[node].nodeValue) : this.xmlToHtmlElement(xmlnode.childNodes[node], newNode));
			}
		}
	}
	
	return tbl || newNode;
}


Ajax.prototype.objectToPostArray = function(object, prefix, postfix) {
	if (!prefix) prefix = '';
	if (!postfix) postfix = '';
	
	var flat = '';
	for (var i in object) {
		if (typeof(object[i]) == 'object' || typeof(object[i]) == 'array') flat += '&' + this.objectToPostArray(object[i], prefix + i+'[',']');
		else flat += '&' + prefix + i + postfix + '=' + encodeURIComponent(object[i]);
	}	
	return flat;
}

Ajax.getXmlHttp = function() {
	try { // Firefox, Opera 8.0+, Safari
		var xmlhttp = new XMLHttpRequest();
	}
	catch (e) { // Internet Explorer
		try {
			var xmlhttp = new ActiveXObject('Msxml2.XMLHTTP');
		}
		catch (e) {
			try {
				var xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');
			}
			catch (e) {
				return false;
			}
		}
	}
	Ajax.xmlHttp = xmlhttp;
	return xmlhttp;
}

Ajax.prototype.getForm = function(form) {
	var params = {};
	
	for (var i = 0; i < form.elements.length; i++) {
		if (!form.elements[i].name) continue;
		if (form.elements[i].type && (form.elements[i].type == 'radio')) {
			if (form.elements[i].checked) params[form.elements[i].name] = form.elements[i].value;
		}
		
		else if (form.elements[i].type == 'checkbox') {
			var name = form.elements[i].name.split('[]')[0];
			if (form[form.elements[i].name].length) {				
				if (!params[name]) params[name] = new Array();
				if (form.elements[i].checked) params[name].push(form.elements[i].value);							
			}
			else {
				if (form.elements[i].checked) params[form.elements[i].name] = form.elements[i].value;
			}			
		}
		else if (form.elements[i].tagName.toLowerCase() == 'select' && form.elements[i].multiple == true) {
			var options = form.elements[i].getElementsByTagName('option');
			
			params[form.elements[i].name] = new Array();
			for (var j = 0; j < options.length; j++) {
				if (options[j].selected) params[form.elements[i].name].push(options[j].value);
			}

		}		
		
		else params[form.elements[i].name] = form.elements[i].value;
	}
	
	return params;
}

//Converts an XML data structure into a javascript object
Ajax.prototype.responseObject = function(xmlnode) {
	var newObj = new Object();

	if (xmlnode) {
		//loop through all the child nodes for the current node
		for (var node=0; node <= xmlnode.childNodes.length-1; node++) {
			if (xmlnode.childNodes[node].nodeType != 3) {
				//ignore (remove) whitespace at the start of lines.
				if (xmlnode.childNodes[node].hasChildNodes()) {
					if (xmlnode.childNodes[node].firstChild.nodeType == 3) {
						if (xmlnode.childNodes[node].firstChild.nodeValue.indexOf("\n") == 0 || xmlnode.childNodes[node].firstChild.nodeValue.indexOf("\t") == 0) {
							xmlnode.childNodes[node].removeChild(xmlnode.childNodes[node].firstChild);
						}
					}
				}
				//find out how many sibling nodes there are with the same name as the current one
				var ncount = 0;
				for (var i=0;i<xmlnode.childNodes.length;i++) {
					if (xmlnode.childNodes[node].nodeName == xmlnode.childNodes[i].nodeName) ncount++;
				}
				//ncount should be > 0. It should at least find itself.
				//get the value for the current node
				var nodeVal = (xmlnode.childNodes[node].hasChildNodes()) ? ((xmlnode.childNodes[node].firstChild.nodeType == 3) ? xmlnode.childNodes[node].firstChild.nodeValue : this.responseObject(xmlnode.childNodes[node])) : '';


				//if there is  more than one tag with this name, create an array
				if (ncount > 1) {
					if (!newObj[xmlnode.childNodes[node].nodeName])	{
						newObj[xmlnode.childNodes[node].nodeName] = new Array();
						newObj[xmlnode.childNodes[node].nodeName].isArray = true;
					}
					newObj[xmlnode.childNodes[node].nodeName].type = 'array';
					newObj[xmlnode.childNodes[node].nodeName][newObj[xmlnode.childNodes[node].nodeName].length] = nodeVal;
				}
				//if there is only one tag of the kind, dont use an array.
				else {
					newObj[xmlnode.childNodes[node].nodeName] =  (typeof(nodeVal) == 'object') ? $.clone(nodeVal) : nodeVal;
					//Even though there is no array, make this accessible as node[0] in case the script is expecting 1 or more items.
					newObj[xmlnode.childNodes[node].nodeName][0] = nodeVal;
					//Give the user a way of checking whether an array has been returned.
					newObj[xmlnode.childNodes[node].nodeName].isArray = false;
					//report length so it is still loopable
					newObj[xmlnode.childNodes[node].nodeName].length = 1;
					//This will cause problems if push()/splice()/etc/other array methods are used on the result though.
				}
			}
		}
		return newObj;
	}
	else return null;
}


$.elementPrototype.load = function(args) {
	args.resultMode = Ajax.RESULT_HTML;
	var self = this;
	this.empty();
	var onloaded = args.onloaded;
	
	if (args.loadingClass) this.addClass(args.loadingClass);
	
	
	if (args.callback) var cb = args.callback;
	args.callback = function(result) {
		self.removeClass(args.loadingClass)
		self.empty();
		self.appendChild(result);
		onloaded();
		cb();
	}
	new Ajax(args);	
}