var util = require('util');
var EventEmitter = require('events').EventEmitter;
var sio = require('socket.io')();
var SioSocket = require('./siosocket');

var PKG_ID_BYTES = 4;
var PKG_ROUTE_LENGTH_BYTES = 1;
var PKG_HEAD_BYTES = PKG_ID_BYTES + PKG_ROUTE_LENGTH_BYTES;

var curId = 1;

/**
 * Connector that manager low level connection and protocol bewteen server and client.
 * Develper can provide their own connector to switch the low level prototol, such as tcp or probuf.
 */
var Connector = function (port, host, opts) {
  if (!(this instanceof Connector)) {
    return new Connector(port, host, opts);
  }

  EventEmitter.call(this);
  this.port = port;
  this.host = host;
  this.opts = opts;
  this.heartbeats = opts.heartbeats || true;
  this.closeTimeout = opts.closeTimeout || 60;
  this.heartbeatTimeout = opts.heartbeatTimeout || 60;
  this.heartbeatInterval = opts.heartbeatInterval || 25;
};

util.inherits(Connector, EventEmitter);

module.exports = Connector;

/**
 * Start connector to listen the specified port
 */
Connector.prototype.start = function (cb) {
  var self = this;
  // issue https://github.com/NetEase/pomelo-cn/issues/174
  if (!!this.opts) {
    this.wsocket = sio.listen(this.port, this.opts);
  }
  else {
    this.wsocket = sio.listen(this.port, {
      transports: [
        'websocket', 'htmlfile', 'xhr-polling', 'jsonp-polling', 'flashsocket'
      ]
    });
  }
  this.wsocket.set('close timeout', this.closeTimeout);
  this.wsocket.set('heartbeat timeout', this.heartbeatTimeout);
  this.wsocket.set('heartbeat interval', this.heartbeatInterval);
  this.wsocket.set('heartbeats', this.heartbeats);
  // this.wsocket.set('log level', 1);
  this.wsocket.sockets.on('connection', function (socket) {
    var siosocket = new SioSocket(curId++, socket);
    self.emit('connection', siosocket);
    siosocket.on('closing', function (reason) {
      siosocket.send({ route: 'onKick', reason: reason });
    });
  });

  process.nextTick(cb);
};

/**
 * Stop connector
 */
Connector.prototype.stop = function (force, cb) {
  this.wsocket.server.close();
  process.nextTick(cb);
};

Connector.encode = Connector.prototype.encode = function (reqId, route, msg) {
  if (reqId) {
    return composeResponse(reqId, route, msg);
  } else {
    return composePush(route, msg);
  }
};

/**
 * Decode client message package.
 *
 * Package format:
 *   message id: 4bytes big-endian integer
 *   route length: 1byte
 *   route: route length bytes
 *   body: the rest bytes
 *
 * @param  {String} data socket.io package from client
 * @return {Object}      message object
 */
Connector.decode = Connector.prototype.decode = function (msg) {
  var index = 0;

  var id = parseIntField(msg, index, PKG_ID_BYTES);
  index += PKG_ID_BYTES;

  var routeLen = parseIntField(msg, index, PKG_ROUTE_LENGTH_BYTES);

  var route = msg.substr(PKG_HEAD_BYTES, routeLen);
  var body = msg.substr(PKG_HEAD_BYTES + routeLen);

  return {
    id: id,
    route: route,
    body: JSON.parse(body)
  };
};

var composeResponse = function (msgId, route, msgBody) {
  return {
    id: msgId,
    body: msgBody
  };
};

var composePush = function (route, msgBody) {
  return JSON.stringify({ route: route, body: msgBody });
};

var parseIntField = function (str, offset, len) {
  var res = 0;
  for (var i = 0; i < len; i++) {
    if (i > 0) {
      res <<= 8;
    }
    res |= str.charCodeAt(offset + i) & 0xff;
  }

  return res;
};