var fs = require("fs");
var path = require("path");
var child_process = require('child_process');

var files = require(path.join(__dirname, '..', 'lib', 'files.js'));

var _ = require('underscore');


/** Internal.
 *
 * If passed, app_dir and port act as filters on the list of running mongos.
 *
 * callback is called with (err, [{pid, port, app_dir}])
 */
var find_mongo_pids = function (app_dir, port, callback) {
  // 'ps ax' should be standard across all MacOS and Linux.
  child_process.exec('ps ax',
    function (error, stdout, stderr) {
      if (error) {
        callback({reason: error});
      } else {
        var pids = [];

        _.each(stdout.split('\n'), function (ps_line) {
          // matches mongos we start.
          var m = ps_line.match(/^\s*(\d+).+mongod .+--port (\d+) --dbpath (.+)(?:\/|\\)\.meteor(?:\/|\\)local(?:\/|\\)db\s*$/);
          if (m && m.length === 4) {
            var found_pid =  parseInt(m[1]);
            var found_port = parseInt(m[2]);
            var found_path = m[3];

            if ( (!port || port === found_port) &&
                 (!app_dir || app_dir === found_path)) {
              pids.push({
                pid: found_pid, port: found_port, app_dir: found_path});
            }
          }
        });

        callback(null, pids);
      }
    });
};


// See if mongo is running already. Callback takes a single argument,
// 'port', which is the port mongo is running on or null if mongo is not
// running.
exports.find_mongo_port = function (app_dir, callback) {
  find_mongo_pids(app_dir, null, function (err, pids) {
    if (err || pids.length !== 1) {
      callback(null);
      return;
    }

    var pid = pids[0].pid;
    try {
      process.kill(pid, 0); // make sure it is still alive
    } catch (e) {
      callback(null);
      return;
    }

    callback(pids[0].port);
  });
}


// Try to kill any other mongos running on our port. Calls callback
// once they are all gone. Callback takes one arg: err (falsy means all
// good).
//
// This is a big hammer for dealing with still running mongos, but
// smaller hammers have failed before and it is getting tiresome.
var find_mongo_and_kill_it_dead = function (port, callback) {
  find_mongo_pids(null, port, function (err, pids) {
    if (err) {
      callback(err);
      return;
    }

    if (pids.length) {
      // Send kill attempts and wait. First a SIGINT, then if it isn't
      // dead within 2 sec, SIGKILL. This goes through the list
      // serially, but thats OK because there really should only ever be
      // one.
      var attempts = 0;
      var dead_yet = function () {
        attempts = attempts + 1;
        var pid = pids[0].pid;
        var signal = 0;
        if (attempts === 1)
          signal = 'SIGINT';
        else if (attempts === 20 || attempts === 30)
          signal = 'SIGKILL';
        try {
          process.kill(pid, signal);
        } catch (e) {
          // it's dead. remove this pid from the list.
          pids.shift();

          // if no more in the list, we're done!
          if (!pids.length) {
            callback();
            return;
          }
        }
        if (attempts === 40) {
          // give up after 4 seconds.
          callback({
            reason: "Can't kill running mongo (pid " + pid + ")."});
          return;
        }

        // recurse
        setTimeout(dead_yet, 100);
      };
      dead_yet();

    } else {
      // nothing to kill, fire OK callback
      callback();
    }
  });
};

exports.launch_mongo = function (app_dir, port, launch_callback, on_exit_callback) {
  var handle = {stop: function (callback) { callback(); } };
  launch_callback = launch_callback || function () {};
  on_exit_callback = on_exit_callback || function () {};

  // If we are passed an external mongo, assume it is launched and never
  // exits. Matches code in run.js:exports.run.

  // Since it is externally managed, asking it to actually stop would be
  // impolite, so our stoppable handle is a noop
  if (process.env.MONGO_URL) {
    launch_callback();
    return handle;
  }

  var mongod_path = path.join(files.get_dev_bundle(),
                              'mongodb',
                              'bin',
                              'mongod');

  // store data in app_dir
  var data_path = path.join(app_dir, '.meteor', 'local', 'db');
  files.mkdir_p(data_path, 0755);
  // add .gitignore if needed.
  files.add_to_gitignore(path.join(app_dir, '.meteor'), 'local');

  find_mongo_and_kill_it_dead(port, function (err) {
    if (err) {
      launch_callback({reason: "Can't kill running mongo: " + err.reason});
      return;
    }

    var proc = child_process.spawn(mongod_path, [
      '--bind_ip', '127.0.0.1',
      '--smallfiles',
      '--port', port,
      '--dbpath', data_path
    ]);
    handle.stop = function (callback) {
      var tries = 0;
      var exited = false;
      proc.removeListener('exit', on_exit_callback);
      proc.kill('SIGINT');
      callback && callback(err);
    };

    proc.on('exit', on_exit_callback);

    proc.stdout.setEncoding('utf8');
    proc.stdout.on('data', function (data) {
      // process.stdout.write(data);
      if (/ \[initandlisten\] waiting for connections on port/.test(data))
        launch_callback();
    });
  });
  return handle;
};
