var AjaxRequest = null;


// ***************************************************** AjaxRequest
with({
	//Private static properties
	_DEFAULT_MASON_AJAX_MODULE : './ajax.shtml',
	_DEFAULT_LOADING_MSG : '<br/><p align="center"><img src="'+GetImageSource('Searching')+'"></p>',
	_DEFAULT_HTTP_REQUEST : 'GET',
	_DEFAULT_GLOBAL_BEHAVIOR : 'proceed',
	_DEFAULT_INSTANCE_BEHAVIOR : 'force',
	_DEFAULT_TIMEOUT: 0, //in seconds
	ActiveRequests : {}, 
	OrderedRequests : {},
	Instances : 0,
	RequestId: 0,

	ActiveCommands : function(command) { //Returns the number of calls being made with the passed in command.
		if (this.ActiveRequests[command]) {
			return this.ActiveRequests[command].length;
		}

		return 0;
	},

	AddActiveCommand : function(call) {
		if (!this.ActiveRequests[call.command]) {
			this.ActiveRequests[call.command]= [];
		}
		this.ActiveRequests[call.command].push(call);
	},

	RemoveActiveCommand : function(call) {
		if (this.ActiveRequests[call.command] && this.ActiveRequests[call.command].length) {
			for (var i=0;i<this.ActiveRequests[call.command].length;i++) {
				if (this.ActiveRequests[call.command][i].request_id == call.request_id) {
					this.ActiveRequests[call.command].splice(i,1);
				}

			}
		}
	},

	PlaceInOrder : function(call) {
		if (!this.OrderedRequests[call.command]) {
			this.OrderedRequests[call.command] = [];
		}   
		this.OrderedRequests[call.command].push({ id: call.request_id});
	},

	PlaceFirstInOrder: function(call) {
		if (!this.OrderedRequests[call.command]) {
			this.OrderedRequests[call.command] = [];
		}

		this.OrderedRequests[call.command].unshift({ id: call.request_id});
	},

	NextInOrder : function(call) {
		if (this.OrderedRequests[call.command] && this.OrderedRequests[call.command].length) {
			if (this.OrderedRequests[call.command][0].id == call.request_id) {
				return true;
			} else {
				return false;
			}
		} else {
			return true;
		}

	},

	ShiftOrder : function(command) {
		if (typeof(this.OrderedRequests[command]) == 'object') {
			this.OrderedRequests[command].shift();
		}
	},

	DeferExecution: function(call,func) {

		for (var i = 0; i< this.OrderedRequests[call.command].length; i++) {
			if (this.OrderedRequests[call.command][i].id == call.request_id) {
				this.OrderedRequests[call.command][i].func = func;
				break;
			}
		}
	},

	RunNextDeferred : function(command) {
		if (typeof(this.OrderedRequests[command]) == 'object') {
			if ((typeof(this.OrderedRequests[command][0]) == 'object') && (typeof(this.OrderedRequests[command][0].func) == 'function')) {
				var func = this.OrderedRequests[command][0].func;
				this.ShiftOrder(command);
				func();
			}
		}
	},

	RemoveFromOrder : function(call) {
		if (typeof(this.OrderedRequests[call.command]) == 'object') {
			for (var i =0; i< this.OrderedRequests[call.command].length; i++) {
				if (this.OrderedRequests[call.command][i].id == call.request_id) {
					this.OrderedRequests[call.command].splice(i,1);
				}
			}
		}
	},
	
	buildParams : function(params,prefix) { //Takes a parameter object and turns it into a string
		var passed_params = '';
		if (!prefix) prefix = '';
		for (var i in params) {
			passed_params += prefix+i + '=' + UrlEncode(params[i]) + '&';
		}
		return passed_params.slice(0,passed_params.length -1);  
	},

	buildRecogniaCallUrl : function(the_call) { //Adds the needed parameters for api and component calls.
		var SessionId = GetProgrammingFieldValue('SessionId');
		var OutputFormat = GetProgrammingFieldValue('OutputFormat');
		return UrlEncode(the_call) + '&session_id=' + SessionId  + '&output=' + OutputFormat + '&random=' + (Math.round((Math.random()*9999)+1)); //Make sure we don't cache.   
	}
}) {
	
	AjaxRequest = (function(params) {

		var Self = this;
		
		//instance defaults vars
		var opt = {
			loadingMsg : _DEFAULT_LOADING_MSG,
			httpRequest : _DEFAULT_HTTP_REQUEST,
			masonAjaxComp : _DEFAULT_MASON_AJAX_MODULE,
			global_behavior : _DEFAULT_GLOBAL_BEHAVIOR,
			instance_behavior : _DEFAULT_INSTANCE_BEHAVIOR,
			timeout: _DEFAULT_TIMEOUT,
			async : true,
			rogue : false,
			id : null,
			loadingMsg_id : null,
			ordered : false,
			json : false,
			retry: 0,
			content: false,
			success: function() {},
			failure: function() {},
			aborted: function() {},
			finish: function() {}
		};

		//Object state vars

		var current = false;

		var process_queue = []; 
		var ajaxObj;
		var obj_id;
		var busy = false;

		
		var constructor  = function() {

			obj_id = Instances++;
			opt = getNewOpt(params);
			ajaxInit();

		}
		
		// PUBLIC FUNCTIONS

		this.api = function(api_command,api_params) { 
			
			var command;

			if (api_command) {
				command = api_command;
				var Parameters = '_api=' + buildRecogniaCallUrl(command);
			}  else {
				alert('AjaxRequest: No "api" parameter passed into api call'); return;
			}

			if (api_params) {

				if (api_params.command) {
					command = api_params.command;
				}
				if (api_params.url_params) {
					Parameters += '&' + api_params.url_params;
				} 
				
				if (api_params.xsl) Parameters += '&_xsl_id=' + api_params.xsl;
				if (api_params.xsl_params) Parameters += '&'+ buildParams(api_params.xsl_params,'_xslparam_');
				if (api_params.params) Parameters += '&' +buildParams(api_params.params);

				if (api_params.xml_tag) {
					Parameters += '&_xml_tag='+api_params.xml_tag;
				}

				if (api_params.utf8_encode) {
					Parameters += '&_utf8_encode_params=1';
				}
			} 
			ajaxCallManager({command: command, path: opt.masonAjaxComp, params:Parameters, opt: getNewOpt(api_params), ref: this});
			
			if (!opt.async) { 
				return ajaxObj.responseText; 
			}

		}

		this.comp = function(comp_call,comp_params) {
			
 			var command;
			if (comp_call) {
				command = comp_call;
				var Parameters = '_comp=' + buildRecogniaCallUrl(command);
			} else {
				alert('AjaxRequest: No "comp" parameter passed into comp call'); return;
			}

			if (comp_params) {
				
				if (comp_params.command) {
					command = comp_params.command;
				}

				if (comp_params.url_params) {Parameters += '&' + comp_params.url_params;}
				if (comp_params.params) Parameters += '&' +buildParams(comp_params.params);
			} 

			ajaxCallManager({command: command, path: opt.masonAjaxComp, params:Parameters, opt: getNewOpt(comp_params), ref: this});
			
			if (!opt.async) { 
				return ajaxObj.responseText; 
			}
		}

		this.url = function(url_call,url_params) {
			
			if (url_call) {
				
				if (url_call.indexOf('?') == -1) {			
					var command = url_call;
				} else {
					var command = url_call.substr(0,url_call.indexOf('?'));
				}
				
			} else {
				alert('AjaxRequest: No url specified for url call'); return;
			}

			if (url_params){

				if (url_params.params) {
					var params = buildParams(url_params.params);
				} else if (url_params.url_params) {
					var params = url_params;
				}

			}
			ajaxCallManager({command:command, path:command, params: params,opt: getNewOpt(url_params), ref: this});
			
			if (!opt.async) { 
				return ajaxObj.responseText; 
			}

		}
		
		this.SetDefaultOpt = function(new_opt) {
			opt = getNewOpt(new_opt);
		}

		this.abort = function() {
			if (isCallBeingMade()) {
				if (typeof(ajaxObj.abort) != 'undefined') {
					ajaxObj.abort();
				} else {
					ajaxObj = false;
				}
				deRegisterCall(current);
				cleanUp(current);
				aborted(current);
				busy = false;
				nextInQueue();
				
				if (current.opt.ordered) {
					RunNextDeferred(current.command);
				}
			}
		}

		this.abortCall = function(call) {
			if (isCallBeingMade() && (current.request_id == call.request_id)) {
				this.abort();
			} else {
				deRegisterCall(call);
				removeFromQueue(call);
				aborted(call);
				busy = false;
			}
		}
		
		this.MakingCall = function() {
			return isCallBeingMade();
		}

		this.IsBusy = function() {
			return busy;
		}

		
		//PRIVATE FUNCTIONS

		var success = function(call,response) {
			call.opt.success(response);
			call.opt.finish('success',response);
			call.retrying = false;
		}

		var failure = function(call,status) {
			if (call.opt.retry) {
				call.opt.retry--;
				call.retrying = true;
				if (call.opt.ordered) {
					PlaceFirstInOrder(call);
				}
				call.opt.finish('retry');
				setTimeout(function() {
					ajaxCall(call);
				},0);

			} else {
				call.retrying = false;
				call.opt.failure(status);
				call.opt.finish('failure',status);
			}
		}
		
		var aborted = function(call) {
			call.retrying = false;
			call.opt.aborted();
			call.opt.finish('aborted');

		}

		var getNewOpt = function(call_opt) {
			var new_opt = {};
			
			for (var i in opt) {
				if (typeof(i) != 'function') {
					new_opt[i] = opt[i];

					for (var j in call_opt)  {
						if (( i == j ) && (typeof(j) != 'function')) {	    
							new_opt[i] = call_opt[i];
						}
					}
				}
			}

			return new_opt;
			
		}

		var cleanUp = function(call) {
			ajaxObj.onreadystatechange = function() {};
			call.calling = false;
		}

		var registerCall = function(call) {
			if (typeof(call.request_id) == 'undefined') {
				call.request_id = RequestId++;
				AddActiveCommand(call,Self);
				if (call.opt.ordered) {
					PlaceInOrder(call);
				}
			}
			
		}
		
		var isCallBeingMade = function() {
			return ((typeof(current) == 'object') && current.calling)
		}

		var deRegisterCall = function(call) {
			RemoveActiveCommand(call);
			if (call.opt.ordered) {
				RemoveFromOrder(call);
			}
		}
		
		var setupNewCall = function(call) {
			
			if (call.opt.timeout && parseInt(call.opt.timeout)) {
				setTimeout(function() {
					Timeout(call);
				},call.opt.timeout*1000)
			}

			if (!call.opt.rogue) {AjaxRequest.GlobalInitialization();}
			if (call.opt.loadingMsg_id && (call.opt.loadingMsg != 'undefined')) {
				document.getElementById(call.opt.loadingMsg_id).innerHTML = call.opt.loadingMsg;
			} else if ((call.opt.loadingMsg != 'undefined') && call.opt.id) {
				document.getElementById(call.opt.id).innerHTML = call.opt.loadingMsg;
			}
			
			if (window.XMLHttpRequest) {                                  
				ajaxObj = new XMLHttpRequest();                                 
			}

			call.calling = true;
			current = call;

		}
		
		var clearQueue = function() {
			process_queue = [];
		}

		var addToQueue = function(call) {
			process_queue.push(call);
		}

		var addToNextQueue = function(call) {
			process_queue.splice(0,0,call);
		}
		
		var nextInQueue = function() {
			if (process_queue[0]) {
				var next_call = process_queue[0];
				process_queue.shift();
				ajaxCallManager(next_call);

			}
			
		}

		var removeFromQueue = function(call) {
			for (var i = 0; i< process_queue.length; i++) {
				if (process_queue[i].request_id = call.request_id) {
					process_queue.splice(i,1);
				}
			}
		}

		var ajaxInit = function() {

			if (window.XMLHttpRequest) {                                  
				ajaxObj = new XMLHttpRequest();                                 
			} else if (window.ActiveXObject) { // IE
				try {
					ajaxObj = new ActiveXObject("Msxml2.XMLHTTP");
				} catch (e) {
					try {
						ajaxObj = new ActiveXObject("Microsoft.XMLHTTP");
					} catch (e) {}
				}
			}  

			if (ajaxObj) {
				ajaxObj.onreadystatechange = function(){};
			}
		}


		var onComplete = function(call) {
			deRegisterCall(call);
			if (ajaxObj.status == 200) {
				if (call.opt.loadingMsg_id) {
					if (call.opt.id != call.opt.loadingMsg_id) {
						document.getElementById(call.opt.loadingMsg_id).innerHTML = '';
					}
	 			}
				if (call.opt.id) {
					document.getElementById(call.opt.id).innerHTML = ajaxObj.responseText;
				}
				
				
				if (call.opt.json) {
					var jsonObj = false;
					try { jsonObj = eval('('+ajaxObj.responseText+')');} catch(e) {}
					

					if (jsonObj && (typeof(jsonObj) == 'object')) {
						success(call,jsonObj);
					} else {
						failure(call,ajaxObj.status);
					}

				} else if (call.opt.content){
					if (ajaxObj.responseText != '') {
						success(call,ajaxObj.responseText);
					} else {
						failure(call,ajaxObj.status);
					}
				} else {
					success(call,ajaxObj.responseText);
				}

				if (!call.opt.rogue) {AjaxRequest.GlobalUnInitialization()};
			} else if (ajaxObj.status == 403) { //"Forbidden" well get sent if session is timedout
				window.location.reload(true);
			} else {
				failure(call,ajaxObj.status);
			}

			cleanUp(call);

			if (!call.retrying) {

				if (call.opt.ordered) {
					RunNextDeferred(call.command);
				}

				nextInQueue();
				busy = false;

			}
		}

		var onReadyStateChange = function(call) {
			if (ajaxObj.readyState == 4) {
				if (ajaxObj.status) {
					if (call.opt.ordered) {
						if (NextInOrder(call)) {
							ShiftOrder(call.command);
							onComplete(call);
						} else {
							DeferExecution(call,function() {onComplete(call)});
						}
					} else {
						onComplete(call);
					}
					
				}
			}			
			return;
		}


		var Timeout = function(call) {
			if (isCallBeingMade() && (current.request_id == call.request_id)) {
				if (typeof(ajaxObj.abort) != 'undefined') {
					ajaxObj.abort();
				} else {
					ajaxObj = false;
				}

				deRegisterCall(current);
				cleanUp(current);
				failure(current,408); //408 is timeout, should we be using http error codes for a set javascript timeout?
				busy = false;
				nextInQueue();
				
				if (current.opt.ordered) {
					RunNextDeferred(current.command);
				}	
			}
			
		}

		var ajaxCallManager = function(call) {

			busy = true;

			if (isCallBeingMade()) { // if there is a conflict in this instance of the object
				switch (call.opt.instance_behavior) { //check the set behavior
				case 'wait':
					registerCall(call);
					addToQueue(call);
					break;

				case 'force':
					clearQueue();
					registerCall(call);
					addToNextQueue(call);
					Self.abort();
					break;

				case 'cancel':
					aborted(call);
					busy = false;
					break;
				}
			} else {
				if (ActiveCommands(call.command)) { // Is AjaxRequest making any requests that have the same command?
					switch (call.opt.global_behavior) { //check the set global behavior
					case 'proceed': //proceed with the command anyways
						ajaxCall(call);	
						break;

					case 'force': //Abort all calls with the same command and call this one
						AjaxRequest.AbortAllCallsWithCommand(call.command);
						ajaxCall(call);
						break;

					case 'cancel': // Cancel this call
						aborted(call);
						busy = false;
						return;
					}
				} else { // This will run if there is no conflict detected
					ajaxCall(call);
				}

				registerCall(call);
			}

		}

		var ajaxCall = function(call) {

			setupNewCall(call);
			if (ajaxObj) {
				if (call.opt.httpRequest == "GET") {
					var url = call.path;
					if (call.params) {
						if (call.path.indexOf('?') == -1) {
							url += '?'+call.params;
						} else if (call.path.chartAt(call.path.length-1) == '&') {
							url += call.params;
						} else {
							url += '&' + call.params;
						}
					} 
					ajaxObj.open("GET",url,call.opt.async); 
					ajaxObj.onreadystatechange = function() {onReadyStateChange(call)}; 
					ajaxObj.send(null)
				} else {
					ajaxObj.open("POST", call.path, call.opt.async);
					ajaxObj.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
					ajaxObj.setRequestHeader("Content-length", call.params.length);
					ajaxObj.setRequestHeader("Connection", "close");
					ajaxObj.onreadystatechange = function() {onReadyStateChange(call)}; 
					ajaxObj.send(call.params);
				}
			}    
		}
		
		constructor();
	});

	//Public Static Methods
	AjaxRequest.CommandBeingMade = function (command) {
		if  (ActiveCommands(command) > 0) {
			return true;
		} 
		return false;
	}

	AjaxRequest.AbortAllCallsWithCommand = function(command) {

		var aborted = false;

		
		if (ActiveRequests[command] && ActiveRequests[command].length) {			

			if (OrderedRequests[command] && OrderedRequests[command].length) {
				delete OrderedRequests[command];
			}

			var len =  ActiveRequests[command].length;
			for (var i=0;i<len;i++) {
				ActiveRequests[command][0].ref.abortCall(ActiveRequests[command][0]);
				aborted = true;
			}
			delete ActiveRequests[command];
		}

		return aborted;
	}

	//GlobalInitialization will get called before any ajax call.
	AjaxRequest.GlobalInitialization = function () {}
	//GlobalUnInitialization will get called after any ajax call with a status of '200' (success)
	AjaxRequest.GlobalUnInitialization = function() {}


	AjaxRequest.Caller = function(global_params) {
		
		var instance;
		var AjaxRequests = [];

		var constructor  = function() {
			AjaxRequests.push(new AjaxRequest(global_params));
		}

		this.call = function(func,command,params) {

			if (AjaxRequests[0].IsBusy()) {
				var call_made = false;
				for (var i =0; i < AjaxRequests.length;i++) {
					if (!AjaxRequests[i].IsBusy()) {
						AjaxRequests[i][func](command,params);
						call_made = true;
						break;
					}
				}

				if (!call_made) {
					AjaxRequests.push(new AjaxRequest(global_params));
					AjaxRequests[AjaxRequests.length-1][func](command,params);
				}
				
			} else {
				AjaxRequests[0][func](command,params);
			}
		}
		constructor();
	}


}
