/*

* MessagePack RPC base class
*
*  (C) 2017 Hiroshi Kuwagata <kgt9221@gmail.com>
*/

if (!msgpack) {

throw "msgpack-lite is not load yet"

}

(function () {

/* declar local symbol */
const newId       = Symbol('newId');
const callback    = Symbol('callback');
const recv        = Symbol('recv');

/* declar contant */
const DEFAULT_URL = `ws://${location.hostname}:${parseInt(location.port)+1}/`;
const COEDEC      = msgpack.createCodec({binarraybuffer:true, preset:true});
const ENC_OPT     = {codec:COEDEC};

msgpack.rpc = class {
  /*
   * core functions
   */
  constructor (url) {
    this.url      = url || DEFAULT_URL
    this.sock     = null;
    this.deferred = null;
    this.maxId    = 1;
    this.handlers = {}
  }

  [newId]() {
    return this.maxId++;
  }

  [callback](name, args) {
    if (this.handlers[name]) {
      this.handlers[name](...args);
    } else {
      throw `unhandled notification received (${name}).`;
    }
  }

  [recv](data) {
    var self;
    var msg;
    var type;
    var id;
    var meth;
    var err;
    var res;
    var para;
    var $df;

    msg  = msgpack.decode(new Uint8Array(data), ENC_OPT);
    type = msg[0];

    switch (type) {
    case 1:
      id  = msg[1];
      err = msg[2];
      res = msg[3];
      $df = this.deferred[id];

      if ($df) {
        if (err) {
          $df.reject(err);
        } else {
          $df.resolve(res);
        }
      }

      delete this.deferred[id];
      break;

    case 2:
      meth = msg[1];
      para = msg[2];

      if (!para) {
        para = [];
      } else if (!(para instanceof Array)) {
        para = [para];
      }

      this[callback](meth, para);
      break;

    default:
      throw `Illeagal data (type=${type}) recevied.`;
    }
  }

  remoteCall(meth) {
    var id;
    var $df;
    var args;

    id   = this[newId]();
    $df  = new $.Deferred();
    args = Array.prototype.slice.call(arguments, 1);

    switch (args.length) {
    case 0:
      args = null;
      break;

    case 1:
      args = args[0];
      break;
    }

    this.deferred[id] = $df;
    this.sock.send(msgpack.encode([0, id, meth, args], ENC_OPT));

    return $df.promise();
  }

  remoteNotify(meth) {
    var args;

    args = Array.prototype.slice.call(arguments, 1);

    switch (args.length) {
    case 0:
      args = null;
      break;

    case 1:
      args = args[0];
      break;
    }

    this.sock.send(msgpack.encode([2, meth, args], ENC_OPT));
  }

  on(name, func) {
    this.handlers[name] = func;
    return this;
  }

  start() {
    var $df;

    $df = new $.Deferred();

    if (!this.sock) {
      this.sock = new WebSocket(this.url);

      this.sock.binaryType = "arraybuffer";

      this.sock.onopen = () => {
        this.deferred = {}
        $df.resolve();
      };

      this.sock.onerror = () => {
        $df.reject();
      };

      this.sock.onmessage = (m) => {
        this[recv](m.data);
      };

      this.sock.onclose = () => {
        this[callback]('session_closed', []);
        this.sock = null;
      };
    }

    return $df.promise();
  }

  finish() {
    var id;

    this.sock.close();

    for (id in this.deferred) {
      this.deferred[id].reject("session finished");
      delete this.deferred[id];
    }

    this.sock     = null;
    this.deferred = null;
  }
}

})();