// GLOBALS
var ajax_failed_requests = 0;			// a placeholder to count the number of failed requests to the server

/**
* Constructor for Xportal's ajax object
* This only needs to be instantiated once in your script and you'll be able to re-use the same object for all your requests
*@param string The URL of the page you're going to connect to on the server, by default ajax_server.php
*@param string The function that will be called when ajax responses come back from the server, by default ajax handles this for you
*/
function XMLHTTP(server_url, readyStateFunction)
{
	this.version = '0.7.0';
	this.server_url = server_url; 		// the SERVER page URL to connect to IE ajax_server.php
	this.async = true;					// whether we're in syncronous mode or async (default)
	this.debug=0;						// debug turned off by default, to see your request/response do ajaxObj.debug=1
	this.throttle=1;					// this enables throttling by default so all your requests are in the proper order
	this.method = "POST";				// by default all requests are sent via POST to override just use ajaxObj.method="GET"; before your call
	this.req = null;					// the xmlhttprequest variable, starts off as null
	this.headers = new Array();			// array of optional headers you may pass in
	this.callBack = '';					// the callback function, when the ajax request is sent, this is the function that will be called
	this.format = "JSON";				// by default JSON encoding is the expected format, to override: ajaxObj.format = "XML"; or ajaxObj.format="TEXT";
	this.net_down_func = this.down;		// the function that will be called if ajax cannot make a request to the server (server down)
	this.abort_timeout = 50000;			// the number of seconds to wait before calling network down function, set to -1  disable feature, defaults to 5 seconds
	this.failed_threshold = 200;			// the number of failed requests before ajax is disabled -> prevents repeated error notifications
	this.ignoreCall = 0;				// set this to 1 if you have a polling object and you don't want to see debug msgs every second or activity indicators
	this.stopRequest = 0;				// set this to one in your client code if you want to make sure your callback functions completes before making another ajax call in the queue
	this.readyStateFunction = (readyStateFunction) ? readyStateFunction : this.responseHandler;
	
	/*-- SYSTEM PROPERTIES - no need to change the properites below --*/
	this.debugID = 0;					// used for showing expandable visual debug data
	this.errors = new Array();			// array of errors generated
	this.queue = new Array();			// the queue that will handle the throttling requests to keep your stuff in sync
	this.queue_in_process = 0;			// the current index for the throttling array
	this.currentCallIgnore=0;			// system uses this to see what calls to ignore from the queue
	this.readySateTimer = '';			// the timer used to check the readystate of the xmlhttprequest to prevent memory leaks
}

/**
* Method to create an XMLHTTP object
*@access private
*/
XMLHTTP.prototype.getXMLHTTP = function()
{
	// moz XMLHTTPRequest object
	if (window.XMLHttpRequest) {
		this.req = new XMLHttpRequest();
	}
	// IE/Windows ActiveX version
	else if (window.ActiveXObject){
		this.req = new ActiveXObject("Microsoft.XMLHTTP");
	} else {
		return false;
	}
	return this.req;
}

/**
* Main API method to use for AJAX requests
* example: ajaxObj.call("action=loadComments&id=1", myCallBackFunction);
*@access public
*@param string A url encoded string of data to send to the server
*@param string A callback function that the server will launch when the response is generated
*@param string Used by the response handler function to send back throttled requests, you won't need to worry about this param
*/
XMLHTTP.prototype.call = function(queryVars, userCallback, queue_request)
{
		var currentVars;
		var callback;
		this.fullUrl = '';
	
		if(this.throttle == 1 && queue_request != 'queue' || this.stopRequest == 1) {		// throttling keeps your requests in sync, so things aren't out of order
			this.add2Queue(queryVars, userCallback);	
		}
		if(this.queue_in_process == 0)
		{
			// get XMLHTTPRequest Object
			if(!this.getXMLHTTP())
			{
				return false;
			}
			
			if(this.throttle == 1) {
				this.queue_in_process = 1;
				var currentCall = this.queue.shift();	// get the current call to make
				currentVars = currentCall.queryVars;
				callback = currentCall.userCallback;
				this.format = currentCall.format;
				this.method = currentCall.method;
				this.abort_timeout = currentCall.abortTimeout;
				this.currentCallIgnore = currentCall.ignoreCall;
				this.async = currentCall.async;
			} else {
				currentVars = queryVars;
				callback = userCallback;
				var ignoreCall=0;
				
			}
			this.callBack = callback;
		
			// check for JSON encoding
			if(this.format != 'JSON') {
				currentVars = currentVars+'&json=false';
			}

			// if get is used, append the query variables to the url string 
			this.full_url = (this.method == "POST") ? this.server_url : this.server_url + '?'+ currentVars;
			
			this.Loading();
			// open connection
			this.req.open(this.method, this.full_url, this.async);
			
			// set any optional headers
			if(this.headers){
				for(var i in this.headers) {
					if(i != '' && (this.headers[i] instanceof String)) {
						try {
							this.req.setRequestHeader( i, this.headers[i]);
						} catch(e) {}
					}
				}
			}
			// START TIMER TO ABORT REQUEST
			if(this.abort_timeout != -1) {
				this.end_timer = setInterval('ajaxObj.endCall()', this.abort_timeout);
			}
			// send request
			if(this.method == 'POST') {
				this.req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
				this.request = currentVars;
				this.req.send(currentVars);
			} else {
				this.req.send(null);
			}
			
			// START POLLING FOR READYSTATECHANGE
			if(this.readyStateFunction) {
				this.readyStateTimer = window.setInterval(
							function(){
								if(ajaxObj.req && ajaxObj.req.readyState == 4){
									window.clearInterval(ajaxObj.readyStateTimer);
									ajaxObj.readyStateTimer = null;
									
									// call is ready, respond
									ajaxObj.responseHandler();
								}
							}
						,50); // poll every 50 milliseconds for ready state
			}
			
		}

}

/**
* Default method for parsing the response from the server. It will try to eval the obj.method_to_call property and pass the native JS object
*/
XMLHTTP.prototype.responseHandler = function()
{	
	if(ajaxObj.req)
	{
		try
		{
		// only if req shows "complete"
		if (ajaxObj.req.readyState == 4) {
			// only if "OK"
			ajaxObj.LoadComplete();	
			if (ajaxObj.req.status && ajaxObj.req.status == 200) {
				if(ajaxObj.req.responseText.indexOf('ajax_msg_failed') != -1) {
					if(ajaxObj.req.responseText.indexOf('notauth') != -1) { 
						if(ajaxObj.abort_timeout != -1) { clearInterval(ajaxObj.end_timer); }
						ajaxObj.callBack('notauthorized'); 
					} else {
						ajaxObj.callBack(false); 
					}
				} else {
					// clear out network down timer
					if(ajaxObj.abort_timeout != -1) {
						clearInterval(ajaxObj.end_timer);
					}
					if(ajaxObj.format == "JSON") {
					try {
						var myObject = JSON.parse(ajaxObj.req.responseText);
					// callback function we passed to the server to process the results
					
					if(document.getElementById(ajaxObj.callBack)) {
						document.getElementById(ajaxObj.callBack).innerHTML = myObject;
					} else {
						ajaxObj.callBack(myObject);
					}
					
					} catch(e) {
							alert('an error occurred in your response function, not ajax related. Error Name: ' + e.name + '  Message:' + e.message);
							if(document.getElementById(ajaxObj.callBack)) { } else {
								ajaxObj.callBack(false);
							}
					}
					} else if(ajaxObj.format == "XML") {
						// send the raw xml data to the callback function
						ajaxObj.callBack(ajaxObj.req.responseXML);	
					} else {
						if(document.getElementById(ajaxObj.callBack)) {
						document.getElementById(ajaxObj.callBack).innerHTML = ajaxObj.req.responseText;
						} else {
							ajaxObj.callBack(ajaxObj.req.responseText);
						}

					}
				}
				
				ajax_failed_requests = 0; // reset failed requests back to 0
			} else {
				// server is came back with a bad status
				ajaxObj.endCall();	
			}
				
			// reset the method, format, etc back to class defaults
			ajaxObj.restoreDefaults();
			
			// reset our queue and call
			ajaxObj.queue_in_process = 0;
			ajaxObj.req = null;
			if(ajaxObj.queue.length > 0) {
				
				ajaxObj.call('','','queue');
			}
		}
		} catch(e) { 
		/*network is down*/}
	}
	
}

/**
* This method will allow you to create a "command queue" so ajax requests are sent in order they were fired. 
* You will be able to keep your request/responses in order they were sent
*/
XMLHTTP.prototype.add2Queue = function(queryVars, userCallback)
{
	var addAjax = new Array();
	addAjax['queryVars'] = queryVars;
	addAjax['userCallback'] = userCallback;
	addAjax['ignoreCall'] = this.ignoreCall;
	addAjax['abortTimeout'] = this.abort_timeout;
	addAjax['format'] = this.format;
	addAjax['method'] = this.method;
	addAjax['async'] = this.async;
	
	this.ignoreCall=0;	// reset back to original state
	this.queue.push(addAjax);
}


/**
* Method called after callback function is called to return the class to a default state
*/
XMLHTTP.prototype.restoreDefaults = function()
{
	this.method = "POST";
	this.format = "JSON";	
	this.callback = "";
	this.abort_timeout = 5000;
	this.failed_threshold = 3;	
	this.async = true;
	
}

/**
* This method will check the request call after x number of seconds to see if the network call is still hanging
* You will want to use this when your server might be down and after 5 seconds you'll want to call a function that lets the 
* user know you're having server issues
*/
XMLHTTP.prototype.endCall = function()
{
	ajaxObj.LoadComplete();
	try{
		ajaxObj.net_down_func();
		ajaxObj.req.abort();
		ajaxObj.req = null;
		clearInterval(ajaxObj.end_timer);
		clearInterval(ajaxObj.readyStateTimer);
		// increase failed requests variable
		ajax_failed_requests++;
		// reset our queue and call
		ajaxObj.queue_in_process = 0;
		if(ajaxObj.queue.length > 0) {
			
			ajaxObj.call('','','queue');
		}
		
	} catch(e) {
		// server is completely down, call netdown function
		//clearInterval(ajaxObj.end_timer);
		//ajaxObj.net_down_func('disable');
		//ajaxObj.net_down_func();
	}
	ajaxObj.req = null;
}


/**
* Default method that will be called when ajax experiences a network down situation, or is disabled
* This method will pop up a div alerting the user of a general network error
* You should define your own method to properly fit in with your page layout
*/
XMLHTTP.prototype.down = function(status)
{
	var notif_div = '<div id="ajax_notification" style="text-align:center;padding:20px;position:absolute;top:100px;left:100px;width:300px;border:thin solid black;background-color:#F8F021;">';
	notif_div 	+= 	'<span id="ajax_notif_msg"> MSGHERE </span> <br><br><input type="button" value="Ok" onclick="document.getElementById(\'ajax_notification\').style.display=\'none\';"></div>';
	if(status == 'disable') {
		var notif = 'A network issue has disabled network connections for this page. Please reload this page or contact the site administrator';
	} else {
		var notif = 'A network issue has occurred which canceled your last request';
	}
	// lets try and find an existing error message or use the current one in the DOM
	try{
		if(document.getElementById('ajax_notification')) {
			document.getElementById('ajax_notification').style.display='block';
		} else {
			var new_div = document.createElement('div');
			new_div.innerHTML = notif_div;
			document.body.appendChild(new_div);
		}
		document.getElementById('ajax_notif_msg').innerHTML = notif;
	} catch(e) { 
		alert('Network Unavailable: Please re-load page or contact the site administrator');
	}
}

/**
* This method is a wrapper for when you have a callback funciton that has to finish executing before any other ajax calls are made
* You may be in this situation when ajax calls rely on each other IE don't update this data until I finish processing the other data
*/
XMLHTTP.prototype.restart = function() 
{
	ajaxObj.stopRequest=0;
	ajaxObj.call('','','queue');
}

XMLHTTP.prototype.Loading = function()
{
	if(!document.getElementById("services_loading")){
		// create element
		var sload = document.createElement('div');
		sload.id = 'services_loading';
		sload.style.border = "thin solid white";
		sload.style.backgroundColor = "#CC0000";
		sload.style.color = "#FFFFFF";
		sload.style.padding = "2px";
		sload.style.position='absolute';
		sload.style.top = 0;
		sload.style.right=0;
		sload.style.width="62px";
		
		sload.style.overflow="auto";
		sload.innerHTML += 'Loading...';
		
		if(document.body) {
			document.body.appendChild(sload);
		} else {
			document.lastChild.appendChild(sload);
		}
	}
}
XMLHTTP.prototype.LoadComplete = function()
{
		if(document.getElementById('services_loading')){
			var sload = document.getElementById('services_loading');
			sload.parentNode.removeChild(sload);
		}
}

/****************************************************/
// INCLUDE JSON.ORG's JSON CLIENT SIDE SERIALIZER
/*
Copyright (c) 2005 JSON.org
*/

/*
    The global object JSON contains two methods.

    JSON.stringify(value) takes a JavaScript value and produces a JSON text.
    The value must not be cyclical.

    JSON.parse(text) takes a JSON text and produces a JavaScript value. It will
    return false if there is an error.
*/
var JSON = function () {
    var m = {
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
        s = {
            'boolean': function (x) {
                return String(x);
            },
            number: function (x) {
                return isFinite(x) ? String(x) : 'null';
            },
            string: function (x) {
                if (/["\\\x00-\x1f]/.test(x)) {
                    x = x.replace(/([\x00-\x1f\\"])/g, function(a, b) {
                        var c = m[b];
                        if (c) {
                            return c;
                        }
                        c = b.charCodeAt();
                        return '\\u00' +
                            Math.floor(c / 16).toString(16) +
                            (c % 16).toString(16);
                    });
                }
                return '"' + x + '"';
            },
            object: function (x) {
                if (x) {
                    var a = [], b, f, i, l, v;
                    if (x instanceof Array) {
                        a[0] = '[';
                        l = x.length;
                        for (i = 0; i < l; i += 1) {
                            v = x[i];
                            f = s[typeof v];
                            if (f) {
                                v = f(v);
                                if (typeof v == 'string') {
                                    if (b) {
                                        a[a.length] = ',';
                                    }
                                    a[a.length] = v;
                                    b = true;
                                }
                            }
                        }
                        a[a.length] = ']';
                    } else if (x instanceof Object) {
                        a[0] = '{';
                        for (i in x) {
                            v = x[i];
                            f = s[typeof v];
                            if (f) {
                                v = f(v);
                                if (typeof v == 'string') {
                                    if (b) {
                                        a[a.length] = ',';
                                    }
                                    a.push(s.string(i), ':', v);
                                    b = true;
                                }
                            }
                        }
                        a[a.length] = '}';
                    } else {
                        return;
                    }
                    return a.join('');
                }
                return 'null';
            }
        };
    return {
        copyright: '(c)2005 JSON.org',
        license: 'http://www.JSON.org/license.html',
/*
    Stringify a JavaScript value, producing a JSON text.
*/
        stringify: function (v) {
            var f = s[typeof v];
            if (f) {
                v = f(v);
                if (typeof v == 'string') {
                    return v;
                }
            }
            return null;
        },
/*
    Parse a JSON text, producing a JavaScript value.
    It returns false if there is a syntax error.
*/
        parse: function (text) {
            try {
                return !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
                        text.replace(/"(\\.|[^"\\])*"/g, ''))) &&
                    eval('(' + text + ')');
            } catch (e) {
                return false;
            }
        }
    };
}();
