 /**
  * rpc.js 
  * 
  * James Holwell 
  *   james.holwell@corporate-eyecare.com
  *   April 2007 - Oxford, UK
  * 
  * RPC Client Library
  */
  
  var xmlrpc = false;
  var xmlrpcInProgress = false;
  var xmlrpcSendTime = false;
  var xmlrpcTimeout = 15;
  var intervalptr = false;
  
  /**
   * string XMLHttpFormRequest (string method, array attributes)
   * 
   * Wraps an array of parameters and a method name in to a well-formed
   * XML-RPC request
   */
  function XMLHttpFormRequest(method, attributes)
  {
    var req;
  
    req = '<?xml version="1.0"?>';
    req += '<methodCall>';
    req += '<methodName>' + method + '</methodName>';
    req += '<params>';
    
    req += '<param><struct>';
    for (key in attributes)
      req += '<member>' + 
             '<name>' + key + '</name>' + 
             '<value><string>' + attributes[key] + '</string></value>' + 
             '</member>';
    req += '</struct></param>';

    req += '</params>';
    req += '</methodCall>';  
    
    return req;
  }
  
  /**
   * void XMLHttpMakeRequest(string url, string data, function callback)
   * 
   * Contacts the XML-RPC server at url with the request data, when the 
   * server responds, calls the callback function with the array of results 
   */
  function XMLHttpMakeRequest(url, data, callback)
  {
    var now;
    
    /**
     * If a further request is made, before the original request has returned, 
     * we fail to avoid a deadlock
     */
    if (xmlrpcInProgress)
    {
		  document.getElementById("rpcfailed").style.display = '';
		  if (intervalptr)
		    window.clearInterval(intervalptr);
		  intervalptr = window.setInterval("XMLHttpClearMessages()", 5000);
      return;
    }
    
    /**
     * Set the flags
     */
    xmlrpcInProgress = true;
    xmlrpcCallback = callback
  
    /**
     * Construct the RPC object
     */
    try
    {
      xmlrpc = new ActiveXObject("Msxml2.XMLHTTP");
    }
    catch(e)
    {
      try
      {
        xmlrpc = new ActiveXObject("Microsoft.XMLHTTP");
      }
      catch(ee)
      {
        xmlrpc = false;
      }
    }
    
    if (!xmlrpc && typeof XMLHttpRequest != 'undefined')
    {
      try
      {
        xmlrpc = new XMLHttpRequest();
      }
      catch (e)
      {
        xmlrpc = false;
      }
    }
  
    /**
     * Connect to the server, attach the RPC callback function and
     * send the data
     */
    try
    {
      if (!xmlrpc)
        throw "Could not initialise an XMLHttp Object";
      
      if (document.getElementById("rpcprocessing"))
        document.getElementById("rpcprocessing").style.display = '';
      
      xmlrpc.onreadystatechange = XMLHttpReadyStateChange;
      xmlrpc.open("POST", url, true);
      xmlrpc.send(data);
    }
    catch (e)
    {
      alert ("Exception in XMLHttpMakeRequest: \n\n" + return_r(e));
    }  
    
    /**
     * Zero the counter for timing
     */
    now = new Date();
    xmlrpcSendTime = now.getSeconds();
  }
  
  
  /**
   * void XMLHttpReadyStateChange ()
   * 
   * Called by the XML-RPC object when an event occurs, processes the
   * event and calls the user-specified callback function
   */
  function XMLHttpReadyStateChange()
  {
    var now, nowTime;
    
    if (!xmlrpc)
      return;
    
    if (xmlrpc.readyState == 4)
    {
      /**
       * readyState = 4 is the "finished" state, irrespective of success
       */
       
      xmlrpcSendTime = -1;

      if (document.getElementById("rpcprocessing"))
        document.getElementById("rpcprocessing").style.display = 'none';
      
      xmlrpcInProgress = false;    
      
      if (xmlrpc.status == 200)
        XMLHttpResponse(xmlrpc.responseXML)
      else
        alert("There was an error contacting the XML RPC server: \n\n" + xmlrpc.statusText);

    }
    else if (parseInt(xmlrpcSendTime) > 0)
    {
      /**
       * provided that we have started timing, if the readyState is not 4, 
       * we are now just waiting for a time-out event
       */
      now = new Date();
      nowTime = now.getSeconds();
      
      if (nowTime < xmlrpcSendTime)
        nowTime =+ 59;
        
      if (parseInt(nowTime) > parseInt(xmlrpcSendTime) + xmlrpcTimeout)
      {
        xmlrpcInProgress = false;
        xmlrpc.abort();
        
        /**
         * Alert the user of the timeout
         */
        if (document.getElementById("rpcprocessing"))
          document.getElementById("rpcprocessing").style.display = 'none';
          
        if (document.getElementById("rpctimeout"))
        {
          document.getElementById("rpctimeout").style.display = '';
          if (intervalptr)
            window.clearInterval(intervalptr);
          intervalptr = window.setInterval("XMLHttpClearMessages()", 10000);
        }
      } 
    }
  }
  
  
  /**
   * void XMLHttpClearMessages()
   * 
   * Removes 'little yellow boxes'
   */
  function XMLHttpClearMessages()
  {
    if (document.getElementById("rpcfailed"))
      document.getElementById("rpcfailed").style.display = 'none';
    if (document.getElementById("rpctimeout"))
      document.getElementById("rpctimeout").style.display = 'none';
    if (document.getElementById("rpcsuccess"))
      document.getElementById("rpcsuccess").style.display = 'none';
    window.clearInterval(intervalptr);
  }
  
  
  /**
   * void XMLHttpResponse(DOMTree response)
   * 
   * Parses the response tree to extract the values, then passes them
   * on to the callback function
   */
  function XMLHttpResponse(response)
  {
    var methodResponses, methodResponse, faults, fault, values, errors, paramss, params, values, responseparams;
    
    try
    {
      if (!response.childNodes)
        alert ("XML-RPC Error: Could not parse the response object, response has no childNodes property.");
      
      methodResponses = __getNodeByTagName(response, 'methodResponse');
      
      if (methodResponses.length)
        methodResponse = methodResponses[0];
      else
        throw ("XML-RPC Error: methodResponse element not found");
      
      /**
       * Look for, and deal with, faults returned from the server
       */
      faults =  __getNodeByTagName(methodResponse, 'fault');

      if (faults.length)
      {
        fault = faults[0];
        values =  __getNodeByTagName(fault, 'value');
        
        if (values.length)
        {
          errors = XMLHttpExtractValues(values[0])
          
          if (errors['faultString'])
            alert("XML-RPC Fault: \n\n" + errors['faultString']);
          else
            alert("XML-RPC Error: Server reported a Fault, however no details were supplied.");
          
          return;
        }
        else
          alert("XML-RPC Error: Server reported a Fault, however no value was supplied.");
        
        return;
      }
      
      /**
       * Drill down params->param->values and then extract the response parameters
       */
      paramss =  __getNodeByTagName(methodResponse, 'params');
      if (!paramss.length)
        alert("XML-RPC Error: params element not found in methodResponse");
        
      params =  __getNodeByTagName(paramss[0], 'param');
      if (!params.length)
        alert("XML-RPC Error: param element not found in params");
      
      values = __getNodeByTagName(params[0], 'value');
      if (!values.length)
        alert("XML-RPC Error: value element not found in param");
      
      
    }
    catch (e)
    {
      if (confirm ("Exception in XMLHttpResponse: \n\n" + return_r(e) + "\n\n View the raw XML-RPC response?"))
        alert(xmlrpc.responseText);
    }
responseparams = XMLHttpExtractValues(values[0])
    if (xmlrpcCallback != null)
      xmlrpcCallback(responseparams);
    else
      print_r(responseparams);
  }
  
  
  /**
   * array XMLHttpExtractValues(DOMTree root)
   * 
   * Extracts the equivalent values out of the value node supplied
   */
  function XMLHttpExtractValues(root)
  {
    var r, i, members, names, values, datas;
    
    if (!root.childNodes)
      throw("XML-RPC Error: root node has no children object");

    if (root.childNodes.length == 0)
      return false;
    else  
    {
      for (i = 0; i < root.childNodes.length; i++)
      {
        if (root.childNodes[i] && root.childNodes[i].tagName)
        {
          child = root.childNodes[i];
          break;
        }
      }
    }

    /**
     * There are three basic types of value construct:
     *  - flat, typed, value
     *  - struct
     *  - array
     * they can all be deduced by the tagName of the child element
     */
    
    switch(child.tagName)
    {
      /**
       * flat data types
       */
      case 'i4':
      case 'int':
      if (child.textContent != undefined)
        return parseInt(child.textContent);
      else
        return parseInt(child.firstChild['data']);
      case 'boolean':
        if (child.textContent) return true; else return false;  
      case 'dateTime.iso8601':
      case 'string':
        if (child.textContent != undefined)
          return child.textContent;
        else
          return child.firstChild['data'];
      case 'double':
        return parseFloat(child.textContent);
      case 'base64':
        return __base64_decode(child.textContent);

      /**
       * structures
       */
      case 'struct':
        members = __getNodeByTagName(child, 'member');
        if (!members.length)
          throw ("XML-RPC Error: structure did not contain members");
        
        r = Array();
        
        for (i = 0; i < members.length; i++)
        {
          names = __getNodeByTagName(members[i], 'name');
          values = __getNodeByTagName(members[i], 'value');
          
          if (!names.length || !values.length)
            throw ("XML-RPC Error: no name/value pair supplied for member in structure");
          
          if (names[0].textContent != undefined)
            r[names[0].textContent] = XMLHttpExtractValues(values[0]);
          else
            r[names[0].firstChild['data']] = XMLHttpExtractValues(values[0]);
        }
        return r;
          
      /**
       * arrays
       */
      case 'array':
        datas = __getNodeByTagName(child, 'data');
        if (!datas.length)
          throw ("XML-RPC Error: array did not contain data");
          
        r = Array();
        
        values = __getNodeByTagName(datas[0], 'value');
        
        if (!values.length)
          return r;
          
        for (i = 0; i < values.length; i++)
          r[i] = XMLHttpExtractValues(values[i]);
        
        return r;
    }
  }
  
  
  /**
   * array __getNodeByTagName(string tagname)
   * 
   * Retrieves a list of all of the DOMTree objects with the specified tagname
   * in the DOMTree object
   */
  function __getNodeByTagName(object, tagname)
  {
    var r, key;
    r = Array();
    
    if (!object.childNodes)
      return r;
    
    for (key = 0; key < object.childNodes.length; key++)
    {      
      if (object.childNodes[key] && object.childNodes[key].tagName == tagname)
      {
        r.push(object.childNodes[key]);
      }
    }
    
    return r;
  }
  
  
  /**
   * string __base64_decode(string code)
   */
  function __base64_decode(code)
  {
    var r, key, code, codelength, i, e1, e2, e3, e4;
    
    r = '';

    key = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
    code = code.replace(/[^A-Za-z0-9\+\/\=]/g, "");
    codelength = code.length
    
    for (i = 1; i < codelength; i = i + 4)
    {
      e1 = key.indexOf(input.charAt(i));
      e2 = key.indexOf(input.charAt(i+1));
      e3 = key.indexOf(input.charAt(i+2));
      e4 = key.indexOf(input.charAt(i+3));

      r += String.fromCharCode( (e1 << 2) | (e2 >> 4) );
      if (e3 != 64) 
        r += String.fromCharCode( ((e2 & 15) << 4) | (e3 >> 2) );
      if (e4 != 64)
        r += String.fromCharCode( ((e3 & 3) << 6) | e4 );
    }
    return r;
  }
