/*______________
|       ______  |   U I Z E     J A V A S C R I P T     A P I
|     /      /  |   -----------------------------------------
|    /    O /   |    MODULE : Uize.Comm Class (version 1.0.0)
|   /    / /    |    AUTHOR : Chris van Rensburg, Jan Borgersen (original code donated by Zazzle, Inc.)
|  /    / /  /| |    ONLINE : http://www.tomkidding.com/uize/uize-js-api
| /____/ /__/_| | COPYRIGHT : (c)2004-2006 UIZE
|          /___ |   LICENSE : Distributed under the terms of the GNU General Public License
|_______________|             http://www.gnu.org/licenses/gpl.txt
*/

/*
	DESCRIPTION
		Implements a base class with a skeleton framework for managing communication to a server. Provides support for queuing subsequent calls to allow serialized server requests.

	REQUIRES
		- Uize.js (base class)

	TO DO
		- caching
			- support for flushing the cache for specific requests
			- edge case optimization: consolidate consecutive requests when identical and memory caching is in use

		- timeout failure mechanism
		- cancelability for requests
		- configurable comm_mode & output format query params (ie. not always assumed)
*/

/*ScruncherSettings Mappings="=b" LineCompacting="TRUE"*/

(function () {

	/*** Variables for Scruncher Optimization ***/
		var
			_true = true,
			_false = false,
			_undefined
		;

	/*** Global Variables ***/
		var _cachedResponses = {};

	/*** Object Constructor ***/
		var
			_superclass = Uize,
			_class = _superclass.Comm = _superclass.subclass (
				function () {
					var _this = this;

					/*** Private Instance Properties ***/
						_this._requestQueue = [];
						_this._inProgress = false;

					/*** Public Instance Properties ***/
						_this.requestQueue = _this._requestQueue;
				}
			),
			_classPrototype = _class.prototype
		;

	/*** Utility Functions ***/
		function _getCachedResponse (_request) {
			return _request.cache == 'memory' ? _cachedResponses [_request.url] : null;
		}

		function _getResponsePropertyNameForRequest (_request) {
			return 'response' + _class.capFirstChar (_request.returnType);
		}

		function _callResponseCallback (_request) {
			var
				_returnType = _request.returnType,
				_requestCallback = _request.callback,
				_cachedResponse = _getCachedResponse (_request)
			;
			if (_cachedResponse) {
				var _returnTypeIsObject = _returnType == 'object';
				if (_requestCallback) {
					_request.responseText = '';
					_request.responseJson = _request.responseXml = null;
					if (_returnTypeIsObject || _returnType == 'xml')
						_request.responseXml = _class.clone (_cachedResponse.responseXml)
					;
					if (_returnTypeIsObject || _returnType == 'text')
						_request.responseText = _cachedResponse.responseText
					;
					if (_returnTypeIsObject || _returnType == 'json')
						_request.responseJson = _class.clone (_cachedResponse.responseJson)
					;
				}
			} else {
				if (_request.cache == 'memory')
					_cachedResponses [_request.url] = {
						responseXml:_class.clone (_request.responseXml),
						responseJson:_class.clone (_request.responseJson),
						responseText:_request.responseText
					}
				;
			}
			if (_requestCallback)
				_requestCallback (
					_returnType == 'object'
						? _request
						: _request [_getResponsePropertyNameForRequest (_request)]
				)
			;
		}

	/*** Private Instance Methods ***/
		_classPrototype._fireRequestQueueUpdatedEvent = function () {
			this.fireEvent ('Request Queue Updated');
		};

	/*** Public Instance Methods ***/
		_classPrototype.performRequest = function (_request,_callback) {callback ()};

		_classPrototype.flush = function () {
			this._requestQueue.length = 0;
			this._fireRequestQueueUpdatedEvent ();
		};

		_classPrototype.request = function (_request) {
			var _this = this;
			if (_getCachedResponse (_request)) {
				setTimeout (function () {_callResponseCallback (_request)},0);
			} else {
				_this.queueRequest (_request);
				_this.useQueue ();
			}
		};

		_classPrototype.queueRequest = function (_request) {
			var _this = this;
			if (!_request.requestMethod) _request.requestMethod = 'GET';
			if (!_request.returnType) _request.returnType = 'object';
			if (typeof _request.cache != 'string')
				_request.cache = _request.cache ? 'memory' : 'never'
			;
			_this._requestQueue.push (_request);
			_this._fireRequestQueueUpdatedEvent ();
		};

		_classPrototype.useQueue = function () {
			var
				_this = this,
				_requestQueue = _this._requestQueue,
				_requestQueueLength = _requestQueue.length
			;
			if (!_this._inProgress && _requestQueueLength) {
				_this._inProgress = _true;

				function _cleanFromQueue () {
					/* TO DO: for Safari issue, must always do a setTimeout if all the requests in the queue were cached */
					while (_requestQueue.length) {
						var _request = _requestQueue [0];
						if (_getCachedResponse (_request) || _request.completed) {
							_requestQueue.shift ();
							_callResponseCallback (_request);
						} else {
							break;
						}
					}
					_this._fireRequestQueueUpdatedEvent ();
					_this._inProgress = false;
					setTimeout (function () {_this.useQueue ()},1);
						/* TO DO:
							- determine whether or not it's necessary and/or OK to set a timeout in the AJAX case
							- determine if it's OK in the IFRAME case to do the location back before this timeout is set
						*/
				}

				function _handleSingleRequest (_request) {
					if (_getCachedResponse (_request)) {
						_cleanFromQueue ();
					} else {
						_this.fireEvent ({name:'Perform Request',requestInfo:_request});
						_this.performRequest (
							_request,
							function () {
								_request.completed = _true;
								_cleanFromQueue ();
							}
						);
					}
				}

				if (_requestQueueLength == 1) {
					_handleSingleRequest (_requestQueue [0]);
				} else {
					var _requestsToBatch = [];

					/*** determine list of requests to batch ***/
						var _batchingAgent;
						for (var _requestNo = 0; _requestNo < _requestQueueLength; _requestNo++) {
							var
								_request = _requestQueue [_requestNo],
								_requestBatchingAgent = _request.batchingAgent
							;
							if (
								!_requestBatchingAgent ||
								(_batchingAgent && _requestBatchingAgent != _batchingAgent) ||
								_request.cache == 'browser'
							) {
								break;
							} else {
								if (!_batchingAgent)
									_batchingAgent = _requestBatchingAgent
								;
								_requestsToBatch.push (_request);
							}
						}

					var _requestsToBatchLength = _requestsToBatch.length;
					if (_requestsToBatchLength > 1) {
						/*** handle batch request ***/
							/*** find all the requests in the batch that aren't already memory cached ***/
								var _requestsInBatchRequest = [];
								for (var _requestNo = 0; _requestNo < _requestsToBatchLength; _requestNo++) {
									var _request = _requestsToBatch [_requestNo];
									if (!_getCachedResponse (_request)) {
										_request.completed = _false;
										_requestsInBatchRequest.push (_request);
									}
								}

							/*** perform batch request and handle responses ***/
								var _requestsInBatchRequestLength = _requestsInBatchRequest.length;
								if (_requestsInBatchRequestLength) {
									if (_requestsInBatchRequestLength == 1) {
										_handleSingleRequest (_requestsInBatchRequest [0]);
									} else {
										/* NOTE:
											only package up as a batch request if there turn out to be more than one non-cached requests
										*/
										var _batchRequest = _batchingAgent.buildRequest (_requestsInBatchRequest);
										_this.fireEvent ({name:'Perform Request',requestInfo:_batchRequest});
										_this.performRequest (
											_batchRequest,
											function () {
												var
													_batchResponses = _batchingAgent.responseParser (_batchRequest),
													_batchResponseNo = 0
												;
												for (var _requestNo = 0; _requestNo < _requestsToBatchLength; _requestNo++) {
													var _request = _requestsToBatch [_requestNo];
													if (_request.completed !== _undefined) {
														_request [_getResponsePropertyNameForRequest (_request)] =
															_batchResponses [_batchResponseNo++]
														;
														_request.completed = _true;
													}
												}
												_cleanFromQueue ();
											}
										);
									}
								} else {
									_cleanFromQueue ();
								}
					} else {
						/*** handle single request ***/
							_handleSingleRequest (_requestQueue [0]);
					}
				}
			}
		};

	/*** Public Instance-static Methods ***/
		_classPrototype.serializeParams = _class.serializeParams = function (_params) {
			var _paramPairs = [];
			if (_class.isArray (_params)) {
				var _subParamSets = _params;
				_params = {};
				for (var _subParamSetNo = 0; _subParamSetNo < _subParamSets.length; _subParamSetNo++)
					_class.copyInto (_params,_subParamSets [_subParamSetNo])
				;
			}
			for (var _paramName in _params) {
				var
					_value = _params [_paramName],
					_valueType = typeof _value
				;
				if (_valueType == 'string' || _valueType == 'number' || _valueType == 'boolean' || _class.isArray (_value))
					_paramPairs.push (_paramName + '=' + escape (_value + '').replace (/\+/g,'%2B'))
				;
			}
			return _paramPairs.join ('&');
		};

		_classPrototype.parseQuery = _class.parseQuery = function (_queryString) {
			var
				_paramPairs = _queryString.split ('&'),
				_params = {}
			;
			for (_paramPairNo = 0; _paramPairNo < _paramPairs.length; _paramPairNo++) {
				var _paramPair = _paramPairs [_paramPairNo].split ('=');
				if (_paramPair [0])
					_params [unescape (_paramPair [0])] = unescape (_paramPair [1])
				;
			}
			return _params;
		};

		_classPrototype.processArrayAsync = _class.processArrayAsync = function (_elements,_asyncFunction,_completion) {
			var
				_elementsLength = _elements.length,
				_elementNo = -1
			;
			function _processNextElement (_mustContinue) {
				_elementNo++;
				if (_elementNo < _elementsLength && _mustContinue !== _false) {
					_asyncFunction (_elements [_elementNo],_processNextElement,_elementNo);
				} else {
					_completion ();
				}
			}
			_processNextElement ();
		};

	/*** Public Static Methods ***/
		_class.getCacheDefeatStr = function () {
			return (new Date ()).getTime () + '' + Math.round (Math.random () * 1000);
		};
}) ();


