// Generated by CoffeeScript 1.10.0

/*
  @author Gilles Gerlinger
  Copyright 2016. All rights reserved.
 */

(function() {
  var Channel, ChannelQ, Promise, Remote, Rpc, classServer, cnf, getSession, json, log, sseChannel;

  Promise = global.Promise || require('./promise');

  log = require('./log');

  cnf = require('./config');

  sseChannel = 'c hannel';

  Rpc = (function() {
    var _return;

    function Rpc(local1) {
      this.local = local1;
    }

    Rpc.prototype.process = function(msg, res) {
      var e, error, ref, rep, uid;
      log(msg.id + " in", msg);
      if (msg.method === cnf.sse) {
        if (typeof this.local[cnf.sse] === 'function') {
          _return(msg, {
            rep: uid = "r-" + (cnf.random())
          }, res);
          return this.local[cnf.sse](new Remote(this.local, msg), uid);
        } else {
          return _return(msg, {
            err: "error: no _remoteReady method for channel " + msg.args[0]
          }, res);
        }
      } else if (msg.method === cnf.srv) {
        return this.local[sseChannel].resolve(msg);
      } else if (this.local[msg.method]) {
        try {
          rep = (ref = this.local)[msg.method].apply(ref, msg.args);
          if (rep && typeof rep.then === 'function') {
            rep.then(function(rep) {
              return _return(msg, {
                rep: rep
              }, res);
            });
            return rep["catch"](function(err) {
              return _return(msg, {
                err: err
              }, res);
            });
          } else {
            return _return(msg, {
              rep: rep
            }, res);
          }
        } catch (error) {
          e = error;
          return _return(msg, {
            err: "error in " + msg.method + ": " + e
          }, res);
        }
      } else {
        return _return(msg, {
          err: "error: method " + msg.method + " is unknown"
        }, res);
      }
    };

    _return = function(msg, rep, res) {
      log(msg.id + " out", rep);
      return res.send(rep);
    };

    return Rpc;

  })();

  getSession = function(msg) {
    if (msg.id) {
      return msg.id.split('-')[0];
    }
  };

  classServer = (function() {
    function classServer(classes, timeOut) {
      var Class;
      this.timeOut = timeOut != null ? timeOut : cnf.sessionTimeOut;
      for (Class in classes) {
        if (typeof classes[Class] === 'function') {
          this["def " + Class] = {
            Class: classes[Class],
            sessions: []
          };
        }
      }
      this["def " + cnf.p2p] = {
        Class: require('./p2p'),
        sessions: []
      };
    }

    classServer.prototype.process = function(Class, msg, res) {
      var rpc, uid;
      uid = getSession(msg);
      if (rpc = this["def " + Class].sessions[uid]) {
        clearTimeout(rpc.timeOut);
      } else {
        this["def " + Class].sessions[uid] = rpc = new Rpc(new this["def " + Class].Class());
        this._echo(Class, 'adding', uid);
      }
      rpc.timeOut = setTimeout((function(_this) {
        return function() {
          delete _this["def " + Class].sessions[uid];
          return _this._echo(Class, 'removing', uid);
        };
      })(this), this.timeOut);
      return rpc.process(msg, res);
    };

    classServer.prototype._echo = function(Class, operation, uid) {
      return log(operation + " " + Class + " session " + uid + " (# sessions: " + (Object.keys(this["def " + Class].sessions).length) + ")");
    };

    return classServer;

  })();

  json = function(req, res, next) {
    var body;
    body = [];
    req.on('data', function(chunk) {
      return body.push(chunk);
    });
    return req.on('end', function() {
      req.body = Buffer.concat(body).toString();
      if (req.headers['content-type'] && req.headers['content-type'].indexOf('application/json') > -1) {
        req.body = JSON.parse(req.body);
        if (!res.send) {
          res.send = function(msg) {
            return res.end(JSON.stringify(msg));
          };
        }
      }
      return next();
    });
  };

  module.exports = function(app, classes, options) {
    var Class, fn, server;
    if (options == null) {
      options = {};
    }
    server = new classServer(classes, options.timeOut);
    app.use(json);
    app.post("/" + (encodeURIComponent(cnf.p2p)), function(req, res) {
      return server.process(cnf.p2p, req.body, res);
    });
    fn = function(Class) {
      log("listening on class " + Class);
      return app.post("/" + (encodeURIComponent(Class)), function(req, res) {
        return server.process(Class, req.body, res);
      });
    };
    for (Class in classes) {
      fn(Class);
    }
    return app.get("/" + cnf.tag, function(req, res, next) {
      if (req.headers.accept && req.headers.accept === 'text/event-stream') {
        return new Channel(req, res);
      }
    });
  };

  Remote = (function() {
    var send;

    function Remote(local, msg) {
      var count, fn, i, len, method, ref, uid;
      local[sseChannel] = new ChannelQ();
      this._sseChannel = Channel.channels[msg.args[0]];
      count = 0;
      uid = getSession(msg);
      ref = msg.args[1] || [];
      fn = (function(_this) {
        return function(method) {
          return _this[method] = function() {
            return send(_this._sseChannel, local[sseChannel], {
              method: method,
              args: [].slice.call(arguments),
              id: uid + "-s" + (++count)
            });
          };
        };
      })(this);
      for (i = 0, len = ref.length; i < len; i++) {
        method = ref[i];
        fn(method);
      }
    }

    send = function(channel, q, msg) {
      return new Promise(function(resolve, reject) {
        q.push(msg, resolve);
        return channel.send(msg);
      });
    };

    return Remote;

  })();

  Channel = (function() {
    Channel.channels = [];

    function Channel(req, resp) {
      var msg;
      this.resp = resp;
      Channel.channels[this.uid = "c-" + (cnf.random())] = this;
      log("SSE out " + this.uid, msg = {
        uid: this.uid
      });
      this.resp.writeHead(200, {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        Connection: 'keep-alive',
        'Access-Control-Allow-Origin': '*'
      });
      this.resp.write("event: " + cnf.tag + "\ndata: " + (JSON.stringify(msg)) + "\n\n");
      req.on('close', (function(_this) {
        return function() {
          log('SSE', _this.uid, 'closed');
          delete Channel.channels[_this.uid];
          return _this.closed = true;
        };
      })(this));
    }

    Channel.prototype.send = function(msg) {
      log(msg.id + " out " + this.uid, msg);
      return this.resp.write("event: " + cnf.tag + "/" + this.uid + "\ndata: " + (JSON.stringify(msg)) + "\n\n");
    };

    return Channel;

  })();

  ChannelQ = (function() {
    function ChannelQ() {
      this.queue = [];
    }

    ChannelQ.prototype.push = function(msg, resolve) {
      return this.queue[msg.id] = resolve;
    };

    ChannelQ.prototype.resolve = function(msg) {
      var resolve;
      if (resolve = this.queue[msg.id]) {
        resolve(msg.args);
        return delete this.queue[msg.id];
      }
    };

    return ChannelQ;

  })();

  process.on('uncaughtException', function(err) {
    return console.log('Caught exception: ', err.stack);
  });

}).call(this);
