var Cathmhaol = window.Cathmhaol || {};

/**
* @library      Cathmhaol.AjaxPool
* @description  Creates a pool of XmlHttpRequest objects that can be used to process requests. Limited to two simultaneous connections.
* @requires     Cathmhaol.AjaxConnection
* @requires     Cathmhaol.AjaxRequest
* @author       Robert King
*
* @example     var oConPool = new Cathmhaol.AjaxPool(); oConPool.send("http://www.cathmhaol.com/license/index.asp", true, function(xhr) { alert(xhr.responseText); }, "", document.forms[0]);
*/
Cathmhaol.AjaxPool = function(numberOfConnections) {
	this._initialize(numberOfConnections);
};
Cathmhaol.AjaxPool.prototype = {
	/**
	* @method       send
	* @description  Opens an XmlHttpRequest session and sends the request
	* @returns {void}
	* @param {string} uri         URL to use
	* @param {boolean} async      Request should be asynchronous
	* @param {function} callback  JavaScript function to handle response
	* @param {string|node} data   The HTTP verb (valid values are "get" and "post") or the form object to use.
	* @param {integer} wait       The number of milliseconds to wait for a response.
	*/
	send: function(uri, async, callback, data, wait) {
		this._queueRequest(new Cathmhaol.AjaxRequest(uri, async, callback, data, wait));
	},

	/**
	* @private
	* @method       _initialize
	* @description  Creates two connections and adds them to the pool.
	* @returns {void}
	* @param {integer} numberOfConnections  The number of active connections to maintain at one time.
	*/
	_initialize: function(numberOfConnections) {
		numberOfConnections = numberOfConnections || 2;
		for (var c = 0; c < numberOfConnections; c++) {
			var conn = new Cathmhaol.AjaxConnection();
			conn.setId(conn.getId() + c);
			this._pool.push(conn);
		}
	},

	/**
	* @private
	* @method       _nextRequest
	* @description  Processes the next request using the available connection.
	* @returns {void}
	*/
	_nextRequest: function() {
		for (var c = 0; c < this._pool.length; c++) {
			if (this._pool[c].isAvailable()) {
				var next = this._unqueueRequest();
				if (next) { this._pool[c].send(next); }
			}
		}
	},

	/**
	* @private
	* @method       _queueRequest
	* @description  Adds the next request to the stack
	* @returns {void}
	* @param {Cathmhaol.AjaxRequest} oRequest  The AjaxRequest object to queue.
	*/
	_queueRequest: function(oRequest) {
		this._queue.push(oRequest);
		if (!this._timer) { this._startTimer(); }
	},

	/**
	* @private
	* @method       _unqueueRequest
	* @description  Returns the next request
	* @returns {Cathmhaol.AjaxRequest}
	*/
	_unqueueRequest: function() {
		var nextRequest = this._queue.shift();
		if (this._queue.length == 0) { this._stopTimer(); }
		return nextRequest;
	},
	

	/**
	* @private
	* @property     _pool
	* @description  Array of Cathmhaol.AjaxConnection objects
	* @type {object[]}
	*/
	_pool: new Array(),

	/**
	* @private
	* @property     _queue
	* @description  Array of Cathmhaol.AjaxRequest objects
	* @type {object[]}
	*/
	_queue: new Array(),

	/**
	* @private
	* @method       _startTimer
	* @description  Starts a 100 millisecond timer that checks for requests.
	* @returns {void}
	*/
	_startTimer: function() {
		var scope = this;
		this._timer = window.setInterval(function() { scope._nextRequest(); }, 100);
	},

	/**
	* @private
	* @method       _stopTimer
	* @description  Stops the timer that checks for requests.
	* @returns {void}
	*/
	_stopTimer: function() {
		window.clearInterval(this._timer);
		this._timer = null;
	},

	/**
	* @private
	* @property
	* @description  The timer id for the interval timer.
	* @type {integer}
	*/
	_timer: null
};

/**
* @library      Cathmhaol.AjaxRequest
* @description  Creates a basic request object.
* @author       Robert King
*/
Cathmhaol.AjaxRequest = function(sURI, bAsync, fCallback, oData, iWait) {
	this._initialize(sURI, bAsync, fCallback, oData, iWait);
};
Cathmhaol.AjaxRequest.prototype = {
	/**
	* @method       getAsync
	* @description  Returns the async property
	* @returns {boolean}
	*/
	getAsync: function() {
		return this._async;
	},

	/**
	* @method       getContentType
	* @description  Returns the contentType property
	* @returns {string}
	*/
	getContentType: function() {
		return this._contentType;
	},

	/**
	* @method       getHandler
	* @description  Returns the handler property
	* @returns {function}
	*/
	getHandler: function() {
		return this._handler;
	},

	/**
	* @method       getMethod
	* @description  Returns the HTTP verb to use
	* @returns {string}
	*/
	getMethod: function() {
		return this._method;
	},

	/**
	* @method       getQueryString
	* @description  Returns the data to send in the request
	* @returns {string}
	*/
	getQueryString: function() {
		return (this.getMethod() == "GET" ? null : this._query.join("&"));
	},

	/**
	* @method       getURI
	* @description  Returns the URI to open
	* @returns {string}
	*/
	getURI: function() {
		return (this.getMethod() == "GET" ? this._uri + this._query.join("&") : this._uri);
	},

	/**
	* @method       getWait
	* @description  Returns the number of milliseconds to wait before timeout
	* @returns {integer}
	*/
	getWait: function() {
		return this._wait;
	},

	/**
	* @method       setAsync
	* @description  Sets the async property
	* @returns {void}
	* @param {boolean} value  Whether or not the request is to be asynchronous.
	*/
	setAsync: function(value) {
		this._async = (value ? value : true);
	},

	/**
	* @method       setContentType
	* @description  Sets the content type
	* @returns {void}
	* @param {string} value  The content type header of the request.
	*/
	setContentType: function(value) {
		switch (value) {
			case "application/x-www-form-urlencoded":
			case "text/xml":
				this._contentType = value;
		}
	},

	/**
	* @method       setForm
	* @description  Sets the form to post in the request.
	* @returns {void}
	* @param {function} value  The HTML form.
	*/
	setForm: function(value) {
		this.setMethod(value.method);
		this.setURI(value.action);
		for (var c = 0; c < value.elements.length; c++) {
			this._query.push(value.elements[c].name+"="+value.elements[c].value);
		}
	},

	/**
	* @method       setHandler
	* @description  Sets the callback handler
	* @returns {void}
	* @param {function} value  The JavaScript function to use as the response handler.
	*/
	setHandler: function(value) {
		if (typeof(value) == "function") {
			this._handler = value;
		} else if (typeof(value) == "string") {
			this._handler = new Function(value);
		} else {
			this._handler = function(xhr) { return; }
		}
	},

	/**
	* @method       setMethod
	* @description  Sets the HTTP verb to either POST or GET.
	* @returns {void}
	* @param {string} value  The HTTP verb to use. May be POST or GET.
	*/
	setMethod: function(value) {
		this._method = (value.toLowerCase() == "post" ? "post" : "get").toUpperCase();
		this.setContentType(value.toLowerCase() == "post" ? "application/x-www-form-urlencoded" : "text/xml");
	},

	/**
	* @method       setQueryString
	* @description  Included for consistency. The query string is not set by the method, it's set using other actions.
	* @returns {void}
	* @param {} value  Ignored.
	*/
	setQueryString: function(value) {
		//Set by another method		
	},

	/**
	* @method       setURI
	* @description  Sets the URI to be opened.
	* @returns {void}
	* @param {string}  The URI to open.
	*/
	setURI: function(value) {
		if (typeof(value) == "string") {
			this._uri = value.substr(0, (value.indexOf("?") > -1 ? value.indexOf("?") : value.length));
			var params = value.indexOf("?") > -1 ? value.substr(value.indexOf("?")+1, value.length).split("&") : new Array();
			for (var c = 0; c < params.length; c++) {
				this._query.push(params[c]);
			}
		}
	},

	/**
	* @method       setWait
	* @description  Sets the number of milliseconds to wait before timeout
	* @returns {void}
	* @param {integer} value  The number of milliseconds to wait
	*/
	setWait: function(value) {
		var i;
		if (value) {
			i = Math.round(Math.parseFloat(value));
			i = (!isNaN(i) && i > 0) ? i : null;
		}
		this._wait = i;
	},

	/**
	* @private
	* @property     _async
	* @description  Whether or not the request should be asynchronous.
	* @type {integer}
	*/
	_async: true,

	/**
	* @private
	* @property     _contentType
	* @description  The content type of the request.
	* @type {string}
	*/
	_contentType: "text/xml",

	/**
	* @private
	* @property     _handler
	* @description  The JavaScript function to use as the callback.
	* @type {function}
	*/
	_handler: function(xhr) { return; },

	/**
	* @private
	* @property     _method
	* @description  The HTTP verb to use. Value values are POST and GET.
	* @type {string}
	*/
	_method: "GET",

	/**
	* @private
	* @property     _query
	* @description  An array of name/value pairs to send.
	* @type {string[]}
	*/
	_query: new Array(),

	/**
	* @private
	* @property     _uri
	* @description  The URI to open.
	* @type {string}
	*/
	_uri: "",

	/**
	* @private
	* @property     _wait
	* @description  Number of milliseconds to wait before timeout.
	* @type {integer}
	*/
	_wait: null,

	/**
	* @private
	* @method       _initialize
	* @description  Creates the object using the provided properties.
	* @returns {void}
	* @param {string} sURI         The URI to get or post.
	* @param {boolean} bAsync      The request should be asynchronous.
	* @param {function} fCallback  The JavaScript function used to handle the response.
	* @param {string|node} oData   The HTTP verb (valid values are "get" and "post") or the form object to use.
	* @param {integer} iWait       The number of milliseconds to wait for a response.
	*/
	_initialize: function(sURI, bAsync, fCallback, oData, iWait) {
		this.setAsync(bAsync);
		this.setHandler(fCallback);
		this.setWait(iWait);
		if (typeof(oData) == "string") {
			this.setURI(sURI);
		} else {
			this.setForm(oData);
		}
	}
};

/**
* @library      Cathmhaol.AjaxConnection
* @description  Creates an XmlHttpRequest object.
* @author       Robert King
*/
Cathmhaol.AjaxConnection = function(oRequest) {
	this._initialize(oRequest);
};
Cathmhaol.AjaxConnection.prototype = {
	/**
	* @method       getId
	* @description  Returns the ID of the connection object.
	* @returns {integer}
	*/
	getId: function() {
		return this._id;
	},

	/**
	* @method       getStatus
	* @description  Returns the status of the connection. Valid values are one of _statusCodes.
	* @returns {integer}
	*/
	getStatus: function() {
		return this._status;
	},

	/**
	* @method       isAvailable
	* @description  Returns false if the request object is working, true otherwise.
	* @returns {boolean}
	*/
	isAvailable: function() {
		return (this.getStatus() != this._statusCodes._WORKING);
	},

	/**
	* @method       send
	* @description  Opens an XmlHttpRequest session and sends the request
	* @returns {void}
	* @param {Cathmhaol.AjaxRequest} oRequest  The Cathmhaol.AjaxRequest object
	*/
	send: function(oRequest) {
		try {
			if (oRequest) { this._request = oRequest; }

			if (!this._xhr || !this._request) { return; }

			this.setStatus(this._statusCodes._WORKING);

			this._xhr.open(this._request.getMethod(), this._request.getURI(), this._request.getAsync());
			this._xhr.setRequestHeader("Content-Type", this._request.getContentType());
			this._xhr.setRequestHeader("Cache-Control", "no-store, no-cache, must-revalidate");
			if (this._xhr.overrideMimeType) { this._xhr.overrideMimeType("text/xml"); }
			this._xhrMonitor(this._xhr, this);
			this._xhr.send(this._request.getQueryString());
			try { console.log("Request for "+this._request.getURI()+" sent using "+this.getId()+ "."); } catch (e) { }
		} catch (err) {
			console.log("Unable to open XMLHttpRequest object");
			this._request = null;
			this.setStatus(this._statusCodes._FAILURE);
		}
		return;
	},

	/**
	* @method       setId
	* @description  Sets the ID of the connection object.
	* @returns {void}
	* @param {string|integer} value  The value of the ID
	*/
	setId: function(value) {
		this._id = value;
		return;
	},

	/**
	* @method       setStatus
	* @description  Set the status of the request. Valid values are one of _statusCodes: _FAILURE; _SUCCESS; _WAITING; or _WORKING
	* @returns {void}
	* @param {integer} value  The status of the connection object
	*/
	setStatus: function(value) {
		this._status = value;
		return;
	},

	/**
	* @private
	* @property     _id
	* @description  The ID of the connection object.
	* @type {integer}
	*/
	_id: null,

	/**
	* @private
	* @method       _initialize
	* @description  Initializes the HTTP status codes and creates an XmlHttpRequest object.
	* @param {Cathmhaol.AjaxRequest} oRequest  The Cathmhaol.AjaxRequest object
	* @returns {void}
	*/
	_initialize: function(oRequest) {
		try {
			if (window.XMLHttpRequest) {
				this._xhr = new XMLHttpRequest();
				this._request = oRequest;
				this.setStatus(this._statusCodes._WAITING);
				this.setId(new Date().getTime());
			} else if (typeof(ActiveXObject) != "undefined") {
				var o = new Array("MSXML2.XMLHTTP.3.0","MSXML2.XMLHTTP","Microsoft.XMLHTTP");
				for (var c = 0; c < o.length; c++) {
					try {
						this._xhr = new ActiveXObject(o[c]);
						this._request = oRequest;
						this.setStatus(this._statusCodes._WAITING);
						this.setId(new Date().getTime());
						break;
					}
					catch(e){
						//Unable to create XMLHttpRequest object
					}
				}
			}
		} catch (e) {
			//Unable to create XMLHttpRequest object
		}
		if (this._request) { this.sendRequest(); }
		return;
	},

	/**
	* @private
	* @property     _request
	* @description  The Cathmhaol.AjaxRequest object used for the session.
	* @type {Cathmhaol.AjaxRequest}
	*/
	_request: null,

	/**
	* @private
	* @method       _startTimer
	* @description  Creates a timer that will handle a timeout of the request.
	* @returns {void}
	* @param {XmlHttpRequest} xhr  The XmlHttpRequest object
	* @param {Object} obj          This object. Used to maintain scope.
	*/
	_startTimer: function(xhr, obj) {
		if (this.request.getWait()) {
			var scope = this;
			this._xhrTimeoutId = window.setTimeout(scope._xhrTimeout(xhr, obj), this._request.getWait());
		}
		return;
	},

	/**
	* @private
	* @property     _status
	* @description  The status of the request. Valid values are: _FAILURE; _SUCCESS; _WAITING; or _WORKING
	* @type {integer}
	*/
	_status: null,

	/**
	* @property     _statusCodes
	* @description  The constant representing the valid states of a request.
	* @type {integer}
	*/
	_statusCodes: {
		/**
		* @property     _FAILURE
		* @description  The constant representing failure.
		* @type {integer}
		*/
		_FAILURE: -1,

		/**
		* @property     _SUCCESS
		* @description  The constant representing success.
		* @type {integer}
		*/
		_SUCCESS: 1,

		/**
		* @property     _WAITING
		* @description  The constant representing wait state.
		* @type {integer}
		*/
		_WAITING: 0,

		/**
		* @property     _WORKING
		* @description  The constant representing a working state.
		* @type {integer}
		*/
		_WORKING: null
	},

	/**
	* @property     _xhr
	* @description  The XMLHttpRequest object
	* @type {XmlHttpRequest}
	*/
	_xhr: null,

	/**
	* @private
	* @method       _xhrMonitor
	* @description  Creates a timer that polls the XmlHttpRequest object every 50 milliseconds
	* @returns {void}
	* @param {XmlHttpRequest} xhr	The XmlHttpRequest object
	* @param {Object} obj		This object. Used to maintain scope.
	*/
	_xhrMonitor: function(xhr, obj) {
		var scope = this;
		this._xhrMonitorId = window.setInterval(function() { if (xhr && xhr.readyState == 4) { window.clearInterval(scope._xhrMonitorId); scope._xhrOnReady(xhr, obj); } }, 50);
		return;
	},

	/**
	* @private
	* @property     _xhrMonitorId
	* @description  The interval id of the XHR polling interval.
	* @type {integer}
	*/
	_xhrMonitorId: null,

	/**
	* @private
	* @method       _xhrOnReady
	* @description  Called when the readyState of the XmlHttpRequest object changes to 4 (loaded).
	* @returns {void}
	* @param {XmlHttpRequest} xhr	The XmlHttpRequest object
	* @param {Object} scope
	*/
	_xhrOnReady: function(xhr, scope) {
		if (this._xhr.status == 200) {
			scope._request.getHandler().apply(this, [this._xhr]);
			scope.setStatus(scope._statusCodes._SUCCESS);
			try { console.log("Handler executed for "+scope._request.getURI()+" using "+scope.getId()+"."); } catch (e) { }
		} else {
			scope.setStatus(scope._statusCodes._FAILURE);
			try { console.log("Handler executed for "+scope._request.getURI()+" using "+scope.getId()+"."); } catch (e) { }
		}
		return;
	},

	/**
	* @property     _xhrTimeoutId
	* @description  The window timeout timer used to monitor the XMLHttpRequest object
	* @type {integer}
	*/
	_xhrTimeoutId: null,

	/**
	* @private
	* @method       _xhrTimeout
	* @description  Method to terminate the XmlHttpRequest call.
	* @returns {void}
	* @param {XmlHttpRequest} xhr	The XmlHttpRequest object
	* @param {Object} scope
	*/
	_xhrTimeout: function(xhr, scope) {
		window.clearTimeout(scope._xhrTimeoutId);
		scope._xhr.abort();
		scope.setStatus(scope.getStatus() == scope._statusCodes._WORKING ? scope._statusCodes._FAILURE : scope.getStatus());
		return;
	}
};
