var G_kjax_serverURL = '/_ajax/handle_ajax.php';
var G_kjax_debuglevel = 0;	// 0=silent, 1=alert failures only, 2=alert incoming traffic, 3=alert all traffic
var G_kjax_lengthkeyaction = 'confirm';	// 'confirm','rename','drop' or 'ignore' 


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

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


function kjax_debug(level, msg) {
	if (level <= G_kjax_debuglevel) {
        //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') { 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 == '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
	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.
}

