/*
 * xmlrpc.js
 * Tool for creating XML-RPC formatted requests and 
 * parsing the response in JavaScript.
 * According to XML-RPC Specification 6/30/2003.
 * Last Modified on 10/21/2006.
 *
 * Copyright (c) 2006- xiaosuo<xiaosuo@gmail.com>
 * Copyright 2001 Scott Andrew LePera
 * scott@scottandrew.com
 * http://www.scottandrew.com/xml-rpc
 * License: 
 * You are granted the right to use and/or redistribute this 
 * code only if this license and the copyright notice are included 
 * and you accept that no warranty of any kind is made or implied 
 * by the author.
 */

///////////////////////////////////////////////////////////////////////////////
// XMLRPCRequest implementation

/** XMLRPCRequest class. */
function XMLRPCRequest(methodname, params)
{
	this.params = params || [];
	this.method = methodname || "system.listMethods";

	return this;
}

XMLRPCRequest.prototype.setMethod = function(methodName) {
	this.method = methodName;
}

XMLRPCRequest.prototype.addParameter = function(data) {
	this.params[this.params.length] = data;
}

XMLRPCRequest.prototype.xml = function() {
	var method = this.method;
	var xml = "";

	xml += "<?xml version=\"1.0\"?>\n";
	xml += "<methodCall>\n";
	xml += "<methodName>" + method+ "</methodName>\n";
	xml += "<params>\n";

	// do individual parameters
	for (var i = 0; i < this.params.length; i++) {
		var data = this.params[i];
		xml += "<param>\n";
		xml += "<value>" + XMLRPCRequest.getParamXML(
				XMLRPCRequest.dataTypeOf(data), data)
				+ "</value>\n";
		xml += "</param>\n";
	}

	xml += "</params>\n";
	xml += "</methodCall>";

	return xml; // for now
}

XMLRPCRequest.dataTypeOf = function(o) {
	if (o == null) {
		return "string";
	}

	var type = typeof(o);
	type = type.toLowerCase();

	switch(type) {
	case "number":
		if (Math.round(o) == o) {
			type = "i4";

		} else {
			type = "double";

		}
		break;

	case "object":
		var con = o.constructor;
		if (con == Date) {
			type = "date";

		} else if (con == Array) {
			type = "array";

		} else {
			type = "struct";

		}
	break;

	default:
		break;
	}

	return type;
}

XMLRPCRequest.doValueXML = function(type, data) {
	data += "";
	data = data.replace(/<|>/g, "");
	var xml = "<" + type + ">" + data + "</" + type + ">";
	return xml;
}

XMLRPCRequest.doBooleanXML = function(data) {
	var value = (data == true) ? 1 : 0;
	var xml = "<boolean>" + value + "</boolean>";
	return xml;
}

XMLRPCRequest.doDateXML = function(data) {
	var xml = "<dateTime.iso8601>";

	xml += dateToISO8601(data);
	xml += "</dateTime.iso8601>";

	return xml;
}

XMLRPCRequest.doArrayXML = function(data) {
	var xml = "<array><data>\n";

	for (var i = 0; i < data.length; i++) {
		xml += "<value>" + XMLRPCRequest.getParamXML(
				XMLRPCRequest.dataTypeOf(data[i]), data[i])
				+ "</value>\n";
	}
	xml += "</data></array>\n";

	return xml;
}

XMLRPCRequest.doStructXML = function(data) {
	var xml = "<struct>\n";

	for (var i in data) {
		xml += "<member>\n";
		xml += "<name>" + i + "</name>\n";
		xml += "<value>" + XMLRPCRequest.getParamXML(
				XMLRPCRequest.dataTypeOf(data[i]), data[i])
				+ "</value>\n";
		xml += "</member>\n";
	}
	xml += "</struct>\n";

	return xml;
}

XMLRPCRequest.getParamXML = function(type, data) {
	var xml;

	switch (type) {
	case "date":
		xml = XMLRPCRequest.doDateXML(data);
		break;
	case "array":
		xml = XMLRPCRequest.doArrayXML(data);
		break;
	case "struct":
		xml = XMLRPCRequest.doStructXML(data);
		break;
	case "boolean":
		xml = XMLRPCRequest.doBooleanXML(data);
		break;
	default:
		xml = XMLRPCRequest.doValueXML(type,data);
		break;
	}

	return xml;
}

// pads a single number with a leading zero. Heh.
function leadingZero(n)
{
	if (n.length == 1) {
		n = "0" + n;
	}
	return n;
}

// wow I hate working with the Date object
function dateToISO8601(date)
{
	var year = new String(date.getYear());
	var month = leadingZero(new String(date.getMonth() + 1));
	var day = leadingZero(new String(date.getDate()));
	var time = leadingZero(new String(date.getHours()))
			+ ":" + leadingZero(new String(date.getMinutes()))
			+ ":" + leadingZero(new String(date.getSeconds()));
	var converted = year + month + day + "T" + time;

	return converted;
} 

///////////////////////////////////////////////////////////////////////////////
// XMLRPCResponse implementation 

function xgetChildElement(doc)
{
	for (var i = 0; i < doc.childNodes.length; i ++) {
		if (doc.childNodes[i].nodeType == 1) {
			return doc.childNodes[i];
		}
	}

	throw "No child exists";
	return null;
}

/*function rpcparam2hash(doc)
{
	var result = null;	
	// <methodResponse>
	if (doc.tagName == "methodResponse") {
		result = rpcparam2hash(xgetChildElement(doc));

	//   <params>
	} else if (doc.tagName == "params" || doc.tagName == "fault") {
		result = {};
		result[doc.tagName] = parseParam(xgetChildElement(doc));

	} else {
		throw ("Unknow tagName: " + doc.tagName);
	}
	return result;
}*/

function rpcparam2hash(doc)
{
	var rpc = null;

	if (doc.tagName == "methodResponse") {
		rpc = rpcparam2hash(xgetChildElement(doc));
	} else if (doc.tagName == "params" || doc.tagName == "fault") {
		rpc = Array();
		rpc[doc.tagName] = rpcparam2hash(xgetChildElement(doc));
	} else if (doc.tagName == "array" || doc.tagName == "param") {
		rpc = rpcparam2hash(xgetChildElement(doc));
	} else if (doc.tagName == "value") {
		try {
			rpc = rpcparam2hash(xgetChildElement(doc));
		} catch (e) {
			if (doc.childNodes[0]) {
				rpc = doc.childNodes[0].nodeValue;
			} else {
				rpc = e.message;
			}
		}
	} else if (doc.tagName == "data") {
		rpc = Array();
		for (var i = 0; i < doc.childNodes.length; i++) {
			if (doc.childNodes[i].nodeName != "#text") {
				var myparam;
				myparam = rpcparam2hash(doc.childNodes[i]);
				rpc.push(myparam);
			}
		}
	} else if (doc.tagName == "i4" || doc.tagName == "int") {
		if (doc.childNodes[0]) {
			rpc = parseInt(doc.childNodes[0].nodeValue);
		}
	} else if (doc.tagName == "string") {
		if (doc.childNodes[0]) {
			rpc = doc.childNodes[0].nodeValue;
		} 
	} else if (doc.tagName == "boolean") {
		if (doc.childNodes[0]) {
			rpc = new Boolean(parseInt(doc.childNodes[0].nodeValue));
		}
	} else if (doc.tagName == "double") {
		if (doc.childNodes[0]) {
			rpc = parseFloat(doc.childNodes[0].nodeValue);
		}
	} else if (doc.tagName == "dateTime.iso8601") {
		if (doc.childNodes[0]) {
			datestr = doc.childNodes[0].nodeValue;
			rpc = new Date(parseInt(datestr.substr(0, 4), 10),
				parseInt(datestr.substr(4, 2), 10),
				parseInt(datestr.substr(6, 2), 10),
				parseInt(datestr.substr(9, 2), 10),
				parseInt(datestr.substr(12, 2), 10),
				parseInt(datestr.substr(15, 2), 10));
		}
	} else if (doc.tagName == "struct") {
		rpc = Array();
		for (var x = 0; x < doc.childNodes.length; x++) {
			var name;
			var value;
			var node = doc.childNodes[x];
			if (node.nodeName == "member") {
				for (var y = 0; y < node.childNodes.length; y++) {
					var mynode = node.childNodes[y];
					if (mynode.nodeName == "#text") continue;
					if (mynode.tagName == "name") {
						name = mynode.childNodes[0].nodeValue
					} 						
					else if (mynode.tagName == "value") {						
						value = rpcparam2hash(mynode);						
					}
					
				}
				rpc[name] = value;
				rpc[x] = name + ": " + value;
			}
		}
	} else {
		throw "Unknown/Unsupported tagname:" + doc.tagName;
	}

	return rpc;
}

function parseParam(doc) {
	return parseValue(xgetChildElement(doc));
}

function parseArray(doc) {
	var data = xgetChildElement(doc);
	if (!data || data.tagName != "data") {
		throw "Not a data element";
	}

	var result = [];	
	for (var i = 0; i < data.childNodes.length; i++) {
		if (data.childNodes[i].nodeType == 1) {
			result.push(parseValue(data.childNodes[i]));
		}
	}

	return result;
}

function parseStruct(doc) {
	var result = {};
	for (var i = 0; i < doc.childNodes.length; i++) {
		if (doc.childNodes[i].nodeName != "member") {
			continue;
		}

		var member = doc.childNodes[i];
		var name = null;
		var value = null;

		for (var j = 0; j < member.childNodes.length; j++) {
			var node = member.childNodes[j];
			switch (node.nodeName) {
			case "name": 
				node = node.childNodes[0];
				name = node ? node.nodeValue : null;
				break;
			case "value": 
				value = parseValue(node);
				break;
			}
		}

		if (name) {
			result[name] = value;
		}
	}

	return result;
}


function parseValue(doc) {
	if (!doc || doc.tagName != "value") {
		throw "Not a value element";
	}

	var value = null;
	for (var i = 0; i < doc.childNodes.length; i ++) {
		if (doc.childNodes[i].nodeType == 1) {
			value = doc.childNodes[i];
			break;
		}
	}

	if (value == null) {
		value = doc.childNodes[0];
		return value ? value.nodeValue : null;
	}

	var node = value.childNodes[0];
	var result = null;	

	switch (value.tagName) {
	case "i4":
	case "int":
		if (node) {
			result = parseInt(node.nodeValue);
		}
		break;
	case "string":
		if (node) {
			result = node.nodeValue;
		}
		break;
	case "boolean":
		if (node) {
			result = new Boolean(parseInt(node.nodeValue));
		}
		break;
	case "double":
		if (node) {
			result = parseFloat(node.nodeValue);
		}
		break;
	case "dateTime.iso8601":
		if (node) {
			var datestr = node.nodeValue;
			result = new Date(parseInt(datestr.substr(0, 4), 10),
				parseInt(datestr.substr(4, 2), 10),
				parseInt(datestr.substr(6, 2), 10),
				parseInt(datestr.substr(9, 2), 10),
				parseInt(datestr.substr(12, 2), 10),
				parseInt(datestr.substr(15, 2), 10));
		}
		break;
	case "base64":
		break;
	case "struct":
		result = parseStruct(value);
		break;
	case "array":
		result = parseArray(value);
		break;
	default:
		if (node) {
			result = node.nodeValue;
		}
		break;
	};
	return result;
}

function XMLRPCResponse(req)
{
	// XMLHTTPRequest object
	if (req.readyState != 4 && req.status != 200) {
		throw "Request error";
	}
	this.m_xml = req.responseXML;
	this.m_serialize = req.responseText;	

	// parse the XML result
	var rootNode = this.m_xml.getElementsByTagName("methodResponse").item(0);
	if (!rootNode) {
		alert(this.m_xml.xml);
		throw "No root node";
	}
	this.m_value = rpcparam2hash(rootNode);	
	return this;
}

XMLRPCResponse.prototype.faultCode = function() {
	if (this.m_value["fault"]) {
		return parseInt(this.m_value["fault"]["faultCode"]);
	}

	return 0;
}

XMLRPCResponse.prototype.faultString = function() {
	if (this.m_value["fault"]) {
		return this.m_value["fault"]["faultString"]
	}

	return null;
}

XMLRPCResponse.prototype.value = function() {
	if (this.m_value) {
		return this.m_value["params"];
	}
}

XMLRPCResponse.prototype.serialize = function() {
	return this.m_serialize;
}

///////////////////////////////////////////////////////////////////////////////
// XMLRPCClient implementation 

// If IE is used, create a wrapper for the XMLHttpRequest object
if (typeof XMLHttpRequest == "undefined" ) {
	XMLHttpRequest = function(){
		return new ActiveXObject(
			navigator.userAgent.indexOf("MSIE 5") >= 0 ?
			"Microsoft.XMLHTTP" : "Msxml2.XMLHTTP"
		);
	};
}

function XMLRPCClient(url, callback)
{
	if (url) {
		this.m_url = url;
	}
	if (callback) {
		this.readystatechange = callback;
	}
	return this;
}

XMLRPCClient.prototype.setURL = function(url) {
	this.m_url = url;
}

XMLRPCClient.prototype.setCallback = function(callback) {
	this.readystatechange = callback;
}

XMLRPCClient.prototype.readystatechange = function(req) {
	// dummy function now
}

XMLRPCClient.prototype.send = function(msg, async, callback) {
	var req;
	var is_async = async || false;
	
	req = new XMLHttpRequest();

	if (is_async) {
		req.onreadystatechange = function() {
			if (callback) {
				callback(req)
			} else {
				this.readystatechange(req);
			}
		};
	}

	req.open("POST", this.m_url, is_async);
	req.setRequestHeader("Content-Type", "text/xml");
	req.send(msg);
	return req;
}

XMLRPCClient.prototype.call = function(method, params) {
	var req;
	var resp;
	var msg;

	msg = new XMLRPCRequest(method, params);
	req = this.send(msg.xml());
	resp = new XMLRPCResponse(req);
	if (resp.faultCode()) {
		throw "XMLRPC call error:\nerrorCode = " + resp.faultCode()
				+ "\nerrorString = " + resp.faultString();
	}
	
	return resp.value();
}


///////////////////////////////////////////////////////////////////////////////
// Util functions

/**
 * 一个工具方法用于比较简单地发送一个 XML-RPC 消息.
 * @param client XML-RPC Client
 * @param callback 回调函数
 * @param methodName XML-RPC 方法
 * @param ... 参数列表, 可以是任意个.
 */
function sendXmlRpc(client, callback, methodName, params)
{
	var arguments = sendXmlRpc.arguments;
	if (arguments.length < 3) {
		return;
	}

	var args = [];
	for (var i = 3; i < arguments.length; i++) {
		args.push(arguments[i]);
	}

	try {
		var msg = new XMLRPCRequest(methodName, args);
		var request = xmlRpcClient.send(msg.xml(), true, callback);
		
	} catch (e) {
		showError("sendXmlRpc: " + e.name + ": " + e.message + " " + methodName);
	}
}	

/** 取得 XML-RPC 回复内容. 如果还没有读取完毕则返回 null. */
function getResponse(request)
{
	if (request.readyState != 4) {
		return null;
	}

	if (request.status != 200) {
		throw {name: "XmlRpcException", code: request.status, message: request.statusText};
	}
	
	var response = new XMLRPCResponse(request);
	if (response && response.faultCode()) {
		throw {name: "XmlRpcException", code: response.faultCode(), message: response.faultString()};
    }
	return response;
}

/** 一个工具对象用来简化 XML-RPC 的处理. */
var xmlrpc = {
	client : null,
	call : function (methodName, params, success_callback, error_callback) {
		if (!xmlrpc.client) {
			return;
		}

		if (!methodName) {
			return;
		}

		var args = null;
		if (!params || params instanceof Array) {
			args = params;
		} else {
			args = [params];
		}

		try {
			var msg = new XMLRPCRequest(methodName, args);
			var request = xmlrpc.client.send(msg.xml(), true, function(request) {
				if (request.readyState != 4) {
					return null;
				}

				if (request.status != 200) {
					if (error_callback) {
						error_callback(request.statusText, request.status);
					}
					return null;
				}
				
				var response = new XMLRPCResponse(request);
				if (response && response.faultCode()) {
					if (error_callback) {
						error_callback(response.faultString(), response.faultCode());
					}
					return null;
				}
			
				if (success_callback) {
					success_callback(response.value());
				}
			});
			
		} catch (e) {
			if (error_callback) {
				error_callback(e.message || e);
			}
		}	
	}
};


