var G_kjax_serverURL = '/_ajax/handle_ajax.php';
var G_kjax_debuglevel = 1;		// 0=silent, 1=alert failures only, 2=alert incoming traffic, 3=alert all traffic
var G_kjax_debug_firebug = true;	// log debug messages to firebug console if available
var G_kjax_lengthkeyaction = 'confirm';	// 'confirm','rename','drop' or 'ignore' 
var G_kjax_encstrings = true;		// whether to base64 encode strings for submitting to the server (new to Kjax 2.2). Safer to do but it can make debugging more difficult

/**** You shouldn't need to change anything below here ****/
// Version 2.2

var G_kjax_httpOK = 4;
var G_kjax_contextSN = new Array();


/**
*
*  Base64 encode / decode class, thanks to http://www.webtoolkit.info/
*
**/
 
var kjax_Base64 = {
 
	// private property
	_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
 
	// public method for encoding
	encode : function (input) {
		var output = "";
		var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
		var i = 0;
 
		input = kjax_Base64._utf8_encode(input);
 
		while (i < input.length) {
 
			chr1 = input.charCodeAt(i++);
			chr2 = input.charCodeAt(i++);
			chr3 = input.charCodeAt(i++);
 
			enc1 = chr1 >> 2;
			enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
			enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
			enc4 = chr3 & 63;
 
			if (isNaN(chr2)) {
				enc3 = enc4 = 64;
			} else if (isNaN(chr3)) {
				enc4 = 64;
			}
 
			output = output +
			this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
			this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
 
		}
 
		return output;
	},
 
	// public method for decoding
	decode : function (input) {
		var output = "";
		var chr1, chr2, chr3;
		var enc1, enc2, enc3, enc4;
		var i = 0;
 
		input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
 
		while (i < input.length) {
 
			enc1 = this._keyStr.indexOf(input.charAt(i++));
			enc2 = this._keyStr.indexOf(input.charAt(i++));
			enc3 = this._keyStr.indexOf(input.charAt(i++));
			enc4 = this._keyStr.indexOf(input.charAt(i++));
 
			chr1 = (enc1 << 2) | (enc2 >> 4);
			chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
			chr3 = ((enc3 & 3) << 6) | enc4;
 
			output = output + String.fromCharCode(chr1);
 
			if (enc3 != 64) {
				output = output + String.fromCharCode(chr2);
			}
			if (enc4 != 64) {
				output = output + String.fromCharCode(chr3);
			}
 
		}
 
		output = kjax_Base64._utf8_decode(output);
 
		return output;
 
	},
 
	// private method for UTF-8 encoding
	_utf8_encode : function (string) {
		string = string.replace(/\r\n/g,"\n");
		var utftext = "";
 
		for (var n = 0; n < string.length; n++) {
 
			var c = string.charCodeAt(n);
 
			if (c < 128) {
				utftext += String.fromCharCode(c);
			}
			else if((c > 127) && (c < 2048)) {
				utftext += String.fromCharCode((c >> 6) | 192);
				utftext += String.fromCharCode((c & 63) | 128);
			}
			else {
				utftext += String.fromCharCode((c >> 12) | 224);
				utftext += String.fromCharCode(((c >> 6) & 63) | 128);
				utftext += String.fromCharCode((c & 63) | 128);
			}
 
		}
 
		return utftext;
	},
 
	// private method for UTF-8 decoding
	_utf8_decode : function (utftext) {
		var string = "";
		var i = 0;
		var c = c1 = c2 = 0;
 
		while ( i < utftext.length ) {
 
			c = utftext.charCodeAt(i);
 
			if (c < 128) {
				string += String.fromCharCode(c);
				i++;
			}
			else if((c > 191) && (c < 224)) {
				c2 = utftext.charCodeAt(i+1);
				string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
				i += 2;
			}
			else {
				c2 = utftext.charCodeAt(i+1);
				c3 = utftext.charCodeAt(i+2);
				string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
				i += 3;
			}
 
		}
 
		return string;
	}
 
}


function kjax_debug(level, msg) {
	if (level > G_kjax_debuglevel) { return; }
	if (!G_kjax_debug_firebug) {
		alert('KJAX DEBUG:\n'+msg);
		return;
	}
        //support firebug if it's available... otherwise just use alert().
       	//(firebug is better because it doesn't interrupt program flow the way alert() does)
       	try {
		var logger;
		switch (level) {     //firebug supports nice icons for different error levels.
			case 1 : logger=console.error; break;
			case 2 : logger=console.info; break;
			default: logger=console.log; break;
		}
		console.log('KJAX DEBUG:\n'+msg);
	}
	catch(e) {
		alert('KJAX DEBUG:\n'+msg);
	}
}


function kjax_serialize(item) {
	// works with strings and arrays/objects (of a sort)
	if (item === undefined) { return 'U'; }
	if (item === null) { return 'u'; }
	var type = typeof(item);
	if (type == 'boolean') { return 's1:'+(item?'1':'0'); }
	if (type == 'number') { item = item.toString(); type = 'string'; }
	if (type == 'string') {
		if (G_kjax_encstrings) {
			var tmp = kjax_Base64.encode(item); return 'B'+tmp.length+':'+tmp;
		} else {
			return 's'+item.length+':'+item;
		}
	}
	//if (type == 'string' && G_kjax_encstrings) { var tmp = kjax_Base64.encode(item); return 'B'+tmp.length+':'+tmp; };
	//if (type == 'string') { return 's'+item.length+':'+item; };
	if (type == 'object') {
		var assoc = 0; var test = 0;
		for (var key in item) {
			if (key !== test) { assoc = 1; break }
		}
		if (assoc) {
			var tmp = new Array();
			var numitems = 0;
			for (var key in item) {
				numitems++;
				tmp.push(kjax_serialize(key));
				tmp.push(kjax_serialize(item[key]));
			}
			return 'a'+numitems+':'+tmp.join('');
		} else {
			var tmp = 'l'+item.length+':';
			for (var i = 0; i < item.length; i++) {
				tmp += kjax_serialize(item[i]);
			}
			return tmp;
		}
	}
}

function kjax_lengthkey_action() {
	if (G_kjax_lengthkeyaction === 'ignore') { return 'length'; } // on you head be it
	if (G_kjax_lengthkeyaction === 'confirm') {
		if (confirm("KJAX alert:'length' key found in return data. Click OK to set, Cancel to skip.")) {
			return 'length';
		}
		return null;
	}
	if (G_kjax_lengthkeyaction === 'rename') { return 'Length'; }
	if (G_kjax_lengthkeyaction === 'drop') { return null; }
	alert("Illegal value in G_kjax_lengthkeyaction; 'length' parameter will be skipped");
	return null;
}


function kjax_uns_sub(serdata, offset) {
	var tmp = serdata.slice(offset,offset+10);
	var datatype = tmp.charAt(0);
	if (datatype == 'u') { return new Array(null,offset+1); }
	if (datatype == 'U') { return new Array(undefined,offset+1); }

	// else we have \w\d+:
	var colon = tmp.indexOf(':');
	if (!colon) { return new Array(undefined,serdata.length); } // something has gone horribly wrong
	var len = parseInt(tmp.slice(1,colon));
	offset += colon + 1;	// move offset just past the colon

	if (datatype == 's') {
		var strend = offset+len;
		return new Array(serdata.slice(offset,strend), strend); 
	}

	if (datatype == 'B') {
		var strend = offset+len;
		return new Array(kjax_Base64.decode(serdata.slice(offset,strend)), strend); 
	}

	if (datatype == 'l') {
		var res = new Array();
		for (var n = 0; n < len; n++) {
			var tmpval = kjax_uns_sub(serdata, offset);
			res.push(tmpval[0]);
			offset = tmpval[1];
		}
		return new Array(res, offset);
	}

	if (datatype == 'a') {
		var res = new Array();
		for (var n = 0; n < len; n++) {
			var tmpkey = kjax_uns_sub(serdata, offset);
			offset = tmpkey[1];
			var tmpval = kjax_uns_sub(serdata, offset);
			offset = tmpval[1];
			if (tmpkey[0] === 'length') {
				tmpkey[0] = kjax_lengthkey_action();
				if (tmpkey[0] === null) { continue; }
			}
			res[tmpkey[0]] = tmpval[0];
		}
		return new Array(res, offset);
	}
}

function kjax_unserialize(serdata) { return kjax_uns_sub(serdata,0)[0]; }


function kjax_getHTTPObject() {
	var C=null;
	try {C=new ActiveXObject("Msxml2.XMLHTTP")}
	catch(e){
		try{C=new ActiveXObject("Microsoft.XMLHTTP")}
		catch(sc){C=null}
	}
	if(!C&&typeof XMLHttpRequest!="undefined"){C=new XMLHttpRequest()}
	return C
}

function kjax_urlenc(str) {
	return escape(str).replace(/\+/g,'%2B').replace(/%20/g,'+');
}

function kjax_urldec(str) {
 	return unescape(str.replace(/\+/g,'%20').replace(/%2B/g,'+'));
}

function kjax_urlenc_arr(arr) {
	var tmparr = new Array();
	for (var key in arr) {
		tmparr.push(kjax_urlenc(key)+'='+kjax_urlenc(arr[key]));
	}
	return tmparr.join('&');
}

// function kjax_urldec_arr(str) {
// 	var arr = new Array();
// 	var tmparr = str.split('&');
// 	for (var i in tmparr) {
// 		var pair = tmparr[i].split('=');
// 		arr[kjax_urldec(pair[0])] = kjax_urldec(pair[1]);
// 	}
// 	return arr;
// }


function kjax_context_update(context) {
	// generate the next serial number in the given context
	var key = 'x'+context;	// in case we want a context called 'length'
	if (typeof(G_kjax_contextSN[key]) === 'number') {
		G_kjax_contextSN[key]++;
	} else {
		G_kjax_contextSN[key] = 1;
	}
	return G_kjax_contextSN[key];
}


function kjax_context_read(context) {
	var key = 'x'+context; return (G_kjax_contextSN[key] === 'number' ? G_kjax_contextSN[key] : 0)
}


function kjax_handleresponse(reqobj) {
	if (reqobj['httpobj'].readyState == G_kjax_httpOK) {
		if (reqobj['serial'] && reqobj['serial'] < kjax_context_read(reqobj['context'])) {
			return;	// silently discard out-of-sequence response
		}
		var stat = reqobj['httpobj'].status;
		if (stat >= 200 && stat <= 299) {
			var tmp = reqobj['httpobj'].responseText;
			kjax_debug(2,'RECEIVED RESPONSE (length='+tmp.length+'):\n'+tmp);
			var resp_arr; var whichfunc; var failure;
			var extended_diags = 0;
			try {
				failure = 'unserializing server response';
				resp_arr = kjax_unserialize(tmp);
				failure = "checking 'error' element in server response";
				whichfunc = (resp_arr.error.length > 0 ? 'fail' : 'succ');
                if(typeof(reqobj[whichfunc])=='string') {
                    callfunc=window[reqobj[whichfunc]];
                } else {
                    callfunc=reqobj[whichfunc];
                }
                if(callfunc) {
                    kjax_debug(2,"calling "+callfunc+'()');
                    failure = 'calling '+callfunc+'()';
                    extended_diags = 1;
                    callfunc(resp_arr, reqobj['locargs']);
                } else {
                    kjax_debug(1, whichfunc+' function '+callfunc+'() not found');
                }
			}
			catch (e) {
				var diags = 'Fail in '+failure+'\n';
				if (extended_diags) {
					for (var key in e) { diags += 'Err['+key+']='+e[key]+'\n'; }
				}
				kjax_debug(1, diags+'\nRAW DATA:\n'+tmp);
			 }
		} else {
			kjax_debug(1, 'Call failed\n\nHTTP Response='+stat+' '+reqobj['httpobj'].statusText);
		}
	}
}

function kjax(func, args, on_success, on_failure, local_args, context) {
	var req = new Array();
	req['func'] = func;
	req['args'] = kjax_serialize(args);
	reqstr = kjax_urlenc_arr(req);
	// create pretty unique URL to get round any silly proxy caching
    //RCT: commenting out the parameters that were inplace to stop caching.
	var url = G_kjax_serverURL;//+"?re="+Math.round(Math.random()*9999)+'&ts='+new Date().getTime();

	kjax_debug(3, 'SENDING DATA:\nURL='+url+'\nDATA='+reqstr);
	var reqobj = new Array;
	if (arguments.length>5 && context.length) {
		reqobj['serial'] = kjax_context_update(context); reqobj['context'] = context;
	} else {
		reqobj['serial'] = 0;
	}
	reqobj['succ'] = on_success;
	reqobj['fail'] = on_failure;
	reqobj['serialnum'] = (arguments.length>5 && context.length ? kjax_context_update(context) : 0);
	reqobj['locargs'] = local_args;
	reqobj['httpobj'] = kjax_getHTTPObject();
	reqobj['httpobj'].open('POST', url, true);
	reqobj['httpobj'].setRequestHeader('Content-Type','application/x-www-form-urlencoded; charset=UTF-8');
	reqobj['httpobj'].onreadystatechange = function() { kjax_handleresponse(reqobj); };
	reqobj['httpobj'].send(reqstr);

	// Note: the context argument is optional. Its purpose is to guard against server
	// responses to duplicate (or similar enough) requests coming back out of sequence
	// (which might happen if the server or network are experiencing delays).
	// 
	// Normally, if you make several kjax() requests in quick succession, you could end
	// up with several outstanding requests. Under these circumstances there is no
	// guarantee that the responses will come back in the same order. KJAX will action
	// each response as it is received, which may not be what you want in your application.
	// 
	// To prevent this from happening, supplying a context string ensures that only the
	// response to the most recent request (with the same context string) will be actioned.
	// Any delayed responses from earlier requests with the same context string will be
	// silently discarded.
}


