/**
 * (c) Badenoch-Smith I.T. Ltd
 * This is the main ajax 'common' functionality within the framework.
 */
 
if(typeof ajax=="undefined") { ajax = {}; }


// Version and name information
ajax.VERSION = 0.1;
ajax.NAME = "ajax";
ajax.DESCRIPTION = "Namespaced ajax functionality";

if(typeof ajax.AJAX_STATES=="undefined") { ajax.AJAX_STATES = {}; }
	
 /**
  * Constants that define the states returned by the XMLHttpRequest object.
  */
ajax.AJAX_STATES.UNINITIALIZED = 0; // unitialised
ajax.AJAX_STATES.OPEN = 1;	// loading
ajax.AJAX_STATES.SENT = 2;	// loaded
ajax.AJAX_STATES.RECEIVING = 3; // interactive
ajax.AJAX_STATES.LOADED = 4;	// complete
ajax.AJAX_MAX_TIMEOUT = 30 * 1000; // max request timeout (30 secs)

 /**
  * Represents a record to store in the batch. Each record will get executed in the order 
  * they are placed within the batch. Call the getAsLiteral method in order to retrieve the
  * record in the form { _url:<some_url>, ... }.
  * PARAMS:
  *	_url - the url
  *	_callback - the callback function
  * 	_method - GET or POST
  *	_content - content to pass to the xmlhttprequest.send method
  *	_async - should we async the calls.
  */
ajax.AjaxBatchItem = function(_url, _callback, _method, _content, _async) {
 	
 	var that = this;
 	
 	// Auto-generated id
 	var __id = "ABI_" + (new Date()).getTime();
 	
 	// The target url
 	var __url = _url;
 	// The callback function
 	var __callback = _callback;
 	// Method GET/POST
 	var __method = _method;
 	// Content to send in the request
 	var __content = _content;
 	// Is this async?
 	var __async = _async;
 	
 	
 	// If we have content then change the method to POST
 	if(__content)
 		__method = "POST";
 		
 	this.getId = function() { return __id; }
 	this.getUrl = function() { return __url; }
 	this.getCallback = function() { return __callback; }
 	this.getMethod = function() { return __method; }
 	this.getContent = function() { return __content; }
 	this.getAsync = function() { return __async; }
 			
 	/** 
 	 * The preProcess gets called before the handler function is called. (Or should it be called
 	 * before the callback.)
 	 */
 	var __preProcess = null;

 	/** 
 	 * The postProcess gets called before the handler function is called. (Or should it be called
 	 * before the callback.)
 	 */
	var __postProcess = null;
	
	/**
	 * Sets the pre process function. If set this function will be called before we make a 
	 * call through XMLHttpRequest.
	 * PARAMS:
	 *	f - the preProcess function
	 */
	this.setPreProcess = function(f) { 
 		if(typeof(f)!="function") {
 			throw new Error("preProcess must be a function");
 		}
		__preProcess = f; 
	}

	/**
	 * Sets the post process function. If set this function will be called after we make a 
	 * call through XMLHttpRequest.
	 * PARAMS:
	 *	f - the postProcess function
	 */
	this.setPostProcess = function(f) { 
 		if(typeof(f)!="function") {
 			throw new Error("postProcess must be a function");
 		}
		__postProcess = f; 
	}
	/**
	 * Returns the pre process function for this item
	 */
	this.getPreProcess = function() { return __preProcess; }
	/**
	 * Returns the post process function for this item
	 */
	this.getPostProcess = function() { return __postProcess; }
	
	/**
	 * Returns the object as an anonymous object using literal syntax.
	 */
	this.getAsLiteral = function() {
	 	return { _id:__id, _url:__url, _method: __method, 
 		 _content:__content, _callback:__callback,
 		 _aysnc:__async }
	 } 		
 }
 
 
 /**
  * This class is used to que (batch) ajax calls. Works as FIFO ...
  * NOTE:
  *	1. Really need to think about this as originally I intended
  *	   this class to delegate to the ajax request processor class - 
  *	   however the ARP class keeps a reference to the callback function
  * 	   which means that the 'next' batch item to execute will replace the
  *	   previous ones callback. Opps!!!! 
  *
  *	   The only way around this is to set async to false and make sure the batch 
  *	   is executed in order all the time .... don't like this at all ...
  */
ajax.AjaxBatchQue = function() {
 	
 	var that = this;
 	
 	// The batch
 	var _batch = new Array();
 	
 	/**
 	 * Gets the length of the batch
 	 */
 	this.getLength = function() {
 		return _batch.length;
 	}
 	
 	/**
 	 * Adds a new BatchRecord to the batch
 	 * PARAMS:
 	 *	b - the batch record
 	 */
 	this.add = function(b) {
 		if(b && b instanceof ajax.AjaxBatchItem) {
 			_batch[_batch.length] = b;	
 		} else {
 			throw new Error("Cannot add items to batch that are not instances of BatchRecord");
 		}
 	}
 	
	
 	/**
 	 * Probably don't need this as we could expose the _batch array through a protected method
 	 * and let the caller iterate over the array. Thought I'd leave it here anyway.
 	 */
 	this.next = function() {
		if(!_batch[0])
			return null;
 		
 		var _next = _batch[0];
 		delete _batch[0];
 		return _next;
 	}
 	
 }
 
 /**
  * The main processor for ajax calls. Things to note:
  *
  *	1. Call execute with one parameter; either a literal representation of a ajaxbatchitem
  *	   or with an instance of AjaxBatchItem. execute will create an entry in the batch que and then
  *	   execute as if this were a batch. Using the literal syntax means that the pre/post Process functions
  *	   will not be defined.
  *	2. Call executeBatch with any number of parameters of type specified in 1. Only the first item in the 
  *	   que is executed. If there are more then the next is executed once the current has finished running.
  *	3. If a batch item in the que has a pre or post process function defined this takes presidence over the 
  *	   pre/post process functions defined for the instance of the ARP.
  *	4. A message center can be defined for this class. This will handle any updates to progress bars etc.
  *	5. This class could possible do with some multithreaded behaviour. i.e. all items in the batch get executed
  *	   immediately.
  *	6. Use partial application and currying for the callbacks.
  *	7. We need to automatically switch the method to POSt if there is content in the request. Also need to set the 
  *	   header if we are doing a post i.e. for text _xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
  *	   or _xmlHttp.setRequestHeader("Content-Type", "text/xml"); for xml
  *	8. Need to put in timeouts
  *	9. Need to do some stuff for file downloads
  *	10. Need to do some testing for type of returned data i.e. XML, text, script, etc.
  */
 ajax.AjaxRequestProcessor = function(x) {
 	
 	var that = this;
 	
 	// The XMLHttpRequest object
 	var _xmlHttpComm = (x?x:org.bsit.AjaxCommsFactory.getInstance());
 	
 	// The message center - show's update message to show progress of the call
 	var _messageCenter = null;
 
 	// Contains the batched ajax calls to make
 	var _ajaxBatchQue = new ajax.AjaxBatchQue();
 	
 	// The current batch item
 	var _currentBatchItem = null;
 	
 	// The timeout value for a request - 0 means do not timeout
 	var _requestTimeout = 0;
 	
 	/** 
 	 * The preProcess gets called before the handler function is called. (Or should it be called
 	 * before the callback.)
 	 */
 	var _preProcess = null;

 	/** 
 	 * The postProcess gets called before the handler function is called. (Or should it be called
 	 * before the callback.)
 	 */
	var _postProcess = null;
	
	/**
	 * Sets the pre process function. If set this function will be called before we make a 
	 * call through XMLHttpRequest.
	 * PARAMS:
	 *	f - the preProcess function
	 */
	this.setPreProcess = function(f) { 
 		if(typeof(f)!="function") {
 			throw new Error("preProcess must be a function");
 		}
		_preProcess = f; 
	}

	/**
	 * Sets the request timeout, Defaults to ajax.AJAX_MAX_TIMEOUT is <0 or null
	 * @param ms the timeout in milliseconds
	 */
	this.setTimeout = function(ms) {
		if(!ms || ms<0)
			_requestTimeout = ajax.AJAX_MAX_TIMEOUT;
		else
			_requestTimeout = ms;
	}

	/**
	 * Sets the message center
	 */
	this.setMessageCenter = function(m) { _messageCenter = m; }
	
	/**
	 * Sets the post process function. If set this function will be called after we make a 
	 * call through XMLHttpRequest.
	 * PARAMS:
	 *	f - the postProcess function
	 */
	this.setPostProcess = function(f) { 
 		if(typeof(f)!="function") {
 			throw new Error("postProcess must be a function");
 		}
		_postProcess = f; 
	}

	/**
	 * Converts its argument to an AjaxBatchItem instance.
	 */
 	var convertToBatchItem = function(i) {
 		var batchItem = i;
 		if(!(i instanceof ajax.AjaxBatchItem)) {
			_callback = i._callback;
			_url = i._url;
			_method = ((!i._method)?"GET":i._method); 		
			_c = i._content;
			_async = ((!i._async)?true:i._async);
	 		batchItem = new ajax.AjaxBatchItem(_url, _callback, _method, _c, _async);
	 	} 
	 	
	 	return batchItem;
 	}	
 	
 	
 	var evaluateResponse = function() {
 		var contentType = _xmlHttpComm.getResponseHeader("Content-Type");
 		// Assume text and return this ..
 		if(!contentType || contentType=="text/plain" || contentType=="text/text" || contentType=="text/html") {
 			return _xmlHttpComm.responseText;
 		}
 		
 		if(contentType=="text/JSON") {
 			var jsonObj = new Function("return " + _xmlHttpComm.responseText);
 			return jsonObj; 		
 		}
 		
 		if(contentType=="text/xml") {
			var xmlResp = _xmlHttpComm.responseXML;
			// IE and Opera
			if(!xmlResp || !xmlResp.documentElement)
				throw("Invald XML response receieved!");
				
			return xmlResp;
		}
 	}
 	
 	/**
 	 * Is the internalCallback for the XMLHttpRequest object. This needs commenting
 	 */
 	var internalCallback = function() {
                 switch(_xmlHttpComm.readyState) {
 			case ajax.AJAX_STATES.UNINITIALIZED: // unitialised
 				break;
 			case ajax.AJAX_STATES.OPEN:	// loading
 				if(_messageCenter && _messageCenter.statusMessage) 
 					_messageCenter.statusMessage("Connection open ...");
 				break;
  			case ajax.AJAX_STATES.SENT:	// loaded
 				if(_messageCenter && _messageCenter.statusMessage) 
 					_messageCenter.statusMessage("Data sent ...");
  				break;
 			case ajax.AJAX_STATES.RECEIVING: // interactive
 				if(_messageCenter && _messageCenter.statusMessage) 
 					_messageCenter.statusMessage("Receiving data ...");
 				break;
 			case ajax.AJAX_STATES.LOADED:	// complete
 				if(_xmlHttpComm.status!=200)
 					throw new Error("An error occurred - status is: " + _xmlHttpComm.status);
 				
 				//clearTimeout();
 				
 				if(_messageCenter && _messageCenter.statusMessage) {
 					_messageCenter.updateTask();
 					_messageCenter.statusMessage("Completed ...");
 				}
 			
 				//http.StatusCodes.checkCode(_xmlHttpComm.status, _xmlHttpComm.statusText);
			
				resp = evaluateResponse();
                		if(_currentBatchItem) {
					if(_currentBatchItem.getCallback()) {
					    _currentBatchItem.getCallback()(resp);
                                         }
					if(_currentBatchItem.getPostProcess())
						_currentBatchItem.getPostProcess()();
					else if(_postProcess)
						_postProcess();
				}
 				if(_ajaxBatchQue.getLength()>0)
 					that.executeBatch();
 				break;
 			default:
 				throw new Error("Unknown readyState code returned from XMLHttpRequest");
 		}
 	}
 	
 	/**
 	 * Starts the request processor off for a single item. Should take a single parameter either a literal
 	 * i.e. { url:<some_url>, callback:<f>,  _method:<GET/POST/HEAD>, content:<content> }
 	 * or an AjaxBatchItem object.
  	 */
 	this.execute = function() {
 	
 		if(arguments.length!=1)
 			throw new Error("Need at least 1 arg to executeCommand. Either a literal or an instance of AjaxBatchItem");
		//var batchItem = convertToBatchItem(arguments[0]);	 	
 		//_ajaxBatchQue.add(batchItem);

		this.executeBatch(arguments[0]);
 	}
 	
 	

 	/**
 	 * Executes the batch items in the batch que. All items will be inserted into the que,
 	 * even if there is only one. This only takes the first on the list and executes that
 	 * if there are more in the internalCallback function will recall this function to execute the next one.
 	 * This function can take any number of arguments of either AjaxBatchItem or as a literal representation
 	 * of the AjaxBatchItem (see #AjaxBatchItem documentation above).
 	 */
 	this.executeBatch = function() {

 		if(arguments.length>0) {
 			for(var i=0;i<arguments.length;i++) {
 				_ajaxBatchQue.add(convertToBatchItem(arguments[i]));
 			}
 		}
		
		// Not sure about this don't think it will work ...
		if(_messageCenter && _messageCenter.setNumberOfTasks)
			_messageCenter.setNumberOfTasks(_ajaxBatchQue.length);

		// Just execute the first item in the list. The internalCallback function will
		// execute the next item if any when this one has completed
		if(_ajaxBatchQue.getLength()==1) {
			_currentBatchItem = _ajaxBatchQue.next();
			if(!_currentBatchItem)
				return;


			if(_xmlHttpComm) {
				_xmlHttpComm.onreadystatechange = internalCallback;
				_xmlHttpComm.open(_currentBatchItem.getMethod(), _currentBatchItem.getUrl(), _currentBatchItem.getAsync());
				// Call the pre process function if defined. Post process if called when the 
				// request has returned.
				if(_currentBatchItem.getPreProcess())
					_currentBatchItem.getPreProcess()();
				else if(_preProcess)
					_preProcess();

				// If we are sending content then set the headers
				if(_currentBatchItem.getMethod()=="POST") {
					_xmlHttpComm.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
					_xmlHttpComm.setRequestHeader("Content-length", _currentBatchItem.getContent().length);
					_xmlHttpComm.setRequestHeader("Connection", "close");
				}

				if(_requestTimeout>0) {
					setTimeout(function() { _xmlHttpRequest.abort(); }, _requestTimeout);
				}

				_xmlHttpComm.send(_currentBatchItem.getContent());
			}
		} 	
 	}
  
 }
 
 
 
 
 