/**
 * @fileoverview
 * 
 * The Frozen.Ajax JavaScript library provides objects to extend in order to
 * implement event-driven applications, as well as a full XMLHttpRequest
 * wrapper object, with an accompanying class to manage garbage collection
 * and pooling.
 * 
 <pre>
 	Copyright (c) 2006 Frozen O Productions
	Written by Shawn Lauriat
	All rights reserved.
 
	Redistribution and use in source and binary forms, with or without
	modification, are permitted provided that the following conditions are met:

	- Redistributions of source code must retain the above copyright notice,
		this list of conditions and the following disclaimer.
	- Redistributions in binary form must reproduce the above copyright notice,
		this list of conditions and the following disclaimer in the
		documentation and/or other materials provided with the distribution.
	- Neither the name of Frozen O Productions nor the names of its
		contributors may be used to endorse or promote products derived from
		this software without specific prior written permission.
 
	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
	AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
	ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
	LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
	CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
	POSSIBILITY OF SUCH DAMAGE.
 </pre>
 */

if (typeof Frozen.Ajax == "undefined") {
	/**
	 * The Frozen.Ajax object acts as the container for
	 * all things in the Frozen.Ajax library.
	 * @package
	 */
	Frozen.Ajax = { };
	
	/**
	 * A CustomEvent to pass Requests when loaded
	 * @construct
	 * @param {Frozen.Ajax.Request} request A Frozen.Ajax.Request
	 * instance to pass along inside the event
	 */
	Frozen.Ajax.Event = function(request) {
		this.request = request;
	}
	Frozen.Ajax.Event.prototype = new Frozen.Event.Event;
	Frozen.Ajax.Event.prototype.type = "ajax";
	Frozen.Ajax.Event.prototype.request = null;
	
	/**
	 * Instantiated by the RequestManager, not directly, in order
	 * to more easily take advantage of pooling.
	 * @construct
	 * @param {int} id The key of the Frozen.Ajax.Request in the
	 * Frozen.Ajax.RequestManager.requests request pool
	 */
	Frozen.Ajax.Request = function(id) {
		this.id = id;
		
		// If the browser follows the standard
		if (window.XMLHttpRequest) {
			this.xhr = new XMLHttpRequest();
			// ...otherwise, if Internet Explorer < 7
		} else if (window.ActiveXObject) {
			this.xhr = new ActiveXObject("Microsoft.XMLHTTP");
		}
		// Callback for this.xhr.onreadystatechanged
		var dis = this;
		this.xhr.onreadystatechange = function() {
			Frozen.Ajax.Request.prototype.stateChanged.apply(
				dis, arguments
			);
		}
		this.events = {
			abort : [],
			data : [],
			fail : [],
			internalservererror : [],
			load : [],
			notfound : [],
			notmodified : [],
			open : [],
			partialload : [],
			requestedrangenotsatisfiable : [],
			send : [],
			unauthorized : []
		};
	}
	Frozen.Ajax.Request.prototype = new Frozen.Event.Dispatcher;
	/**
	 * This object supports a wide range of events, as XMLHttpRequest
	 * objects can return many different HTTP states, depending on the
	 * error, partial content, or conditional request made.
	 */
	Frozen.Ajax.Request.prototype.events = {};
	/**
	 * Holds the various HTTP returned statuses supported and
	 * can easily get extended to support more.
	 */
	Frozen.Ajax.Request.prototype.statusCodeEvents = {
		200 : "load",
		206 : "partialload",
		304 : "notmodified",
		401 : "unauthorized",
		404 : "notfound",
		416 : "requestedrangenotsatisfiable",
		500 : "internalservererror"
	};
	/**
	 * Used to emulate this meaning this
	 */
	Frozen.Ajax.Request.prototype.id = null;
	Frozen.Ajax.Request.prototype.xhr = null;
	Frozen.Ajax.Request.prototype.aborted = false;
	
	/**
	 * Store key/value pairs for the request headers
	 */
	Frozen.Ajax.Request.prototype.headers = {};
	
	/**
	 * Store variable/value pairs for the GET request
	 */
	Frozen.Ajax.Request.prototype.get = {};
	
	/**
	 * Store variable/value pairs for the POST request
	 */
	Frozen.Ajax.Request.prototype.post = {};
	
	/**
	 * Decide whether or not to send this.post
	 */
	Frozen.Ajax.Request.prototype.method = "POST";
	
	/**
	 * Callback for this.xhr.onreadystatechanged
	 */
	Frozen.Ajax.Request.prototype.stateChanged = function() {
		// Only trigger load if finished returning
		switch(this.xhr.readyState) {
			case 3:
				var e = new Frozen.Ajax.Event(this);
				this.dispatchEvent("data", e);
				break;
			case 4:
				try {
					if (this.statusCodeEvents[this.xhr.status]) {
						var e = new Frozen.Ajax.Event(this);
						this.dispatchEvent(this.statusCodeEvents[this.xhr.status], e);
					}
				// Failed request?
				} catch (ex) {
					var e = new Frozen.Ajax.Event(this);
					this.dispatchEvent("fail", e);
				}
		}
	}
	
	/**
	 * Simple alias to abort the call
	 */
	Frozen.Ajax.Request.prototype.abort = function() {
		this.aborted = true;
		var event = new Frozen.Ajax.Event(this);
		event.returned = this.xhr.abort();
		this.dispatchEvent("abort", event);
		return event.returned;
	}
	
	/**
	 * Send all of the headers specified
	 */
	Frozen.Ajax.Request.prototype.sendHeaders = function() {
		for (i in this.headers) {
			this.xhr.setRequestHeader(i, this.headers[i]);
		}
	}
	
	/**
	 * Alias to this.xhr.open, which stores the method in
	 * order to decide whether to bother concatinating
	 * this.post into url-encoded string form. Note: this
	 * only takes the baseurl as its url, since it encodes
	 * and concatinates this.get into the GET parameters.
	 */
	Frozen.Ajax.Request.prototype.open = function(method, url) {
		this.method = method.toUpperCase();
		var real_get = this.urlEncodeObject(this.get);
		url += "?" + real_get;
		var async = (typeof arguments[2] != "boolean") ? true : arguments[2];
		var user = (typeof arguments[2] != "String") ? null : arguments[3];
		var pass = (typeof arguments[2] != "String") ? null : arguments[4];
		var event = new Frozen.Ajax.Event(this);
		event.returned = this.xhr.open(
			this.method,
			url,
			async,
			user,
			pass
		);
		this.dispatchEvent("open", event);
		return event.returned;
	}
	
	/**
	 * Simple alias to this.xhr.send, adjusting this.post
	 * depending on the request method specified.
	 */
	Frozen.Ajax.Request.prototype.send = function() {
		if (this.aborted) {
			return false;
		}
		this.sendHeaders();
		var real_post = "";
		var event = new Frozen.Ajax.Event(this);
		if (this.method == "POST") {
			this.xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
			real_post = this.urlEncodeObject(this.post);
			event.returned = this.xhr.send(real_post);
		} else {
			event.returned = this.xhr.send("");
		}
		this.dispatchEvent("send", event);
		return event.returned;
	}
	
	/**
	 * Non-recursive serialization from object to
	 * url-encoded values
	 */
	Frozen.Ajax.Request.prototype.urlEncodeObject = function(obj) {
		var first = true;
		var string = "";
		var temp_key;
		var temp_obj;
		for (i in obj) {
			temp_key = encodeURIComponent(i);
			switch (typeof obj[i]) {
				case "number":
					temp_obj = obj[i];
					break;
				case "boolean":
					temp_obj = (obj[i]) ? 1 : 0;
					break;
				case "undefined":
					temp_obj = "";
					break;
				default:
					temp_obj = encodeURIComponent(obj[i]);
					break;
			}
			if (first) {
				first = false;
				string += temp_key + "=" + temp_obj;
			} else {
				string += "&" + temp_key + "=" + temp_obj;
			}
		}
		return string;
	}
	
	/**
	 * Manage pool of Request instances
	 * @construct
	 */
	Frozen.Ajax.RequestManager = function() { }
	Frozen.Ajax.RequestManager.prototype = {
		/**
		 * Array of Request instances
		 */
		requests : [],
		
		/**
		 * Factory-type function to instanciate Requests
		 */
		createRequest : function() {
			var new_id = ++this.requests.length;
			try {
				this.requests[new_id] = new Frozen.Ajax.Request(new_id);
				return this.requests[new_id];
			} catch (e) {
				alert(e);
				// Clean up junk reference if necessary
				if (this.requests[new_id]) {
					this.requests.pop();
				}
				return false;
			}
		},
	
		/**
		 * Garbage collection
		 */
		eliminateRequest : function(req) {
			if (!req || !req.id || !this.requests[req.id]) {
				return false;
			}
			var id = req.id;
			// Call abort in case of current activity
			this.requests[id].abort();
			// First, delete the reference
			this.requests.splice(id, 1);
			// Then, adjust the references of the remaining
			// objects to match their new indices
			while (id < this.requests.length) {
				this.requests[id++].id--;
			}
			return true;
		},
	
		/**
		 * Provide a method to cancel all active and pending requests
		 */
		abortAll : function() {
			for (i = 0; i < this.requests.length; i++) {
				if (this.requests[i]) {
					this.requests[i].abort();
				}
			}
		},
		
		/**
		 * Auto-add listeners to Request events
		 */
		addEventListener : function(type, listener, capture) {
			var dis = this;
			Frozen.Event.Dispatcher.prototype.addEventListener.call(
				dis,
				type,
				listener
			);
		},
		
		/**
		 * If it supports the type, remove the listener (capture ignored)
		 */
		removeEventListener : function(type, listener, capture) {
			var dis = this;
			Frozen.Event.Dispatcher.prototype.removeEventListener.call(
				dis,
				type,
				listener
			);
		}
	}
	/**
	 * An instance of the RequestManager for immediate usage
	 * as a singleton, making life a lot easier.
	 */
	Frozen.Ajax.Manager = new Frozen.Ajax.RequestManager();
}
