//-----------------------------------------------------------------------------------------|
//-----------------------------------------------------------------------------------------|
// AUTHOR:                  Jeff Kody (kodyj@nlg.com)
// CREATED:                 10/23/2005
// MODIFICATION HISTORY:
// PURPOSE:                 this file contains objects and static methods for making AJAX
//							calls within a web-app page
// DEPENDENCIES:			- lib/javascript/general/event.js
//							- lib/javascript/general/logger.js
//-----------------------------------------------------------------------------------------|
//-----------------------------------------------------------------------------------------|

/*
the following file represents a library for using AJAX functionality on the websites
when this file is included on a web application page, it first created the following 3
objects and attaches them to the browser's "window" object.  That is how they should be
accessed, and inner functions rely on this in their functionality

MessageBuffer: 			This object manages all AJAX requests made by the client app
MessageCache:			This object caches message responses for possible later use. this
						is determined on the message level, and should only be used for 
						deterministic web-service calls
RequestPool:			This is a pool of 1 - n request objects that are available for use by the 
						message objects.  this is created on page load to reduce the overhead of 
						creating objects when a server call is being made
						

------		USING THE AJAX MESSAGE FUNCTIONALITY		------
//---THIS IS THE FUNCTION SIGNATURE FOR ADDING A MESSAGE, AND WHAT THE FINAL BOOLS MEAN
MessageBuffer.addMessage(as_Url, as_Method, as_CallBackMethod, as_RequestType, as_ReturnType, as_RequestBody, ab_Recurring, ab_Cache)
as_Url:					this is the address of the server that the request is made to
as_Method:				this is the method on the server that will be called--used in case we do a web-services implementation
as_CallBackMethod:		the function that will be executed om the browser when the request is complete.  the "message" object is passed
						to this function, so all of it's info is available
as_RequestType:			a constant defining the request type: valid values are"
						- AJAX_REQTYPE_GET (default)
						- AJAX_REQTYPE_POST 
						- AJAX_REQTYPE_HEADER
as_ReturnType:			this is the expected message format of the return type.  the return value will be in the form (XML Document, JS Object, or string)
						based on the value passed in.  valid values are as follows:
						- AJAX_RETTYPE_XML
						- AJAX_RETTYPE_JSON
						- AJAX_RETTYPE_STRING (default)
as_RequestBody:			if the request type is a "POST" then this value will be passed to the server as the request body.  not required for a GET or HEAD
ab_Cache:				if the message call is deterministic, the request can be cached.  When a message is being executed, it will first look in the 
						cache for a message with the same signature (as determined by a message-hashcode).  if one is found, it discards the new message, and 
						uses the one from the cache.
ab_Recurring:			a recurring request is one that is made with the same parameters at a specified interval.  MessageBuffer.beginTimedExecution must
						be called for these requests to be processed
						
TODO: 
- create functions to cast objects to their requested formats
*/
var AJAX_REQTYPE_GET 					= "GET";
var AJAX_REQTYPE_POST 					= "POST";
var AJAX_REQTYPE_HEADER 				= "HEAD";

var AJAX_RETTYPE_XML 					= "XML";
var AJAX_RETTYPE_JSON 					= "JSON";
var AJAX_RETTYPE_STRING 				= "STRING";

var AJAX_DEFAULT_PROCESS_MS				= 2000;

var AJAX_QUEUE_PROCESSTYPE_IMMEDIATE 	= 1;
var AJAX_QUEUE_PROCESSTYPE_TIMED 		= 2;
var AJAX_QUEUE_SINGLE_MESSAGE_IN_QUEUE  = false;
var AJAX_DOLOGGING						= true;  	//---if((AJAX_DOLOGGING) && (window.Logger)) Logger.log()

var AJAX_CACHE_EXPIRETYPE_TIMED			= 1;
var AJAX_CACHE_EXPIRETYPE_HITCOUNT		= 2;
var AJAX_MESSAGE_TIMEOUT_INTERVAL		= 1800000; 	//---3 minutes

var AJAX_MAX_POOL_SIZE					= 2; 		//---this is the max allowed by IE using HTTP1.1 without changing a registry
													//	 setting.  Firefox allows 8 by default, but this is probably overkill.

//---*************************************************************************************************************************
//	 MESSAGE BUFFER OBJECT CREATION, AND ATTACHMENT TO WINDOW OBJECT
var __messageBuffer = {
	addMessage: function (as_Url, as_Method, as_CallBackMethod, as_RequestType, as_ReturnType, as_RequestBody, ab_Cache, ab_Recurring) {

			lo_msg = new AjaxMessage(as_Url, as_Method, as_CallBackMethod, as_RequestType, as_ReturnType, as_RequestBody, this.nextMID++, ab_Recurring, ab_Cache);		
			
			this.addMessageObject(lo_msg);
		}, 
		
	addMessageObject: function(ao_Msg) {
			var lo_msg = ao_Msg;
	
			if(lo_msg.CacheMessage) {
				//---if this is a cachable message, then we may have already cached it.  if so,
				//	 destroy the identical message, and use the cached version.
				if((AJAX_DOLOGGING) && (window.Logger)) Logger.addMsg("checking cache for message key: " + lo_msg.generateUniqueKey())		
				lo_cachedMsg = MessageCache.get(lo_msg.generateUniqueKey());

				if(lo_cachedMsg) {
					if((AJAX_DOLOGGING) && (window.Logger)) Logger.addMsg("message found in cache; replacing new message with cached version")	
					lo_msg = null;
					lo_msg = lo_cachedMsg;
				}
			}
			
			if(this.processType != AJAX_QUEUE_PROCESSTYPE_TIMED) {
				if((AJAX_DOLOGGING) && (window.Logger)) Logger.addMsg("processing message: " + lo_msg)		
				lo_msg.processMessage();
			} else {
				if(this.singleMessageInQueue) {
					this.messages[0] = lo_msg;	
				} else if(!this.existsInQueue(lo_msg)) {
					if((AJAX_DOLOGGING) && (window.Logger)) Logger.addMsg("queueing message: " + lo_msg)		
					this.messages.push(lo_msg);	
				} else {
					if((AJAX_DOLOGGING) && (window.Logger)) Logger.addMsg("already queued for processing--message disregarded: " + lo_msg.generateUniqueKey())	
				}
			}
		},
		
		
	processQueue: function() {
			if((this.processType == AJAX_QUEUE_PROCESSTYPE_IMMEDIATE) || (this.messages.length == 0)) {
				return false;		
			}

			if((AJAX_DOLOGGING) && (window.Logger)) Logger.addMsg("processing queue: " + this.messages.length + " messages")		
			
			for(i = 0; i < this.messages.length; i++) {
				var lo_MesssageFromCache = MessageCache.get(this.messages[i].generateUniqueKey());
				
				if(lo_MesssageFromCache) {
					lo_MesssageFromCache.processMessage();
					this.messages[i].Processed++;
				} else {
					if((this.messages[i].Recurring) || (this.messages[i].Processed == 0)) {
						this.messages[i].processMessage();				
					}
				}				
				
				if(this.singleMessageInQueue) {
					this.messages = new Array();
				} else if((this.messages[i]) && (!this.messages[i].Recurring)) {
					this.messages.splice(i, 1);
				}
			}
		},
		
	existsInQueue: function(ao_Message) {
			for(i = 0; i < this.messages.length; i++) {
				if(ao_Message.isEqual(this.messages[i]))
					return true;
			}
			
			return false;
		},	
	
	getMessageById: function(ai_MID) {
			alert(	this.messages.length + '---' + 	ai_MID);	
			for(i = 0; i < this.messages.length; i++) {
				
				if(this.messages[i].MID == ai_MID) {
						
					return this.messages[ai_MID];
				}
			}
			
			return null;
		},
	
	beginTimedExecution: function(al_ms) {
			if((AJAX_DOLOGGING) && (window.Logger)) Logger.addMsg("timed execution activated")	
			this.processType = AJAX_QUEUE_PROCESSTYPE_TIMED;
			var al_Interval = al_ms ? al_ms : AJAX_DEFAULT_PROCESS_MS;
			this.intervalID = setInterval("MessageBuffer.processQueue()", al_Interval);
		},
	
	cancelTimedExecution: function() {
			if((AJAX_DOLOGGING) && (window.Logger)) Logger.addMsg("timed execution cancelled")	
			this.processType = AJAX_QUEUE_PROCESSTYPE_IMMEDIATE;
			clearInterval(this.intervalID);
		},
		
	handleTimeout: function(ao_msg) {
			if((AJAX_DOLOGGING) && (window.Logger)) Logger.addMsg("<font color=purple>timeout event fired: " + ao_msg.generateUniqueKey() + "</font>");
			ao_msg.AJAX.abort();
			clearTimeout(ao_msg.timeoutHandle);
			var ls_timeoutHandler = this.onTimeout;
			
			if (typeof ls_timeoutHandler != 'undefined' || ls_timeoutHandler != null)
				ls_timeoutHandler(ao_msg);
		},
		
	toString: function() {
			return "[userobject MessageQueue]";
		},
		
	messages: [],
	singleMessageInQueue: AJAX_QUEUE_SINGLE_MESSAGE_IN_QUEUE,
	intervalID: 0,
	nextMID: 1,
	processType: AJAX_QUEUE_PROCESSTYPE_IMMEDIATE,
	onTimeout: handleMessageTimeout,
	timeoutMilliSeconds: AJAX_MESSAGE_TIMEOUT_INTERVAL,
	messageElement: null
}
//---*************************************************************************************************************************


//---*************************************************************************************************************************
//	 MESSAGE CACHE OBJECT CREATION, AND ATTACHMENT TO WINDOW OBJECT
var __messageCache = {
	add: function(ao_Message) {
			try {
				this.cache[ao_Message.generateUniqueKey()] = new CacheObject(ao_Message)
			} catch(ex) {}
		},
		
	cleanup: function() {
			if(this.cache.length < this.threshold) {
				//---if we haven't met the threshold, don't bother doing the cleanup routine
				return;
			}
	
			if(this.expiretype == AJAX_CACHE_EXPIRETYPE_TIMED) {
				//---TODO: implement code here
				for(x = 0; x < this.cache.length; x++) {
					var ldt_exp = new Date();
					
					ldt_exp.setTime(ldt_exp.getTime() - this.shelflife);
					if(this.cache[x].lastHitTime < ldt_exp) {
						this.cache[x] = null;
					}
				}
			} else if(this.expiretype ==  AJAX_CACHE_EXPIRETYPE_HITCOUNT) {
				//---TODO: implement code here
			} else {
				//---this will allow the cache infinite growth
			}
		},
	
	get: function(as_Key) {			
			if(this.cache[as_Key]) {
				this.cache[as_Key].hits++;
				this.cache[as_Key].lastHitTime = new Date();
				return this.cache[as_Key].message;
			} else {
				return false;
			}
		},
		
	capacity: 15,
	threshold: 7,
	shelflife: 180000, //---3 minutes
	cache: [],
	expiretype: AJAX_CACHE_EXPIRETYPE_TIMED
}
//---*************************************************************************************************************************


//---*************************************************************************************************************************
//	 REQUEST POOL OBJECT CREATION, AND ATTACHMENT TO WINDOW OBJECT
var __requestPool =  {
	init: function(ai_PoolSize) {
			var li_PoolSize = ai_PoolSize ? ai_PoolSize : this.numberOfRequestInPool;
			this.pool = [];
			
			for(i = 0; i < li_PoolSize; i++) {
				if((AJAX_DOLOGGING) && (window.Logger)) Logger.addMsg("adding request to pool")	
				this.pool.push(new this._getRequestObject());
			}
		},
		
	getOpenRequest: function() {
			//while(1 == 1) {
				for(i = 0; i < this.pool.length; i++) {
					if ((this.pool[i]) && ((this.pool[i].readyState == 4) || (this.pool[i].readyState == 0))) {
						if((AJAX_DOLOGGING) && (window.Logger)) Logger.addMsg("returning request " + i );
						return this.pool[i];
					}
				}
			//}
		},
		
	_getRequestObject: function() {
			var lo_xmlhttp = false;

			//---try getting an request object in an IE version
			try {
				lo_xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
			} catch (lo_ex) {
				try {
					lo_xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
				} catch (lo_iex) {
					lo_xmlhttp = false;
				}
			}
			
			//---try getting the request object from the window object
			if ((!lo_xmlhttp) && (typeof XMLHttpRequest != 'undefined')) {
				lo_xmlhttp = new XMLHttpRequest();
			}
				
			return lo_xmlhttp;
		},	
		
		toString: function() {
			return "[userobject RequestPool]";
		},			


		numberOfRequestInPool: 5,			
		pool: []
}
//---*************************************************************************************************************************



//---*************************************************************************************************************************
//	 AJAX MESSAGE OBJECT
function AjaxMessage(as_Url, as_WebMethod, as_CallBackMethod, as_RequestType, as_ReturnType, as_RequestBody, ai_MID, ab_CacheMessage, ab_Recurring) {  
	//---Message Object Methods
	this.validateRequestType = function(as_Type) {
			//---this funciton forces a valid request type
			if( (as_Type == AJAX_REQTYPE_POST) || (as_Type == AJAX_REQTYPE_HEADER) ) {
				return as_Type;
			} else {
				return AJAX_REQTYPE_GET;
			}
		 }
	  
	this.validateReturnType = function(as_Type) {
			//---this funciton forces a valid return type
			if( (as_Type == AJAX_RETTYPE_JSON) || (as_Type == AJAX_RETTYPE_XML) ) {
				return as_Type;
			} else {
				return AJAX_RETTYPE_STRING;
			}
		}
	this.isEqual = function(ao_Message) {
			//---this function has been altered from its original form so that the
			//	 logic for identifying a unique message request can exist in one place
			if( (this.URL == ao_Message.URL) && 
				(this.WebMethod == ao_Message.WebMethod) && 
				(this.CallBackMethod == ao_Message.CallBackMethod) && 
				(this.RequestType == ao_Message.RequestType) && 
				(this.ReturnType == ao_Message.ReturnType) && 
				(this.Recurring == ao_Message.Recurring) && 
				(this.CacheMessage == ao_Message.CacheMessage) && 
				(this.RequestBody == ao_Message.RequestBody)			
			) {
				return true;
			} else {
				return false;
			}
		}
	this.getContentType = function() {
			switch(this.RequestType) {
				case(AJAX_REQTYPE_POST):
					if(this.ReturnType == AJAX_RETTYPE_XML) {
						return "application/x-www-form-urlencoded";
						//return "text/xml";
					} else {
						return "application/x-www-form-urlencoded";
					}
					
				default:
					return "text/html";
			}
		}
	this.toString = function() {
			return "<Message> <MID>" + this.MID + "</MID> " +
					"<HashKey>" + this.generateUniqueKey() + "</HashKey> " +
					"<URL>" + this.URL + "</URL> " +
					"<Recurring>" + this.Recurring + "</Recurring> " +  
					"<Processed>" + this.Processed + "</Processed> " +
					"<CacheMessage>" + this.CacheMessage + "</CacheMessage> " +
					"<WebMethod>" + this.WebMethod + "</WebMethod> " +
					"<CallBackMethod>" + this.CallBackMethod + "</CallBackMethod> " + 
					"<RequestType>" + this.RequestType + "</RequestType> " +  
					"<ReturnType>" + this.ReturnType + "</ReturnType> " +  
					"<RequestBody>" + this.RequestBody + "</RequestBody> " +  
					"<ReturnValue>" + this.ReturnValue + "</ReturnValue> </Message>";
		}
		
	this.processMessage = function() {	
			var lo_self = this;
			
			//---if a message has been cached, and is being re-processed, use
			//	 the data that was previously retrieved to save a round-trip
			//	 to the server
			if((lo_self.Processed > 0) && (lo_self.ReturnValue.length > 0)) {
				var ls_callback = lo_self.CallBackMethod + "(lo_self)";
				
				lo_self.Processed++;
				if((AJAX_DOLOGGING) && (window.Logger)) Logger.addMsg("<font color=green>message processed from cache: " + lo_msg.generateUniqueKey() + "</font>");
				
				if (typeof eval(lo_self.CallBackMethod) != 'undefined' || eval(lo_self.CallBackMethod) != null)
						eval(ls_callback);			
				
				return;
			}

			//---otherwise, we've got to get the data
			lo_self.AJAX = RequestPool.getOpenRequest();
			lo_self.AJAX.open(this.RequestType, this.URL, true); 					
			lo_self.AJAX.setRequestHeader("Content-Type", this.getContentType());
			lo_self.AJAX.setRequestHeader("MessageID", this.MID.toString());
			
			var timeoutFunc = function () { MessageBuffer.handleTimeout(lo_self) };
			lo_self.timeoutHandle = setTimeout(timeoutFunc, MessageBuffer.timeoutMilliSeconds)	
			
			lo_self.AJAX.onreadystatechange = function() {
					if(MessageBuffer.messageElement)
						MessageBuffer.messageElement.innerHTML = defineReadyState(lo_self.AJAX.readyState);
					
					if(lo_self.AJAX.readyState == 4) {
						clearTimeout(lo_self.timeoutHandle);						
						var lo_data = null;		
						var lo_tmp = null
						var ls_callback = lo_self.CallBackMethod + "(lo_self)";		
						var ix = 0;				
						var la_tmp = [];		
						
						if((lo_self.Processed > 0) && (!lo_self.Recurring))
							return;						
						
						switch(lo_self.ReturnType) {
							case(AJAX_RETTYPE_XML):
								if(lo_self.AJAX.responseXML) {
									lo_data = lo_self.AJAX.responseXML;
								} else {
									//---in this case, the user was expecting XML, but the data that was
									//	 returned couldn't be parsed into a DOM, so 
									//this.Warnings.push("Response data not in XML format, converted type to string");
									lo_self.ReturnType = AJAX_RETTYPE_STRING;
									lo_data = lo_self.AJAX.responseText;	
								}								
								break;						
							case(AJAX_RETTYPE_JSON):		
								if(lo_self.AJAX.responseXML) {
									for(ix = 0; ix < lo_self.AJAX.responseXML.documentElement.childNodes.length; ix++) {
										la_tmp.push(lo_self.AJAX.responseXML.documentElement.childNodes[ix].nodeValue);
									}
								}		

								var ls_o = la_tmp.join("");
								
								if(ls_o.length > 0) {
									//eval("lo_data = " + ls_o);
									//lo_data = ls_o;
									
									try {
										eval("lo_data = " + ls_o);
									} catch(e) {
										//alert(e.message + '\n\n' + e.description);
										
										lo_self.ReturnType = AJAX_RETTYPE_STRING;
										lo_data = ls_o;
									}		
															
								}
								//lo_data = new Function("return " + la_tmp.join(""))();
								break;
							case(AJAX_RETTYPE_STRING):
								lo_data = lo_self.AJAX.responseXML ? lo_self.AJAX.responseXML.documentElement.firstChild.nodeValue : lo_self.AJAX.responseText;	
								
								break;								
						}
						
						lo_self.ReturnValue = lo_data;
						lo_self.Processed++;						
						
						if((lo_self.CacheMessage) && (MessageCache)) {
							 MessageCache.add(lo_self);			
							 if((AJAX_DOLOGGING) && (window.Logger)) Logger.addMsg("<font color=green>message added to cache: " + lo_msg.generateUniqueKey() + "</font>");
						}
							 				
						if((AJAX_DOLOGGING) && (window.Logger)) Logger.addMsg("<font color=purple>message processed server round-trip: " + lo_msg.generateUniqueKey() + "</font>");
						
						if (typeof eval(lo_self.CallBackMethod) != 'undefined' || eval(lo_self.CallBackMethod) != null)
							eval(ls_callback);						
					}
				}
			
			if((lo_self.Recurring) || (lo_self.Processed == 0))
				lo_self.AJAX.send(lo_self.RequestBody);
		}
		
	this.generateUniqueKey = function() {
			return this.URL.replace(/[\.\/:(http)]/g, "") +
					 (this.Recurring ? "1" : "0") +
					 (this.CacheMessage ? "1" : "0") +
					 this.WebMethod +
					 this.CallBackMethod + 
					 this.RequestType +
					 this.ReturnType +
					 this.RequestBody +
					 this.MID;
		}
	
	this.URL = as_Url;
	this.WebMethod = as_WebMethod;
	this.CallBackMethod = as_CallBackMethod ? as_CallBackMethod : "genericAJAXCallback";
	this.RequestType = this.validateRequestType(as_RequestType);
	this.ReturnType = this.validateReturnType(as_ReturnType);
	this.Recurring = ab_Recurring;
	this.RequestBody = as_RequestBody;
	this.CacheMessage = ab_CacheMessage;
	this.MID = ai_MID;
	this.Warnings = [];
	
	//---Outputs
	this.Processed = 0;
	this.ReturnValue = "";  
}
//---*************************************************************************************************************************


//---*************************************************************************************************************************
//	 CACHE OBJECT DEFINITION
function CacheObject(ao_Message) {
	this.message = ao_Message;
	this.hits = 0;
	this.lastHitTime = new Date();
	
	this.toString = function() {
			return "[userobject CacheObject]";
		}
}

//---*************************************************************************************************************************

	
//---*************************************************************************************************************************
//	 STATIC HELPER FUNCTIONS		
function genericAJAXCallback(lo_msg) {
	if((AJAX_DOLOGGING) && (window.Logger)) Logger.addMsg("<font color=blue>generic callback used--no code ex: " + lo_msg.generateUniqueKey() + "</font>");
	//---this function handles calls that require no response.  we can log, but no code will be executed
}

function handleAjaxIncapable() {
	//---TODO: add code here
}

function handleMessageTimeout(msg) {
	//---TODO: add code here
	alert("TIMEOUT OCCURED:\n" + msg);
}

function initAjaxFramework(requestsInPool) {
	var li_Request = requestsInPool ? requestsInPool : AJAX_MAX_POOL_SIZE;

	window.MessageBuffer = __messageBuffer;	
	window.MessageCache = __messageCache;
	window.RequestPool = __requestPool;
	window.RequestPool.init(requestsInPool);
}

function disposeAjaxFramework() {
	//---this is a reference to a object in the document (which
	//	 is marshalled in IE) and is therefore being explicitly
	//	 de-referenced.  leaving this hanging can cause memory leaks
	if(window.MessageBuffer) {
		if(window.MessageBuffer.messageElement)
			window.MessageBuffer.messageElement = null;
	}

	__messageBuffer = null;
	__messageCache = null;
	__requestPool = null;
	
	window.MessageBuffer = null;	
	window.MessageCache = null;
	window.RequestPool = null;
	
}
function defineReadyState(ai_state) {
	var ls_out = "";
	
	switch(ai_state) {
		case(0):
			ls_out = "Uninitialized";
			break;
		case(1):
			ls_out = "Processing...";
			break;
		case(2):
			ls_out = "Request sent";
			break;
		case(3):
			ls_out = "Receiving data";
			break;
		case(4):
			ls_out = "Complete";
			break;
		default:
			ls_out = "Unexpected state code";
			break;
	}
	
	return ls_out;
}




//---*************************************************************************************************************************
