/*
 * JSON-RPC JavaScript client
 *
 * $Id: jsonrpc_async.js,v 1.4 2005/03/15 02:29:22 jamesgbritt Exp $
 *
 * Copyright (c) 2003-2004 Jan-Klaas Kollhof
 * Copyright (c) 2005 Michael Clark, Metaparadigm Pte Ltd
 * Copyright (c) 2005 James Britt, Neurogami, LLC
 *
 * This code is based on Michael Clark's' version of Jan-Klaas' jsonrpc.js
 * file fom the JavaScript o lait library (jsolait).
 
 * The Clark version seemed easier to use the original Module-based
 * code, but did not do asyncronous requests.  The current version
 * is essentialy the same code, but with the async requests and callback 
 * hooks
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public (LGPL)
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details: http://www.gnu.org/
 *
 */

// TODO: Add async
// TODO: Add HTTP auth (user, password)

Object.prototype.toJSON = function Object_toJSON() {
    var v = [];
    for(attr in this) {
      if(this[attr] == null) v.push("\"" + attr + "\": null");
      else if(typeof this[attr] == "function"); // skip
      else v.push("\"" + attr + "\": " + this[attr].toJSON());
    }
    return "{" + v.join(", ") + "}";
  }

String.prototype.toJSON = function String_toJSON()  {
    return "\"" + this.replace(/([\"\\])/g, "\\$1") + "\"";
}

Number.prototype.toJSON = function Number_toJSON() {
    return this.toString();
}

Boolean.prototype.toJSON = function Boolean_toJSON() {
    return this.toString();
}

Date.prototype.toJSON = function Date_toJSON() {
    this.valueOf().toString();
}

Array.prototype.toJSON = function Array_toJSON() {
    var v = [];
    for(var i=0; i<this.length; i++) {
      if(this[i] == null) v.push("null");
      else v.push(this[i].toJSON());
    }
    return "[" + v.join(", ") + "]";
}


/*******************************************************************
 ******************************************************************/
JSONRpcAsyncClient = function JSONRpcAsyncClient_ctor( serverURL, objectID ) {
    this.serverURL = serverURL;
    this.objectID = objectID;
    if( !JSONRpcAsyncClient.http )
      JSONRpcAsyncClient.http = JSONRpcAsyncClient.getHTTPRequest();
    this.addAsyncMethods( ["system.listMethods"] );
    var m = this.sendRequest( "system.listMethods", [] );
    this.addAsyncMethods( m );    

}

/*******************************************************************
 ******************************************************************/
JSONRpcAsyncClient.Exception = function JSONRpcAsyncClient_Exception_ctor(code, msg) {
    this.code = code;
    this.msg = msg;
}

/*******************************************************************
 ******************************************************************/
JSONRpcAsyncClient.Exception.prototype.toString =
function JSONRpcAsyncClient_Exception_toString(code, msg)  {
    return "JSONRpcAsyncClientException: " + this.msg;
}




JSONRpcAsyncClient.AsyncMethod =
function JSONRpcAsyncClient_Method_ctor( client, methodName ) {
  
    var fn=function()  {
      var args = [];
      var callback = arguments[0]
      for(var i=1; i< arguments.length;i++) {
          args.push(arguments[i]);
      }
      return fn.client.sendAsyncRequest.call( fn.client, fn.methodName, callback, args);
    }
    
    fn.client = client;
    fn.methodName = methodName;
    return fn;
}

 JSONRpcAsyncClient.prototype.addAsyncMethods =
function JSONRpcAsyncClient_addAsyncMethods(methodNames) {
    for(var i=0; i<methodNames.length; i++) {
    var obj = this;
    var names = methodNames[i].split(".");
    for(var n=0; n<names.length-1; n++){
      var name = names[n];
      if(obj[name]){
        obj = obj[name];
      } 
      else {
        obj[name]  = new Object();
        obj = obj[name];
      }
  }
  var name = names[names.length-1];
  if(!obj[name]){
      var method = new JSONRpcAsyncClient.AsyncMethod(this, methodNames[i]);
      obj[name] = method;
   }
  }
}


/*******************************************************************
 ******************************************************************/
// Async JSON-RPC call.  The tricky part may be catching the
// state change when the call is complete, and unmarshalling the
// response.
// Possibility: Have the user pass in a caqllback method; this method
// assumes it will get called with the unnarshalled response.
// When the reqadyState goes to 4, grab the JSON response, unmarshal,
// and invokve the callback

JSONRpcAsyncClient.prototype.sendAsyncRequest =
function JSONRpcAsyncClient_sendAsyncRequest( methodName, call_back, args ) {
    var obj = {"method" : methodName, "params" : args };
    if (this.objectID) obj.objectID = this.objectID;
    
    var req_data = obj.toJSON();
    
    var http = JSONRpcAsyncClient.getHTTPRequest();

    http.open("POST", this.serverURL, true); //  async

    try {
      http.setRequestHeader( "Content-type", "text/plain" );
    } 
    catch(e) {}


    http.onreadystatechange = function() {
      if ( http.readyState == 4 && http.status == 200) {
        try {
          s  = "var obj = " + http.responseText 
          eval( s );
        }
        catch( e ) {
          var e_msg = "onreadystatechange Error: " + e + "\nresponseText: " + http.responseText  
          obj.err = e.toString()
        }
        if( obj.error ) {
          throw new JSONRpcAsyncClient.Exception( obj.error.code, obj.error.msg );
        }
        return call_back( obj.result );
      }
      else {
        }
    }

    http.send( req_data );

}


/*******************************************************************
 ******************************************************************/
JSONRpcAsyncClient.prototype.sendRequest =

 function JSONRpcAsyncClient_sendRequest(methodName, args) {
    // Make request object
    var obj = {"method" : methodName, "params" : args};
    if (this.objectID) obj.objectID = this.objectID;

    // Marshall the request object to a JSON string
    var req_data = obj.toJSON();

    // Send the request
    JSONRpcAsyncClient.http.open("POST", this.serverURL, false); // no async
    // setRequestHeader is missing in Opera 8 Beta
    try {
      JSONRpcAsyncClient.http.setRequestHeader("Content-type", "text/plain");
    } 
    catch(e) {}
    JSONRpcAsyncClient.http.send(req_data);

    // Unmarshall the response

    try {
      eval("var obj = " + JSONRpcAsyncClient.http.responseText);
    }
    catch(e) {
      alert( "sendRequest error: " + e + "\nresponseText: " + JSONRpcAsyncClient.http.responseText )            
      obj.err = e.toString()
    }
    if( obj.error) {
      alert( JSONRpcAsyncClient.http.responseText )      
      throw new JSONRpcAsyncClient.Exception(obj.error.code, obj.error.msg);
     }
      var res = obj.result;

    // Handle CallableProxy
    if(res && res.objectID && res.JSONRPCType == "CallableReference")
      return new JSONRpcAsyncClient(this.serverURL, res.objectID);

    return res;
}

/*******************************************************************
 ******************************************************************/
JSONRpcAsyncClient.getHTTPRequest = function JSONRpcAsyncClient_getHTTPRequest() {

    try { // to get the mozilla httprequest object
      return new XMLHttpRequest();
    } 
    catch(e) {}

    try { // to get MS HTTP request object
      return new ActiveXObject("Msxml2.XMLHTTP.4.0");
    } 
    catch(e) {}

    try { // to get MS HTTP request object
      return new ActiveXObject("Msxml2.XMLHTTP");
    } 
    catch(e) {}

    try {// to get the old MS HTTP request object
      return new ActiveXObject("microsoft.XMLHTTP"); 
    } 
    catch(e) {}

    throw new JSONRpcAsyncClient.Exception( 0, "Can't create XMLHttpRequest object");
}