let RPC = {
  _sdup: ffi('void *strdup(char *)'),
  _ah: ffi('void *mgos_rpc_add_handler(void *, void (*)(void *, char *, char *, userdata), userdata)'),
  _resp: ffi('bool mgos_rpc_send_response(void *, char *)'),
  _call: ffi('bool mgos_rpc_call(char *, char *, char *, void (*)(char *, int, char *, userdata), userdata)'),
  _err: ffi('bool mg_rpc_send_errorf(void *, int, char *, char *)'),

  _ahcb: function(ri, args, src, ud) {
    // NOTE(lsm): not using `this` here deliberately. Calleth from C.
    let resp = ud.cb(JSON.parse(args || 'null'), src, ud.ud);
    if (typeof(resp) === 'object' && typeof(resp.error) === 'number') {
      RPC._err(ri, resp.error, '%s', resp.message || '');
    } else {
      RPC._resp(ri, JSON.stringify(resp));
    }
    // NOTE: we don't call ffi_cb_free here because this handler might be used
    // more than once
  },

  _ccb: function(res, code, msg, ud) {
    ud.cb(res ? JSON.parse(res) : null, code, msg, ud.ud);
    ffi_cb_free(RPC._ccb, ud);
  },

  LOCAL: "RPC.LOCAL",

  // ## **`RPC.addHandler(name, handler)`**
  // Add RPC handler. `name` is a string like `'MyMethod'`, `handler`
  // is a callback function which takes `args` arguments object.
  // If a handler returns an object with a numeric `error` attribute and
  // optional `message` string attribute, the caller will get a failure.
  //
  // Return value: none.
  //
  // Example:
  // ```javascript
  // RPC.addHandler('Sum', function(args) {
  //   if (typeof(args) === 'object' && typeof(args.a) === 'number' &&
  //       typeof(args.b) === 'number') {
  //     return args.a + args.b;
  //   } else {
  //     return {error: -1, message: 'Bad request. Expected: {"a":N1,"b":N2}'};
  //   }
  // });
  // ```
  addHandler: function(name, cb, ud) {
    let data = {cb: cb, ud: ud};
    // TODO(lsm): get rid of this strdup() leak. One-off, but still ugly.
    this._ah(this._sdup(name), this._ahcb, data);
  },

  // ## **`RPC.call(dst, method, args, callback)`**
  // Call remote or local RPC service.
  // Return value: true in case of success, false otherwise.
  //
  // If `dst` is empty, connected server is implied. `method` is a string
  // like "MyMethod", `callback` is a callback function which takes the following
  // arguments: res (results object), err_code (0 means success, or error code
  // otherwise), err_msg (error messasge for non-0 error code), userdata. Example:
  //
  // ```javascript
  // RPC.call(RPC.LOCAL, 'Config.Save', {reboot: true}, function (resp, ud) {
  //   print('Response:', JSON.stringify(resp));
  // }, null);
  // ```
  call: function(dst, name, args, cb, ud) {
    let data = {cb: cb, ud: ud};
    return this._call(dst, name, JSON.stringify(args), this._ccb, data);
  },
};

