/*

xmlrpc.js beta version 1
Tool for creating XML-RPC formatted requests in JavaScript

Copyright 2001 Scott Andrew LePera

scott@scottandrew.com
http://www.scottandrew.com/xml-rpc

Copyright 2008 Stanley Huang
stanleyhuangyc@gmail.com

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.

*/

function XMLRPCMessage(methodname){
  this.method = methodname||"system.listMethods";
  this.params = [];
  this.paramNames = [];
  return this;
}

XMLRPCMessage.prototype.setMethod = function(methodName){
  if (!methodName) return;
  this.method = methodName;
}

XMLRPCMessage.prototype.addParameter = function(data, name){
  if (arguments.length==0) return;
  this.params[this.params.length] = data;
  this.paramNames[this.paramNames.length] = name ? name : null;
}

XMLRPCMessage.prototype.xml = function(){

  var method = this.method;
  
  // assemble the XML message header
  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 name = this.paramNames[i];
    var data = this.params[i];
    xml += "<param>\n";
	if (name) xml += "<name>" + name + "</name>\n";
    xml += "<value>" + XMLRPCMessage.getParamXML(XMLRPCMessage.dataTypeOf(data),data) + "</value>\n";
    
    xml += "</param>\n";
  }
  
  xml += "</params>\n";
  xml += "</methodCall>";
  
  return xml; // for now
}

XMLRPCMessage.dataTypeOf = function (o){
  // identifies the data type
  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;
  }
  return type;
}

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

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

XMLRPCMessage.doDateXML = function(data){
  var xml = "<dateTime.iso8601>";
  xml += dateToISO8601(data);
  xml += "</dateTime.iso8601>";
  return xml;
}

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

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

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

function dateToISO8601(date){
  // wow I hate working with the Date object
  var year = new String(date.getYear());
  var month = leadingZero(new String(date.getMonth()));
  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;
} 
  
function leadingZero(n){
  // pads a single number with a leading zero. Heh.
  if (n.length==1) n = "0" + n;
  return n;
}

function getValue(xmlval)
{
	var v = getChildNode(xmlval);
	var ret = getNodeValue(v);
	switch (v.nodeName) {
	case "boolean":
		return ret == "1";
	case "int":
	case "i4":
		return parseInt(ret, 10);
	case "double":
		return parseFloat(ret);
	case "string":
	case "date":
		return ret;
	case "struct":
		vals = v.getElementsByTagName("member");
		var result = new Array;
		for (var x = 0; x < vals.length; x++) {
			var name = getChildNodeValue(vals[x], "name");
			var v = findChildNode(vals[x], "value");
			result[name] = getValue(v);
		}
		return result;
	}
	return null;
}

function parseRPC(xml)
{
	// retrieve return value
	if (!xml) return null;
	var vals = xml.getElementsByTagName("value");
	if (vals.length > 0) {
		return getValue(vals[0]);
	} else {
		return null;	
	}
}

function getRPCPayload(method)
{
	var msg = new XMLRPCMessage();
	msg.setMethod(method);
	for(var i=1; i<arguments.length; i++) {
		msg.addParameter(arguments[i]);
	}
	return msg.xml();
}

var xmlrpcurl = "/xmlrpc";

function callRPC(method)
{
	var url = xmlrpcurl;
	var xmlhttp = window.XMLHttpRequest ? new XMLHttpRequest : new ActiveXObject("Microsoft.XMLHTTP");
	if (!xmlhttp) return -1;

	var msg = new XMLRPCMessage();
	msg.setMethod(method);
	for(var i=1; i<arguments.length; i++) {
		msg.addParameter(arguments[i]);
	}

	try { 
		xmlhttp.open("POST", url, false);
		xmlhttp.send(msg.xml());
	}
	catch (ex) {
		alert(ex);
	}
	return parseRPC(xmlhttp.responseXML);
}

function addHTML (html) {
  if (document.all)
	document.body.insertAdjacentHTML('beforeEnd', html);
  else if (document.createRange) {
	var range = document.createRange();
	range.setStartAfter(document.body.lastChild);
	var docFrag = range.createContextualFragment(html);
	document.body.appendChild(docFrag);
  }
  else if (document.layers) {
	var l = new Layer(window.innerWidth);
	l.document.open();
	l.document.write(html);
	l.document.close();
	l.top = document.height;
	document.height += l.document.height;
  }
}

var userCallback = null;

function callbackRPC(method, response)
{
	if (!userCallback) return;
	if (response.XMLDocument) {
		var node = response.XMLDocument.getElementsByTagName("methodResponse");
		if (node.length > 0)
			userCallback(method, parseRPC(node[0]));
	} else {
		if (response.firstChild.nodeName == "methodResponse") {
			userCallback(method, parseRPC(response));
		}
	}
}

function postRPC(url, callback, method)
{
	if (!document.getElementById("rpcform")) {
		addHTML("<div style='display:none'><form id='rpcform' method='post' action='"+url+"' target='rpcframe' enctype='text/plain'><textarea name='rpcpayload'></textarea></form><iframe id='rpcframe' name='rpcframe' onload=\"callbackRPC('"+method+"', window.frames['rpcframe'].document)\"></iframe></div>");
	}
	var msg = new XMLRPCMessage();
	msg.setMethod(method);
	for(var i=3; i<arguments.length; i++) {
		msg.addParameter(arguments[i]);
	}
	var frm = document.getElementById("rpcform");
	if (!frm) return false;
	frm.rpcpayload.value = msg.xml();
	userCallback = callback;
	frm.submit();
	return true;
}