var bluebird = require('bluebird'); var net = require('net'); var debug = require('debug');

module.exports = {

// Returns a promise that will be resolved with an array of open ports
test: bluebird.method(function(port, iface, callback) {
  var that = this;
  var log = debug('portastic:test');
  var def = bluebird.defer();

  // Callback handling
  def.promise
    .then(function(ports) {
      if (!that._callback(callback, [ports]))
        return ports;
    })
    .catch(function(err) {
      if (!that._callback(callback, [err]))
        throw err;
    });

  if (typeof iface !== 'string' && !callback) {
    callback = iface;
    iface = null;
  }

  var server = net.createServer();

  server.on('error', function(err) {
    if (err.code === 'EADDRINUSE') {
      log('Port %s was in use', port);
      return def.resolve(false);
    }

    def.reject(err);
  });

  server.on('close', function() {
    log('TCP server on port %s closed', port);
    def.resolve(true);
  });

  log('Trying to test port %s', port);
  server.listen(port, iface, function(err) {
    if (err && err.code === 'EADDRINUSE') {
      log('Port %s was in use', port);
      return def.resolve(false);
    }

    if (err)
      return def.reject(err);

    server.close(function(err) {
      if (err)
        return def.reject(err);

      log('Port %s was free', port);
    });
  });

  return def.promise;
}),

// Filter ports that are in use
filter: bluebird.method(function(ports, iface, callback) {
  var that = this;
  var log = debug('portastic:filter');
  var def = bluebird.defer();

  // Callback handling
  def.promise
    .then(function(ports) {
      if (!that._callback(callback, [ports]))
        return ports;
    })
    .catch(function(err) {
      if (!that._callback(callback, [err]))
        throw err;
    });

  if (typeof iface !== 'string' && !callback) {
    callback = iface;
    iface = null;
  }

  bluebird.all(ports)
    .filter(function(port) {
      log('Filtering port %s', port);
      return that.test(port, iface);
    })
    .then(function(free) {
      def.resolve(free);
    })
    .catch(function(err) {
      def.reject(err);
    });

  return def.promise;
}),

// Find open ports in a range
find: bluebird.method(function(options, iface, callback) {
  var log = debug('portastic:find');
  var that = this;
  var ports = [];
  var result = [];

  if (typeof iface !== 'string' && !callback) {
    callback = iface;
    iface = null;
  }

  for (var i = options.min; i <= options.max; i++)
    ports.push(i);

  log('Trying to find open ports between range %s and %s', options.min,
    options.max);

  var promise = bluebird.resolve(ports)
    .each(function(port) {
      return that.test(port, iface)
        .then(function(open) {
          if (options.retrieve && result.length >= options.retrieve) {
            log('Result reached the maximum of %s ports, returning...',
              options.retrieve);
            return promise.cancel();
          }

          if (open) {
            log('Port %s was open, adding it to the result list', port);
            log('Pushing port %s to the result list', port);
            return result.push(port);
          } else
            log('Port %s was not open', port);
        });
    })
    .cancellable()
    .catch(bluebird.CancellationError, function() {
      return;
    })
    .then(function() {
      if (!that._callback(callback, [ports]))
        return result;
    })
    .catch(function(err) {
      if (!that._callback(callback, [err]))
        throw err;
    });

  return promise;
}),

// Handles callbacks
_callback: function(cb, args) {
  if (cb) {
    // This will bypass promises errors catching
    process.nextTick(function() {
      cb.apply(cb, args);
    });
  }

  return !!cb;
}

};