/**
 * Copyright (c) 2008-2009 Transparent Language, Inc. All rights reserved.
 */
/*
	JSON marshalling 
*/
function asJSON(obj) {
	var buff = [];
		encodeJSON(obj,buff);
	return buff.join("");
}

function encodeJSON(obj,buff) {
	if(obj == null) {
		buff.push("null");
	} else {
		switch(typeof obj) {
			case "number" :
				buff.push(obj);
			break;
			case "string" :
				buff.push("\"" + obj.replace(/\\/g,"\\\\").replace(/\"/g,"\\\"") + "\"");
			break;
			case "boolean" :
				buff.push(obj);
			break;
			case "object" :
				if(obj.reverse && obj.splice) {
					var first = true;
					buff.push("[");
					for(var i = 0; i < obj.length; i++) {
						if(!first) { buff.push(","); } else { first=false; }
						encodeJSON(obj[i],buff);
					}
					buff.push("]");
				} else {
					var first = true;
					buff.push("{");
					for(var idx in obj) {
						if(typeof obj[idx] != "function") {
							if(!first) { buff.push(","); } else { first=false; }
							buff.push("\"" + idx + "\"");
							buff.push(" : ");
							encodeJSON(obj[idx],buff);
						}
					}
					buff.push("}");
				}
			break;
		}
	}
}

/*
	Query string marshalling 
*/
asQueryString = function(obj) {
	var buff = [];
	var first = true;
	for(var idx in obj) {
		var value = obj[idx];
		if(typeof value == "string" || typeof value == "number" || typeof value == "boolean") {
			if(first) {
				first=false;
			} else {
				buff.push("&");
			}
			buff.push(encodeURIComponent(idx));
			buff.push("=");
			buff.push(encodeURIComponent(obj[idx]));		
		}
	}
	return buff.join("");
}

/*
	AjaxRpc
*/
function AjaxRpc(urlPrefix) {
	this.urlPrefix=urlPrefix ? urlPrefix : "";
	this.lastRequestTime=0;
	this.appendToUrl=false;
}

AjaxRpc.prototype.onError = function(requestURI,requestBody,xmlrq,error) {
	alert("Unexpected error while communicating with server !");
}

AjaxRpc.prototype.onBeforeCall = function(xmlrq) {}

AjaxRpc.prototype.onAfterCall = function(xmlrq) {}

AjaxRpc.prototype.buildXmlHttpRequest = function(method,url,async,contentType) {
	var request;
	if (window.XMLHttpRequest) {
		request = new XMLHttpRequest();
	} else if (window.ActiveXObject) {
		request = new ActiveXObject("Microsoft.XMLHTTP");
	} else {
		window.alert("No XMLHttpRequest implementation found !")
	}
	request.open(method,url,async);

	if(method == "POST") {
		request.setRequestHeader("Content-Type",(contentType ? contentType : "text/javascript"));
	}
	return request;
}

AjaxRpc.prototype.decodeResponse = function(xmlrq) {
	if(xmlrq.getResponseHeader("Content-Type").indexOf("text/javascript") != -1) {
		return eval("(" + xmlrq.responseText + ")");
	} else {
		return xmlrq.responseText;
	}
}

AjaxRpc.prototype.send = function(method,url,body,onError,contentType) {
	var request = this.buildXmlHttpRequest(method,this.urlPrefix + url,false,contentType);
	var requestURI = method + "/" + url;
	var error;

	this.lastRequestTime=(new Date()).getTime();
	this.onBeforeCall(request);
		request.send(body);
	this.onAfterCall(request);
	if((request.readyState != 4) || (request.status != 200)) {
		if(typeof onError != 'function' || !onError(response)) {
			error = 'Error while communicating with server: ' + request.status + "/" + request.statusText;
			this.onError(requestURI,body,request,error);
			throw error;
		}
	} else {
		var response;
		try {
			response = this.decodeResponse(request);
		} catch(exc) {
			error = "Error while decoding result: " + exc.message;
			this.onError(requestURI,body,request,error);
			throw error;
		}
		if(typeof response == 'object' && response != null && "isException" in response) {
			if(typeof onError != 'function' || !onError(response)) {
				this.onError(requestURI,body,request,response);
				throw response;
			}
		} else {
			return this.decodeResponse(request);
		}
	}
}

AjaxRpc.prototype.sendAsync = function(method,url,body,callback,onError,contentType) {
	var request = this.buildXmlHttpRequest(method,this.urlPrefix + url,true,contentType);
	var requestURI = method + "/" + url;
	var self = this;

	this.lastRequestTime=(new Date()).getTime();
	request.onreadystatechange = function() {
		if(request.readyState == 4) {
			self.onAfterCall(request);
			if(request.status != 200) {
				self.onError(requestURI,body,request,'Error while communicating with server: ' + request.status + "/" + request.statusText);
			} else {
				var response;
				try {
					response = self.decodeResponse(request);
				} catch(exc) {
					self.onError(requestURI,body,request,"Error while decoding result: " + exc.message);
				}
				if(typeof response == 'object' && response != null && "isException" in response) {
					if(typeof onError != 'function' || !onError(response)) {
						self.onError(requestURI,body,request,response);
					}
				} else {
					callback(response);
				}
			}
		}
	}		
	this.onBeforeCall(request);
	request.send(body);
}

AjaxRpc.prototype.GET = function(rq) {
	if(this.appendToUrl) {
		extend(rq.args,this.appendToUrl,false);
	}

	var url = this.preprocessUrl(rq.url) + "?" + asQueryString(rq.args);
	if(typeof rq.callback == "function") {
		this.sendAsync("GET",url,"",rq.callback,rq.onError);
	} else {
		return this.send("GET",url,"",rq.onError);
	}
}

AjaxRpc.prototype.POST = function(rq) {
	if(this.appendToUrl) {
		rq.url = this.preprocessUrl(rq.url) + "?" + asQueryString(this.appendToUrl);
	}

	var body;
	var contentType;

	if(rq.urlencoded) {
		body = asQueryString(rq.args);
		contentType = "application/x-www-form-urlencoded";
	} else {
		body = asJSON(rq.args);
		contentType = "text/javascript";
	}

	if(typeof rq.callback == "function") {
		this.sendAsync("POST",rq.url,body,rq.callback,rq.onError,contentType);
	} else {
		return this.send("POST",rq.url,body,rq.onError,contentType);	
	}
}

AjaxRpc.prototype.preprocessUrl = function(url) {
    return url;
}
