
// AJAX null response object
function AJAX_DefaultResponse(args) {
	return true;
}

// AJAX response object class
function AJAX_Handler(response) {
	this.command = new Function();
	this.init = false;

	// prevent non-function handlers
	if (typeof response == "function") {
		this.command = response;
		this.init = true;
	} else {
		//alert("AJAX Error: Response handler is not a function!");
		this.init = false;
	}

	// assign handler
	this.handle_Response = function(args) {
		if (this.init) { this.command(args); }
		else { alert("AJAX Error: Response handler is not initialized!"); }
	}
}

// AJAX query object class
function AJAX_Query(type, args, response) {
	// query properties
	this.type = type;
	this.args = args;

	// response properties
	this.responseText = 0;
	this.responseXML = 0;
	this.statusText = 0;
	this.status = 0;

	// response function
	this.response = new AJAX_Handler(response);
	this.updateID = 0;
}

// AJAX interaction object class
function AJAX_Object() {
	// cross browser object initialization
	this.init_Request = function() {
		var obj = null;

		if (window.XMLHttpRequest) {
			obj = new XMLHttpRequest();
			if (obj.overrideMimeType) { obj.overrideMimeType('text/html'); }
		} else if (window.ActiveXObject) { obj = new ActiveXObject("Microsoft.XMLHTTP"); }
		
		// notify user if browser is not AJAX capable
		if (!obj) { alert("AJAX Error: Unable to create XMLHttp object!"); }

		return obj;
	}

	// returns the name of the longest arg
	this.get_SplitArg = function(args) {
		var arg_parts = args.split("&");
		var longest_arg = "";
	
		for (var i = 0; i < arg_parts.length; i++) {
			if (arg_parts[i].length > longest_arg.length) {
				longest_arg = arg_parts[i];
			}
		}
		
		// verbose debugger
		if (this.verbose) { alert("AJAX Debugger:\n\n" + "split_arg=" + longest_arg); }

		return longest_arg;
	}

	// splits request and adds parts to process queue
	this.split_Request = function(type, args, response) {
		var pos = get_SplitArg(args);
		var base_arg = pos.substring(0, (pos.indexOf("=") + 1));
		var split_value = pos.substring((pos.indexOf("=") + 1), pos.length);
		var split_name = base_arg;
		var base_arg = args.substring(0, (args.indexOf(base_arg) - 1));

		// queue base request
		this.queue[this.queue.length] = new AJAX_Query(type, base_arg, response);

		// split longest arg into parts
		var arg_parts = new Array();
		while (split_value.length > 0) {
			if (split_value.length >= this.postlength) {
				arg_parts[arg_parts.length] = split_value.substring(0, this.postlength);
				split_value = split_value.substring(this.postlength, split_value.length);
			} else {
				arg_parts[arg_parts.length] = split_value;
				split_value = "";
			}
		}

		// queue split requests
		for (i = 0; i < arg_parts.length; i++) {
			this.queue[this.queue.length] = new AJAX_Query(type, "action=update&" + split_name + "=" + arg_parts[i], response);
		}
	}
	
	// returns true if current request is identical to last request
	this.check_Request = function(type, args) {
		var duplicate = false;

		if ((this.last.type == type) && (this.last.args == args)) {
			duplicate = true;
		}

		// verbose debugger
		if ((this.verbose) && (duplicate)) { alert("AJAX Debugger: Duplicate request aborted!"); }
		
		return duplicate;
	}

	// returns true if a response is numeric
	this.check_Numeric = function(response) {
		var valid_chars = "0123456789";
		var numeric = true;
		var char;

		for (i = 0; ((i < response.length) && (numeric == true)); i++) { 
			char = response.charAt(i);
			if (valid_chars.indexOf(char) == -1) { numeric = false; }
		}
			
		return numeric;
	}
	
	// adds request to process queue
	this.queue_Request = function(type, args, response) {
		// prevent null requests
		if (this.url == "") {
			alert("AJAX Error: URL not specified!");
			return;
		}

		// prevent duplicate requests
		if (!this.cache) {
			if (this.check_Request(type, args)) { return; }
			else { this.last = new AJAX_Query(type, args, response); }
		}

		// prevent request uri too long errors
		if (args.length <= this.postlength) {
			this.queue[this.queue.length] = new AJAX_Query(type, args, response);
		} else {
			// verbose debugger
			if (this.verbose) { alert("AJAX Debugger: Splitting request..."); }
			this.split_Request(type, args, response);
		}

		if (this.idle) { this.process_Queue(); }
	}

	// sends AJAX request
	this.send_Request = function(type, args, response) {
		// scope callback
		var _this = this;
		var query = this.base + this.url;
		
		// append last record id for udpates
		if ((args.substring(0, 14) == "action=update&") && (this.last.updateID != 0)) {
			args += "&update_id=" + this.last.updateID;
		}

		// get requests must be less then 512 bytes
		if (args.length >= this.getlength) {
			if (type == "aget") { type = "apost"; }
			else if (type == "sget") { type = "spost"; }

			// update last snapshot
			this.last.type = type;
		}

		// prevent IE6 caching requests
		if (!this.cache) {
			args += "&cache_stamp=" + new Date().getTime();
		}

		// verbose debugger
		if (this.verbose) { alert("AJAX Debugger:\n\n" + "query=" + query + "\n" + "args=" + args); }

		// set state
		this.idle = false;

		// send request
		if (type == "aget") {
			this.http.open("get", query + "?" + args, true);
			this.http.onreadystatechange = function() { _this.handle_Response(response); }
			this.http.send(null);
		} else if (type == "sget") {
			this.http.open("get", query + "?" + args, false);
			this.http.onreadystatechange = function() { _this.handle_Response(response); }
			this.http.send(null);
		} else if (type == "apost") {
			this.http.open("post", query, true);
			this.http.onreadystatechange = function() { _this.handle_Response(response); }
			this.http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
			this.http.setRequestHeader("Content-Length", args.length);
			this.http.setRequestHeader("Connection", "close");
			this.http.send(args);
		} else if (type == "spost") {
			this.http.open("post", query, false);
			this.http.onreadystatechange = function() { _this.handle_Response(response); }
			this.http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
			this.http.setRequestHeader("Content-Length", args.length);
			this.http.setRequestHeader("Connection", "close");
			this.http.send(args);
		}
	}
	
	// handles server response to an AJAX request
	this.handle_Response = function(response) {
		// wait for response
		if (this.http.readyState == 4) {
			// check if the request was successfull
			if (this.http.status == 200) { 
				this.last.responseText = this.http.responseText;
				this.last.responseXML = this.http.responseXML;
				this.last.statusText = this.http.statusText;
				this.last.status = this.http.status;

				// verbose debugger
				if (this.verbose) { alert("AJAX Debugger:\n\n" + "responseText=" + this.http.responseText + "\n\n" + "statusText=" + this.http.statusText); }

				// numeric only responses are considered update ids
				if (this.check_Numeric(this.http.responseText)) {
					this.last.updateID = this.http.responseText;
					
					// verbose debugger
					if (this.verbose) { alert("AJAX Debugger:\n\n" + "updateID=" + this.last.updateID); }
				} else {
					this.last.updateID = 0;
				}

				// call response handler
				response.handle_Response();
			}
			/*
			else {
				// notify user AJAX request failed
				alert("AJAX Error: " + this.http.statusText);
			}
			*/

			this.idle = true;
			this.process_Queue();
		}
	}

	// process jobs in queue
	this.process_Queue = function() {
		if (this.queue.length > 0) {
			// remove and process first queued item
			var current_request = this.queue.shift();
			this.send_Request(current_request.type, current_request.args, current_request.response);
		}
	}
	
	// remove all queued items
	this.clear_Queue = function() {
		this.queue = new Array();
	}
	
	// abort current request
	this.abort_Request = function() {
		if (!this.idle) {
			this.http.abort();
			this.idle = true;
		}
	}
	
	this.on_Unload = function() {
		// clear current requests
		this.abort_Request();
		this.clear_Queue();
	
		// clear global object
		AJAX_Destructor();
	}

	// object properties
	this.http = undefined;
	this.last = new AJAX_Query(null, null, AJAX_DefaultResponse);
	this.base = location.protocol + "//" + location.hostname;
	this.url = "";

	// cache properties
	this.cache = false;

	// debugger properties
	this.verbose = false;

	// queue properties
	this.idle = true;
	this.getlength = 512;
	this.postlength = 4096;
	this.queue = new Array();
}

// AJAX object destructor
function AJAX_Destructor() {
	if (AJAX) { delete AJAX; }
	return true;
}
