/*
 * jQuery iQ plugin
 * Developed by Pete DeLaurentis
 *
 * Copyright (C) ShapeTools 2010
 * 
 */

// Put ourselves into a jQuery function
(function($) {

	// Define our object that we'll use
	function RequestQueue()
	{
		// We keep an array of requests
		this._requests = [];

		// This is our current request
		this._currentRequest = null;
		
		// This is our next request ID
		this._nextRequestId = 1;
	};

	// Setup our functions now
	jQuery.extend(RequestQueue.prototype, {
		
		// This adds a new request onto the queue
		pushRequest: function(request) {

			// See if it's an idle only request
			// if it is, and we're not idle, bail
			if ( request.idle ) {
				if ( this._currentRequest != null || this._requests.length > 0 ) {
					return;
				}
			}
			
			// Assign a unique ID to the request
			request.id = this._nextRequestId;
			this._nextRequestId += 1;

			// Push the request onto the queue
			this._requests.push(request);

			// This is called after we add it to the queue
			// but before we do anything with it
			if ( request.queued ) {
				request.queued.apply(null, [request]);
			}
		},
	
		// If we're idle, this will process the next request
		processNextRequestIfIdle: function() {
		
			// Make sure there isn't already a request in progress
			if ( this._currentRequest == null ) {
				this.processNextRequest();
			}
		},
	
		// If we're idle, this will process the next request
		processNextRequest: function() {
		
			// This will process the next request in the queue
			// See if the array has any length
			if ( this._requests.length > 0 ) {
	
				// Shift off the next element and read it
				this._currentRequest = this._requests.shift();
				if ( this._currentRequest != null ) {

					// If we do have a next request, we'll process it
					this.networkActivity(true);

					// Filter our any requests that are identical.  This is used 
					// when the user types quickly to filter out extra requests.
					this.filterDuplicateRequests(this._currentRequest);

					// Go ahead and handle this request now
					this.processRequest(this._currentRequest);
				}
			}
			else {
		
				// There's no next request, so set that there is no network activity
				this.networkActivity(false);
			}
		},
	
		// Take any identical requests in the queue and filter them out
		filterDuplicateRequests: function(request) {

			// Loop through the list of requeusts
			while ( this._requests.length > 0 ) {
			
				// Peek at the first request on the queue
				var comp = this._requests[0];
			
				// See if all the values are the same (except for the callbacks)
				for (var key in request) {
					if ( key != "success" && 
						 key != "error" && 
						 key != "data" && 
						 key != "provideData" && 
						 key != "queued" && 
						 key != "id" && 
						 request[key] != comp[key] ) {
						
						// Bail
						return;
					}
				}
			
				// Flip point of view and see if there are failures to match the other way
				for (var key in comp) {
					if ( key != "success" && 
						 key != "error" && 
						 key != "data" && 
						 key != "provideData" && 
						 key != "queued" && 
						 key != "id" && 
						 request[key] != comp[key] ) {

						// Bail
						return;
					}
				}
			
				// Inherit the ID of the discarded request
				// This is important, b/c it will always be a higher version #
				// If we don't do, this we'll have dangling outbound field submissions
				// which will prevent future updates from a remote server
				request.id = comp.id;

				// Pop the entry now.  If it matched, we don't want it anymore.
				this._requests.shift();
			}
		},
	
		// Process the specified request
		processRequest: function(request) {
		
			// Store the this pointer on the stack for use in our callbacks
			var me = this;
		
			// See if there is a provide data method and if so, 
			// call it now to generate our data
			if ( request.provideData ) {
				request.data = request.provideData.apply(null, [request]);
			}
		
			// Figure out our data type
			var dataType = "jsonp";
			if ( request.dataType ) {
				dataType = request.dataType;
			}
		
			// See what our request URL is
			var requestUrl = request.url;

			// Figure out our content type
			var contentType = "application/x-www-form-urlencoded; charset=UTF-8";
			if ( request.contentType ) {
				contentType = request.contentType;
			}
						
			// Add a request number onto the data (so we can see it)
			var dataToSend = request.data;
			if ( request.type != "GET" ) {
				if ( dataToSend && contentType == null ) {
					dataToSend += "&request_id=" + request.id;
				}
				else {
					
					// Add the request ID to the query string then
					if ( requestUrl.indexOf("?") >= 0 ) {
						requestUrl += "&request_id=" + request.id;
					}
					else {
						requestUrl += "?request_id=" + request.id;
					}
				}
			}
			
			// See if there is a timeout specified
			if ( request.timeout == null || request.timeout < 1 ) {
				request.timeout = 5000;
			}
		
			// This gets called whenever a request completes
			var completeMethod = function (xhr, textStatus) {

				// If it times out, we want to retry up to a few times
				if ( textStatus == "success") {
				}
				else if ( textStatus == "timeout" ) {
					if ( request.retries && request.retries > 1 ) {

						// Reduce the retry count
						request.retries -= 1;
					
						// Push this request back onto the end of the queue
						me._requests.unshift(request);
					}
					else {
					}
				}
				else {

					// See if it's a 500 error which may mean the server can't keep up
					if ( xhr.status == "500" || 	// Internal Server Error
						 xhr.status == "502" || 	// Bad Gateway
						 xhr.status == "503" || 	// Service Unavailable
						 xhr.status == "504" || 	// Gateway Timeout
						 xhr.status == "507" || 	// Insufficient Storage
						 xhr.status == "509" ) {	// Bandwidth Limit Exceeded
							
						// Retry now, sending error only if we run out of retries
						if ( request.retries && request.retries > 1 ) {

							// Reduce the retry count
							request.retries -= 1;

							// Push this request back onto the end of the queue
							me._requests.unshift(request);
						}
						else {
							if ( request.error ) {
								request.error.apply(null, [request, xhr, textStatus]);
							}
						}
					}
					else if ( request.error ) {
						request.error.apply(null, [request, xhr, textStatus]);
					}
				}

				// Process the net request in the queue, if there is one
				me._currentRequest = null;
				me.processNextRequestIfIdle();
			 };
			
			// Setup our method for success
			var successMethod =  function(data, textStatus) { 
				if ( request.success ) {
					request.success.apply(null, [request, data, textStatus]); 
				}
			};
		
			// Setup for the put method
			var setupRequestHeaders = function(xhr) {
				xhr.setRequestHeader("Accept", "text/javascript, application/javascript, */*, text/javascript");
			    if ( $.cookie("vs") ) { xhr.setRequestHeader('X-VS', $.cookie("vs")); }
			    if ( $.cookie("vss") ) { xhr.setRequestHeader('X-VSS', $.cookie("vss")); }
			};
		
			// Go ahead and generate our data
			if ( request.type == "GET") {

				// Send the request now
				$.ajax({ beforeSend: setupRequestHeaders,
						 type: "GET",
				         url: requestUrl,
						 contentType: contentType,
		       			 /*data: dataToSend,*/
		       			 dataType: dataType,  // script is the only way to pick up OK head responses
						 jsonp: 'callback',
						 complete: completeMethod,
		       			 success: successMethod,
						 timeout: request.timeout
				});
			
			}
			else if ( request.type == "POST") {

				// Send the request now
				$.ajax({ beforeSend: setupRequestHeaders,
						 type: "POST",
				         url: requestUrl,
						 contentType: contentType,
		       			 data: dataToSend,
		       			 dataType: dataType,  // script is the only way to pick up OK head responses
						 jsonp: 'callback',
						 complete: completeMethod,
		       			 success: successMethod,
						 timeout: request.timeout
				});
			}
			else if ( request.type == "PUT" ) {
			
				// Setup for the put method
				var setupMethodForPut = function(xhr) {
					xhr.setRequestHeader("Accept", "text/javascript, application/javascript, */*, text/javascript");
				    xhr.setRequestHeader('X-Http-Method-Override', 'PUT');
				    if ( $.cookie("vs") ) { xhr.setRequestHeader('X-VS', $.cookie("vs")); }
				    if ( $.cookie("vss") ) { xhr.setRequestHeader('X-VSS', $.cookie("vss")); }
				};

				// jQuery
				$.ajax({ beforeSend: setupMethodForPut,
						 type: "POST",
				         url: requestUrl,
						 contentType: contentType,
		       			 data: dataToSend,
		       			 dataType: dataType,
						 jsonp: 'callback',
						 complete: completeMethod,
		       			 success: successMethod,
						 timeout: request.timeout
				});
			}
			else if ( request.type == "DELETE" ) {
			
				// Setup for the delete method
				var setupMethodForDelete = function(xhr) {
					xhr.setRequestHeader("Accept", "text/javascript, application/javascript, */*, text/javascript");
				    xhr.setRequestHeader('X-Http-Method-Override', 'DELETE');
				    if ( $.cookie("vs") ) { xhr.setRequestHeader('X-VS', $.cookie("vs")); }
				    if ( $.cookie("vss") ) { xhr.setRequestHeader('X-VSS', $.cookie("vss")); }
				};

				// jQuery
				$.ajax({ beforeSend: setupMethodForDelete,
						 type: "POST",
				         url: requestUrl,
						 contentType: contentType,
		       			 data: "",
		       			 dataType: dataType,
						 jsonp: 'callback',
						 complete: completeMethod,
		       			 success: successMethod,
						 timeout: request.timeout
				});
			}
		},
	
		// Updates whether there is network activity
		networkActivity: function(active) {

			// Depending on whether there is network activity, call the right callback
			if ( active == true ) {
				if ( this.onNetworkActivityStarted != null ) { this.onNetworkActivityStarted.apply();	}
			}
			else {
				if ( this.onNetworkActivityStopped != null ) { this.onNetworkActivityStopped.apply();	}
			}
		}
	});
	
	// Setup our queue of requests
	$.requestQueue = new RequestQueue();

})(jQuery);

// Add a queue function to jQuery
jQuery.iQ = function(config) {
	
	// Push a new request onto our queue
	jQuery.requestQueue.pushRequest(config);
	
	// Go ahead and process the request
	jQuery.requestQueue.processNextRequestIfIdle();
};




