'use strict';

var serversMap = module.exports._serversMap = {};
var dgramMap = module.exports._dgramMap = {};
var PromiseA = require('bluebird');

module.exports.addTcpListener = function (port, handler) {
  return new PromiseA(function (resolve, reject) {
    var stat = serversMap[port];

    if (stat) {
      if (stat._closing) {
        stat.server.destroy();
      } else {
        // We're already listening on the port, so we only have 2 options. We can either
        // replace the handler or reject with an error. (Though neither is really needed
        // if the handlers are the same). Until there is reason to do otherwise we are
        // opting for the replacement.
        stat.handler = handler;
        resolve();
        return;
      }
    }

    var enableDestroy = require('server-destroy');
    var net = require('net');
    var resolved;
    var server = net.createServer({allowHalfOpen: true});

    stat = serversMap[port] = {
      server: server
    , handler: handler
    , _closing: false
    };

    // Add .destroy so we can close all open connections. Better if added before listen
    // to eliminate any possibility of it missing an early connection in it's records.
    enableDestroy(server);

    server.on('connection', function (conn) {
      conn.__port = port;
      conn.__proto = 'tcp';
      stat.handler(conn);
    });
    server.on('close', function () {
      console.log('TCP server on port %d closed', port);
      delete serversMap[port];
    });
    server.on('error', function (e) {
      if (!resolved) {
        reject(e);
      } else if (handler.onError) {
        handler.onError(e);
      } else {
        throw e;
      }
    });

    server.listen(port, function () {
      resolved = true;
      resolve();
    });
  });
};
module.exports.closeTcpListener = function (port, timeout) {
  return new PromiseA(function (resolve) {
    var stat = serversMap[port];
    if (!stat) {
      resolve();
      return;
    }
    stat._closing = true;

    var timeoutId;
    if (timeout) {
      timeoutId = setTimeout(() => stat.server.destroy(), timeout);
    }

    stat.server.once('close', function () {
      clearTimeout(timeoutId);
      resolve();
    });
    stat.server.close();
  });
};
module.exports.destroyTcpListener = function (port) {
  var stat = serversMap[port];
  if (stat) {
    stat.server.destroy();
  }
};
module.exports.listTcpListeners = function () {
  return Object.keys(serversMap).map(Number).filter(function (port) {
    return port && !serversMap[port]._closing;
  });
};


module.exports.addUdpListener = function (port, handler) {
  return new PromiseA(function (resolve, reject) {
    var stat = dgramMap[port];

    if (stat) {
      // we'll replace the current listener
      stat.handler = handler;
      resolve();
      return;
    }

    var dgram = require('dgram');
    var server = dgram.createSocket({type: 'udp4', reuseAddr: true});
    var resolved = false;

    stat = dgramMap[port] = {
      server: server
    , handler: handler
    };

    server.on('message', function (msg, rinfo) {
      msg._size = rinfo.size;
      msg._remoteFamily = rinfo.family;
      msg._remoteAddress = rinfo.address;
      msg._remotePort = rinfo.port;
      msg._port = port;
      stat.handler(msg);
    });

    server.on('error', function (err) {
      if (!resolved) {
        delete dgramMap[port];
        reject(err);
      }
      else if (stat.handler.onError) {
        stat.handler.onError(err);
      }
      else {
        throw err;
      }
    });

    server.on('close', function () {
      delete dgramMap[port];
    });

    server.bind(port, function () {
      resolved = true;
      resolve();
    });
  });
};
module.exports.closeUdpListener = function (port) {
  var stat = dgramMap[port];
  if (!stat) {
    return PromiseA.resolve();
  }

  return new PromiseA(function (resolve) {
    stat.server.once('close', resolve);
    stat.server.close();
  });
};
module.exports.listUdpListeners = function () {
  return Object.keys(dgramMap).map(Number).filter(Boolean);
};


module.exports.listeners = {
  tcp: {
    add: module.exports.addTcpListener
  , close: module.exports.closeTcpListener
  , destroy: module.exports.destroyTcpListener
  , list: module.exports.listTcpListeners
  }
, udp: {
    add: module.exports.addUdpListener
  , close: module.exports.closeUdpListener
  , list: module.exports.listUdpListeners
  }
};