// vim: ts=4 sw=4 expandtab cindent foldmethod=marker filetype=javascript
//
// BEGIN FLOCK GPL
//
// Copyright Flock Inc. 2005-2009
// http://flock.com
//
// This file may be used under the terms of the
// GNU General Public License Version 2 or later (the "GPL"),
// http://www.gnu.org/licenses/gpl.html
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
// for the specific language governing rights and limitations under the
// License.
//
// END FLOCK GPL

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

var flockIXmlRpcValue = Components.interfaces.flockIXmlRpcValue;
var flockIXmlRpcServer = Components.interfaces.flockIXmlRpcServer;

/* Keep this object in sync between:
    $c/xmlrpc/src/flockXmlRpcServer.js
   and:
    $c/xmlrpc/public/xmlrpchelper.js
*/

function XmlRpcValue(aType) {
  this.XmlRpcType = aType;
}

XmlRpcValue.prototype.QueryInterface =
  new XPCOMUtils.generateQI([flockIXmlRpcValue]);

XmlRpcValue.Int = function XRV_Int(aValue) {
  var value = new XmlRpcValue(flockIXmlRpcValue.TYPE_INT);
  value.IntValue = aValue;
  return value;
}
XmlRpcValue.Boolean = function XRV_Boolean(aValue) {
  var value = new XmlRpcValue(flockIXmlRpcValue.TYPE_BOOLEAN);
  value.BooleanValue = aValue;
  return value;
}
XmlRpcValue.String = function XRV_String(aValue) {
  var value = new XmlRpcValue(flockIXmlRpcValue.TYPE_STRING);
  value.StringValue = aValue;
  return value;
}
XmlRpcValue.Double = function XRV_Double(aValue) {
  var value = new XmlRpcValue(flockIXmlRpcValue.TYPE_DOUBLE);
  value.DoubleValue = aValue;
  return value;
}
XmlRpcValue.DateTime = function XRV_DateTime(aValue) {
  var value = new XmlRpcValue(flockIXmlRpcValue.TYPE_DATETIME);
  value.DateTimeValue = aValue.getTime() / 1000;
  return value;
}
XmlRpcValue.Array = function XRV_Array(aValue) {
  var value = new XmlRpcValue(flockIXmlRpcValue.TYPE_ARRAY);
  value._value = aValue;
  value.ArrayElements = function XRV_Array_ArrayElements(aCount) {
    aCount.value = this._value.length;
    return this._value;
  };
  return value;
}
XmlRpcValue.Struct = function XRV_Struct(aValue) {
  var value = new XmlRpcValue(flockIXmlRpcValue.TYPE_STRUCT);
  value._value = aValue;
  value.StructKeys = function XRV_Struct_StructKeys(aCount) {
    var keys = [];
    for (var x in this._value) {
      keys.push (x);
    }
    aCount.value = keys.length;
    return keys;
  };
  value.StructItem = function XRV_Struct_StructItem(aKey) {
    if (this._value[aKey]) {
      return this._value[aKey];
    } else {
      return null;
    }
  };
  return value;
}
XmlRpcValue.Base64 = function XRV_Base64(aValue) {
  var value = new XmlRpcValue(flockIXmlRpcValue.TYPE_BASE64);
  value.Base64Value = aValue;
  return value;
}
XmlRpcValue.FromJavaScript = function XRV_FromJavaScript(value) {
  if (value == null || value == undefined) {
    return null;
  }
  switch (value.constructor) {
    case Number:
      if (Math.floor(value) != value) {
        return this.Double(value);
      } else {
        return this.Int(value);
      }
    case Boolean:
      return this.Boolean(value);
    case String:
      return this.String(value);
    case Date:
      return this.DateTime(value);
    case Array:
      var arr = [];
      for (var i in value) {
        var v = this.FromJavaScript(value[i]);
        if (v != null && v != undefined) {
          arr.push (v);
        }
      }
      return this.Array(arr);
    case Object:
      var obj = new Object ();
      for (var k in value) {
        var v = this.FromJavaScript(value[k]);
        if (v != null && v != undefined) {
          obj[k] = v;
        }
      }
      return this.Struct(obj);
    case XmlRpcValue:
      // its already the right type
      return value;
    case Function:
      // ignore methods
      return null;
    default:
      throw "error in XmlRpcValue.FromJavaScript converting "
            + value + " "
            + "(type " + value.constructor + ")";
  }
}
XmlRpcValue.ToJavaScript = function XRV_ToJavaScript(value) {
  if (value == undefined) {
    return null;
  }
  // FIXME: why the next two blocks?
  if (value.QueryInterface) {
    value = value.QueryInterface(flockIXmlRpcValue);
    if (value == null) {
      return null;
    }
  } else {
    return null;
  }
  try {
    value = value.QueryInterface(flockIXmlRpcValue);
  } catch (ex) {
    return value;
  }

  switch (value.XmlRpcType) {
    case flockIXmlRpcValue.TYPE_INT:
      return value.IntValue;
    case flockIXmlRpcValue.TYPE_BOOLEAN:
      return value.BooleanValue;
    case flockIXmlRpcValue.TYPE_STRING:
      return value.StringValue;
    case flockIXmlRpcValue.TYPE_DOUBLE:
      return value.DoubleValue;
    case flockIXmlRpcValue.TYPE_DATETIME:
      var date = new Date();
      date.setTime(value.DateTimeValue * 1000);
      return date;
    case flockIXmlRpcValue.TYPE_ARRAY:
      var arr = [];
      var elements = value.ArrayElements({});
      for (var i in elements) {
        arr.push(XmlRpcValue.ToJavaScript(elements[i]));
      }
      return arr;
    case flockIXmlRpcValue.TYPE_STRUCT:
      var keys = value.StructKeys({});
      var struct = {};
      for (var i in keys) {
        var key = keys[i];
        var xmlval = value.StructItem(key);
        var val = XmlRpcValue.ToJavaScript(xmlval);
        struct[key] = val;
      }
      return struct;
    case flockIXmlRpcValue.TYPE_BASE64:
      return value.Base64Value;
    default:
      throw "error in XmlRpcValue.ToJavaScript";
  }
}



function flockXmlRpcServer(aURL) {
  this._server = Components.classes['@flock.com/xmlrpc/server;1']
                           .createInstance(flockIXmlRpcServer);
  this._server.init(aURL);
}

flockXmlRpcServer.prototype.call =
function XRS_call(aMethod, aArguments, aListener) {
  var args = XmlRpcValue.FromJavaScript(aArguments);
  var listener = {
    onResult: function XRS_call_onResult(aResult) {
      aListener.onResult(XmlRpcValue.ToJavaScript(aResult));
    },
    onFault: function XRS_call_onFault(aFaultCode, aFaultString) {
      aListener.onFault(aFaultCode, aFaultString);
    },
    onError: function XRS_call_onError(aMessage) {
      aListener.onError(aMessage);
    }
  };
  this._server.call(aMethod, args, listener);
}

flockXmlRpcServer.prototype.makeProxy =
function XRS_makeProxy(aMethods) {
  var proxy = { __server: this };

  for each (var method_name in aMethods) {
    var property = method_name.replace(/\./g, '_');
    proxy[property] = function XRS_makeProxy_property() {
      if (arguments.length < 1) {
        throw "not enough arguments";
      }
      proxy.__server.call(method_name,
                          arguments.slice(0, arguments.length - 1),
                          arguments[arguments.length - 1]);
    }
  }
  return proxy;
}
