/*

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

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 = [];
  return this;
};

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

XMLRPCMessage.escapeXML = function(srcString)
{
  return srcString.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
};

XMLRPCMessage.prototype.replaceStr = function(orig,lookfor,replacewith,ignorecase)  // JPW::Jan 6, 2003
  {
    var str = new String();
    str += orig;
    var type = 'g';
    if (ignorecase) 
      type += 'i';
    var re = new RegExp (lookfor, type);
    return(str.replace(re,replacewith));
  };

XMLRPCMessage.prototype.addParameter = function(data){
  if (arguments.length==0) return;
  var type = typeof(data);
  type = type.toLowerCase();
  this.params[this.params.length] = data;
};

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 data = this.params[i];
    var paramXML=XMLRPCMessage.getParamXML(XMLRPCMessage.dataTypeOf(data),data);
    if (paramXML!=null)
    {
      xml += "<param>\n";
      xml += "<value>" + paramXML + "</value>\n";
      xml += "</param>\n";
    }
    else
      alert('Warning:\nAn error occurred when building a request to send to the server\nAttempted to add an undefined value as a parameter.\nSome data may be lost.');
  }
  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":
      try {
        var con = o.constructor;
        if (con == Date) type = "date";
        else if (con == Array) type = "array";
        else type = "struct";
      }
      catch(e)
      { type = "struct" }
      break;
  }
  return type;
};

XMLRPCMessage.doValueXML = function(type,data){
  var xml=null;
  switch (type)
  {
    case 'string':
      data = this.escapeXML(data);
      break;
    case 'undefined':
      data=null;
      break;
    default:
      break;
  }
  if (data!=null)
    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++){
    var paramXML=XMLRPCMessage.getParamXML(XMLRPCMessage.dataTypeOf(data[i]),data[i]);
    //if an element has been deleted from an array it will leave a null entry.
    // warn the user and keep going.
    if (paramXML!=null)
      xml += "<value>" + paramXML + "</value>\n";
    /*
    else
      alert('Warning:\nAn error occurred when building a request to send to the server\nAttempted to add an undefined index to an Array.\nSome data may be lost.');
    */
  }
  xml += "</data></array>\n";
  return xml;
};

XMLRPCMessage.doStructXML = function(data){
  var xml = "<struct>\n";
  for (var i in data){
    if(i=='toJSONString')continue;
    var paramXML=XMLRPCMessage.getParamXML(XMLRPCMessage.dataTypeOf(data[i]),data[i]);
    if (paramXML!=null){
      xml += "<member>\n";
      xml += "<name>" + this.escapeXML(i) + "</name>\n";
      xml += "<value>" + paramXML + "</value>\n";
      xml += "</member>\n";
    }
    else{
      alert('Warning:\nAn error occurred when building a request to send to the server\nAttempted to add an undefined member to a Struct.\nSome data may be lost.');      
    }
  }
  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;
};


