var xmlEntAmp = RegExp("&", "g");
var xmlEntLt = RegExp("<", "g");
var xmlEntGt = RegExp(">", "g");
var xmlEntApos = RegExp("'", "g");
var xmlEntQuot = RegExp('"', "g");
function xmlEscape(str) {
  return str.replace(xmlEntAmp, "&amp;")
            .replace(xmlEntLt, "&lt;")
            .replace(xmlEntGt, "&gt;")
            .replace(xmlEntApos, "&apos;")
            .replace(xmlEntQuot, "&quot;");
}

//////////////////////////////////////////////////////////////////////////////
//
// DOM Node Type Constants
//
//
var NodeType = {};
NodeType.ELEMENT_NODE = 1;
NodeType.ATTRIBUTE_NODE = 2;
NodeType.TEXT_NODE = 3;
NodeType.CDATA_SECTION_NODE = 4;
NodeType.ENTITY_REFERENCE_NODE = 5;
NodeType.ENTITY_NODE = 6;
NodeType.PROCESSING_INSTRUCTION_NODE = 7;
NodeType.COMMENT_NODE = 8;
NodeType.DOCUMENT_NODE = 9;
NodeType.DOCUMENT_TYPE_NODE = 10;
NodeType.DOCUMENT_FRAGMENT_NODE = 11;
NodeType.NOTATION_NODE = 12;

//
// These two seem to be the only required
// types that the XML-RPC binder needs
//
function textNodePredicate(element, i) {
  if (element.nodeType == NodeType.TEXT_NODE) return true;
  else return false;
}

function elementNodePredicate(element, i) {
  if (element.nodeType == NodeType.ELEMENT_NODE) return true;
  else return false;
}

function makeElementPredicate(name) {
  var res = function(element, i) {
    if (element.nodeType == NodeType.ELEMENT_NODE
        && element.nodeName == name) {
      return true;
    } else {
      return false;
    }
  }
  return res;
}

function getElementData(element) {
  var node = $A(element.childNodes).find(textNodePredicate);
  var res = null;
  if (node != null) {
    res = node.nodeValue;
  }
  return res;
}

function firstChildElement(node, name) {
  var element = $A(node.childNodes).find(makeElementPredicate(name));
  if (!element) throw Error('Element "' + name + '" not found');
  return element;
}

function xrpcType(node) {
  var type = typeof(node);
  type = type.toLowerCase();
  if (type === "number") {
    if (Math.round(node) == node) return "i4";
    else return "double";
  } else if (type === "object") {
    var ctor = node.constructor;
    if (ctor === Date) return "date";
    else if (ctor === Array) return "array";
    else return "struct";
  } else if (type === "string") {
    return "string";
  } else if (type === "boolean") {
    return "boolean";
  } else {
    /* catch all */
    return "string";
  }
}

function xrpcValueBuild(type, node) {
  return '<' + type + '>' + xmlEscape(String(node)) + '</' + type + '>';
}

function xrpcBooleanBuild(node) {
  return '<boolean>' + ((node==true) ? 1 : 0) + '</boolean>';
}

function xrpcDateBuild(node) {
  var year = String(node.getFullYear());
  var month = (node.getMonth() + 1).toPaddedString(2);
  var day = node.getDate().toPaddedString(2);
  var hours = node.getHours().toPaddedString(2);
  var mins = node.getMinutes().toPaddedString(2);
  var secs = node.getSeconds().toPaddedString(2);
  return year + '-' + month + '-' + day + "T" + hours + ":" + mins + ":" + secs;
}

function xrpcStructBuild(node) {
  var local = '<struct>';
  for (var k in node) {
    local += '<member><name>' + k
             + '</name><value>' + xrpcBuildNode(node[k])
             + '</value></member>';
  }
  local += '</struct>';
  return local;
}

function xrpcArrayBuild(node) {
  var local = '<array><data>';
  node.each(function (n) {
    local += '<value>' + xrpcBuildNode(n) + '</value>';
  });
  local += '</data></array>';
  return local;
}

function xrpcBuildNode(node) {
  var type = xrpcType(node);
  switch (type) {
  case "date": return xrpcDateBuild(node);
  case "array": return xrpcArrayBuild(node);
  case "struct": return xrpcStructBuild(node);
  case "boolean": return xrpcBooleanBuild(node);
  default: return xrpcValueBuild(type, node);
  }
}

function xrpcBuild() {
  var args = $A(arguments);
  var text = '';

  text += '<?xml version="1.0"?>\n';
  text += '<methodCall><methodName>' + args.shift()
                 + '</methodName><params>';
  args.each(function (n) {
    text += '<value>' + xrpcBuildNode(n) + '</value>';
  });
  text += '</params></methodCall>';
  return text;
}

function xrpcNumberBind(node) {
  return Number(getElementData(node));
}

function xrpcBooleanBind(node) {
  if (Number(getElementData(node)) == 0) {
    return false;
  } else {
    return true;
  }
}

function xrpcStringBind(node) {
  return String(getElementData(node));
}

function xrpcDateBind(node) {
  // FIXME - Convert to real javascript Date
  var datetime = new Date();
  datetime.setISO8601(String(getElementData(node)));
  return datetime;
}

function xrpcArrayBind(node) {
  var datum = firstChildElement(node, "data");
  var values = $A(datum.childNodes).findAll(makeElementPredicate("value"));
  var arr = $A();
  values.each(function (node) {
    var value = xrpcValueBind(node);
    arr.push(value);
  });
  return arr;
}

function xrpcStructBind(node) {
  var members = $A(node.childNodes).findAll(makeElementPredicate("member"));
  if (!members) {
    throw Error("no members found");
  }

  var obj = {};
  members.each(function (node) {
    var nameElem = firstChildElement(node, "name");
    var name = getElementData(nameElem);
    var valueElem = firstChildElement(node, "value");
    var value = xrpcValueBind(valueElem);
    obj[name] = value;
  });
  return obj;
}

function xrpcValueBind(element) {
  var childElements = $A(element.childNodes).findAll(elementNodePredicate);
  if (!childElements) {
    throw Error("No typed value");
  }

  var node = childElements[0]
  var type = node.nodeName;
  switch (type) {
  case "int": return xrpcNumberBind(node);
  case "i4": return xrpcNumberBind(node);
  case "boolean": return xrpcBooleanBind(node);
  case "double": return xrpcNumberBind(node);
  case "dateTime.iso8601": return xrpcDateBind(node);
  case "base64": return xrpcStringBind(node);
  case "struct": return xrpcStructBind(node);
  case "array": return xrpcArrayBind(node);
  case "nil": return null;
  case "string": return xrpcStringBind(node);
  default: return xrpcStringBind(node);
  }
}

var XmlRpcFault = klass.create();
Object.extend(XmlRpcFault.prototype, {
  initialize: function(faultNode) {
    var value = firstChildElement(faultNode, "value");
    var faultObj = xrpcValueBind(value);
    this.code = faultObj.faultCode;
    this.string = faultObj.faultString;
  },

  toString: function() {
    return String('XML-RPC Fault:' + this.code + ': ' + this.string);
  }
});

function xrpcFault(obj) {
  return klass.instanceOf(obj, XmlRpcFault);
}

function xrpcBind(dom) {
  var root = dom.documentElement;
  // must find methodResponse
  if (root.nodeName != "methodResponse") {
    throw Error("Invalid Response");
  }
  try {
    var params = firstChildElement(root, "params");
  } catch (e) {
    var params = null;
  }
  if (!params) {
    var fault = firstChildElement(root, "fault");
    throw new XmlRpcFault(fault);
  }

  var param = firstChildElement(params, "param");
  var value = firstChildElement(param, "value");
  var result = xrpcValueBind(value);
  return result;
}

// vim:set sts=2 sw=2 expandtab:

