
var AJAXQUEUE_STATUS_PREPPING   = 0;
var AJAXQUEUE_STATUS_QUEUED     = 1;
var AJAXQUEUE_STATUS_PROCESSING = 2;
var AJAXQUEUE_STATUS_SENT       = 3;
var AJAXQUEUE_STATUS_COMPLETE   = 4;
var AJAXQUEUE_STATUS_ERROR      = 5;
var AJAXQUEUE_STATUS_CANCELLED  = 6;

function AjaxQueue() {

	// do clean up-- leaving this off is good for debugging, but will waste memory over time
	this.bDoCleanup = true;

	// how long to wait before acting on the next doAjaxHandling
	this.iAjaxCallInterval = 250;

	// the id setInterval hands to AjaxQueue.   this is (unfortunately) set outisde the object
	this.iIntervalId = 0;

	// wether or not set interval has started
	this.bIntervalStarted = false;

	// the locks that will prevent ajax calls from occurring
	this.locks = new Object;

	// the list of ajax calls themselves
	this.calls = new Object;

	// for debugging
	this.strLastCall = null;
	this.iIteration = 0;
   this.errorHandler = null;

   //Error handlers will receive an error object.
   this.RegisterErrorHandler = function( pFunction ) {
      this.errorHandler = pFunction;
   }

	// schedule an ajax call
	this.schedule = function(strName, strUrl, fCallback, bInterrupt, bPriority, strPostData) {
		// if the ajax call name exists, overwrite
		if (this.calls[strName] != undefined) {
			this.destroyCall(strName);
		}
		var bReturn = true;
		var queueItem = new AjaxQueueCall(strUrl, strPostData);
		queueItem.callback = fCallback;
		queueItem.priority = (bPriority == true) ? true : false;

		if (bInterrupt == true) {
			this.purgeAllCalls();
		}

      // push the ajax call onto the list of calls, set it's state to preparing, NOT ready
      this.calls[strName] = queueItem;
      this.calls[strName].state = AJAXQUEUE_STATUS_PREPPING;

		// create the ajax function, and set it up so all doAjaxHandling has to do
		// is fire the ajax event.  complain if you can't schedule
		try {
			this.calls[strName].xmlObject = this.getNewXMLObject();
			this.calls[strName].xmlObject.onreadystatechange = function() { onReadyStateChangeHelper( strName ); }
		} catch (e) {
         if( this.errorHandler ) {
            this.errorHandler( e );
         } else {
            alert("AjaxQueue Scheduling Failed for ["+strName+"]: "+e.message);
         }			
			bRetrun = false;
		}

		// Now set the state to ready
		this.calls[strName].state = AJAXQUEUE_STATUS_QUEUED;

		return bReturn;
	}

	// check ajax locks for anything that might stop us
	this.isLocked = function(strLock) {
		if (strLock != undefined) {
			if (this.locks[strLock] != undefined) {
				return this.locks[strLock].state;
			} else {
				return false;
			}
		} else {
			for (var i in this.locks) {	
				if (this.locks[i].state == true) {
					return true;
				}
			}
			return false;
		}
	}


	// create a lock that could stop ajax calls
	this.registerLock = function(strLockName) {
		var lock = new AjaxQueueLock(strLockName);
		this.locks[strLockName] = lock;
		return;
	}

	// destroy a lock
	this.unregisterLock = function(strLockName) {
      try{
         delete this.locks[strLockName];
      } catch (e) {
      }
      return;
	}

	// make the lock go, halting ajax calls.
	this.engageLock = function(strLockName) {
		return this.changeLockState(strLockName, true);
	}

	// stop the lock, allowing ajax calls
	this.disengageLock = function(strLockName) {
		return this.changeLockState(strLockName, false);
	}

	// this used to be a lot harder, then I decided that object > array
	this.changeLockState = function(strLockName, bState) {
		try {
			this.locks[strLockName].state = bState;
		} catch (e) {
         if( this.errorHandler ) {
            this.errorHandler( e );
         } else {
            alert("AjaxQueue: cannot change lock ["+strLockName+"]: " + e.message);
         }
		}
		return;
	}

	// destory an ajax call completely
	this.destroyCall = function(strName) {
		if (this.calls[strName] != undefined) {
			this.calls[strName].state = AJAXQUEUE_STATUS_CANCELLED;
         this.abortCall(strName);
         if (this.bDoCleanup == true) {
            this.purgeCall(strName);
         }
		}
	}

	// removes the call from the calls object	
	this.purgeCall = function(strName) {
      try {
         delete this.calls[strName];
      } catch (e) {
      }
		return;
	}

	// calls the XML object native abort.  It may be necessary to call the abort but not
	// destroy the object some time in the future.  maybe.
	this.abortCall = function(strName) {
		this.calls[strName].xmlObject.abort();
		return;
	}


	// destroy ALL ajax calls
	this.purgeAllCalls = function() {
		for (var i in this.calls) {	
			this.destroyCall(i);
		}

		// just to make really, really sure
		if (this.bDoCleanup == true) {
			this.calls = new Object;
		}
	}

	this.getNewXMLObject = function() {
      var newAjaxObj = null;
		try {
			if (window.XMLHttpRequest) {
				newAjaxObj = new XMLHttpRequest();
			} else {
				newAjaxObj = new ActiveXObject("Microsoft.XMLHTTP");
			}
			if (newAjaxObj == undefined || newAjaxObj == null) {
				// do no AJAX handling here
				alert("This website requires that your browser support AJAX.  Please update your browser, or use the accesible site links at the bottom of the page to continue.");
			} 
		} catch (e) {
			alert("This website requires that your browser support AJAX.  Please update your browser, or use the accesible site links at the bottom of the page to continue.");
		}

	  return newAjaxObj;
	}



	this.doAjaxHandling = function() {

		var strCall = this.getNextPriorityOpenCall();
		if (strCall == null) {
			strCall = this.getNextOpenCall();
		}
		this.strLastCall = strCall;
		this.iIteration++;
			
		if (strCall != null) {

			if (this.isLocked() == true && this.calls[strCall].priority == false) {
				this.strLastCall = "skip! " + this.isLocked() + " " + this.calls[strCall].priority;
				return;
			}
         this.calls[strCall].state = AJAXQUEUE_STATUS_PROCESSING;
				
			try {
				var queueItem = this.calls[strCall];
				if( queueItem.postData == null ) {
					queueItem.xmlObject.open("GET", queueItem.url, true);
					queueItem.xmlObject.send("");
				} else {
					queueItem.xmlObject.open("POST", queueItem.url, true);
					queueItem.xmlObject.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
					queueItem.xmlObject.setRequestHeader("Content-length", queueItem.postData.length);
					queueItem.xmlObject.setRequestHeader("Connection", "close");					
					queueItem.xmlObject.send(queueItem.postData);
				}
			} catch (e) {
            queueItem.state = AJAXQUEUE_STATUS_ERROR;
            if( this.errorHandler ) {
               this.errorHandler( e );
            } else {
               alert("AjaxQueue: Could not act on ["+strCall+"]:" + e.message);
            }			
			}
		}

		//time to call the cleaners
		this.cleanUpCalls();
	}


	// give me the next ajax call which has a state of AJAXQUEUE_STATUS_QUEUED, meaning it's ready to go
	this.getNextOpenCall = function() {
		for (var i in this.calls) {
			if (this.calls[i].state == AJAXQUEUE_STATUS_QUEUED) {
				return i;
			}
		}
		return null;
	}

	this.getNextPriorityOpenCall = function() {
		for (var i in this.calls) {
			if (this.calls[i].state == AJAXQUEUE_STATUS_QUEUED && this.calls[i].priority == true) {
				return i;
			}
		}
		return null;
	}	


	// destory all calls that have a state > 2, which means that they're completed or failed
	this.cleanUpCalls = function() {
		if (this.bDoCleanup == true) {
			for (var i in this.calls) {
				if (this.calls[i].state > AJAXQUEUE_STATUS_SENT) {
					this.destroyCall(i);
				}
			}
			return;
		}
	}


   //Does this browser do AJAX?
   this.IsAjaxCapable = function() {
      var tmp = this.getNewXMLObject();
      if( tmp == undefined || tmp == null ) {
         return false;
      }
      tmp = null;
      return true;
   }

}

//helper functions go here
function onReadyStateChangeHelper( strName )
{
   if( g_AjaxQueue.calls[strName] ) {
      if( g_AjaxQueue.calls[strName].state != AJAXQUEUE_STATUS_PROCESSING )  {
         //We're not processing, we don't need to do anything, this is a complete or abandoned transaction.
         return;
      }
      if (g_AjaxQueue.calls[strName].xmlObject.readyState == 4 ) {
         if( g_AjaxQueue.calls[strName].xmlObject.status && g_AjaxQueue.calls[strName].xmlObject.status == 200) {
            try {
               g_AjaxQueue.calls[strName].callback(g_AjaxQueue.calls[strName].xmlObject);
               g_AjaxQueue.calls[strName].state = AJAXQUEUE_STATUS_COMPLETE;
            } catch (e) {
               g_AjaxQueue.calls[strName].state = AJAXQUEUE_STATUS_ERROR;
               if( g_AjaxQueue.errorHandler ) {
                  g_AjaxQueue.errorHandler( e );
               } else {
                  alert("AjaxQueue: callback for ["+strName+"] failed: "+e.message);
               }
            }
         } else if (g_AjaxQueue.calls[strName].xmlObject.status == 404) {
            g_AjaxQueue.calls[strName].state = AJAXQUEUE_STATUS_ERROR;
            if( g_AjaxQueue.errorHandler ) {
               var e = new Error("404: Page not found, " + strName );
               g_AjaxQueue.errorHandler( e );
            } else {
               alert("AjaxQueue Call Failed for ["+ strName +"]: 404 Page not found.");
            }         
         } else if (g_AjaxQueue.calls[strName].xmlObject.status == 500) {
            g_AjaxQueue.calls[strName].state = AJAXQUEUE_STATUS_ERROR;
            if( g_AjaxQueue.errorHandler ) {
               var e = new Error("500: Internal server error, " + strName );
               g_AjaxQueue.errorHandler( e );
            } else {
               alert("AjaxQueue Call Failed for ["+ strName +"]: 500 Internal Server Error!");
            }         
         }
      }
   }
}

function AjaxQueueCall(strUrl, strPostData) {
	this.url = strUrl;
	this.postData = strPostData;
	this.xmlObject = null;
	this.state = AJAXQUEUE_STATUS_PREPPING;
	this.callback = null;
	this.priority = 0;
}

function AjaxQueueLock(strLockName) {
	this.name = strLockName;
	this.state = false;
}

function AjaxGetNodeValue(obj, tag)
{
   if( obj ) {
      var pElement = obj.getElementsByTagName(tag);
      if( pElement && pElement[0] ) {
         if( pElement[0].firstChild ) {
            return pElement[0].firstChild.nodeValue;
         }
      }
   }
   return "";
}



// make the queue object, and fire off the timer
g_AjaxQueue = new AjaxQueue();
g_bAjaxEnabled = g_AjaxQueue.IsAjaxCapable();

function fireAjaxQueue() {
	if (g_AjaxQueue.bIntervalStarted == false) {
		g_AjaxQueue.iIntervalId = setInterval(function(){g_AjaxQueue.doAjaxHandling();}, g_AjaxQueue.iAjaxCallInterval);
		g_AjaxQueue.bIntervalStarted == true;
	}
}
fireAjaxQueue();

