var ChildProcess = require('child_process');
var IS_WIN = process.platform === 'win32';
var TableParser = require('table-parser');
/**
 * End of line.
 * Basically, the EOL should be:
 * - windows: \r\n
 * - *nix: \n
 * But i'm trying to get every possibilities covered.
 */
var EOL = /(\r\n)|(\n\r)|\n|\r/;
var SystemEOL = require('os').EOL;

/**
 * Execute child process
 * @type {Function}
 * @param {String[]} args
 * @param {Function} callback
 * @param {Object=null} callback.err
 * @param {Object[]} callback.stdout
 */

var Exec = module.exports = exports = function (args, callback) {
  if (Array.isArray(args)) {
    args = args.join(' ');
  }

  // on windows, if use ChildProcess.exec(`wmic process get`), the stdout will gives you nothing
  // that's why I use `cmd` instead
  if (IS_WIN) {

    var spawn = ChildProcess.spawn;
    var CMD = spawn('cmd');
    var stdout = [];
    var stderr = null;

    CMD.stdout.on('data', function (data) {
      stdout += data.toString();
    });

    CMD.stderr.on('data', function (data) {

      if (stderr === null) {
        stderr = data.toString();
      }
      else {
        stderr += data.toString();
      }
    });

    CMD.on('exit', function () {

      var beginRow;
      stdout = stdout.split(EOL);

      // Find the line index for the titles
      stdout.forEach(function (out, index) {
        if (out && typeof beginRow == 'undefined' && out.indexOf('CommandLine') === 0) {
          beginRow = index;
        }
      });

      // get rid of the start (copyright) and the end (current pwd)
      stdout.splice(stdout.length - 1, 1);
      stdout.splice(0, beginRow);

      callback(stderr, stdout.join(SystemEOL) || false);
    });

    CMD.stdin.write('wmic process get ProcessId,CommandLine \n');
    CMD.stdin.end();
  }
  else {
    ChildProcess.exec('ps ' + args, function (err, stdout, stderr) {

      if (err || stderr) {
        return callback(err || stderr.toString());
      }
      else {
        stdout = stdout.toString();
        callback(null, stdout || false);
      }
    });
  }
};

/**
 * Query Process: Focus on pid & cmd
 * @param query
 * @param {String|String[]} query.pid
 * @param {String} query.command RegExp String
 * @param {String} query.arguments RegExp String
 * @param {Function} callback
 * @param {Object=null} callback.err
 * @param {Object[]} callback.processList
 * @return {Object}
 */

exports.lookup = function (query, callback) {

  /**
   * add 'l' as default ps arguments, since the default ps output in linux like "ubuntu", wont include command arguments
   */
  var exeArgs = query.psargs || ['l'];
  var filter = {};
  var idList;

  // Lookup by PID
  if (query.pid) {

    if (Array.isArray(query.pid)) {
      idList = query.pid;
    }
    else {
      idList = [query.pid];
    }

    // Cast all PIDs as Strings
    idList = idList.map(function (v) {
      return String(v);
    });

  }


  if (query.command) {
    filter['command'] = new RegExp(query.command, 'i');
  }

  if (query.arguments) {
    filter['arguments'] = new RegExp(query.arguments, 'i');
  }

  if (query.ppid) {
    filter['ppid'] = new RegExp(query.ppid);
  }

  return Exec(exeArgs, function (err, output) {
    if (err) {
      return callback(err);
    }
    else {
      var processList = parseGrid(output);
      var resultList = [];

      processList.forEach(function (p) {

        var flt;
        var type;
        var result = true;

        if (idList && idList.indexOf(String(p.pid)) < 0) {
          return;
        }

        for (type in filter) {
          flt = filter[type];
          result = flt.test(p[type]) ? result : false;
        }

        if (result) {
          resultList.push(p);
        }
      });

      callback(null, resultList);
    }
  });
};

/**
 * Kill process
 * @param pid
 * @param opts
 * @param next
 */

exports.kill = function( pid, opts, next ){
  //opts are optional
  if(arguments.length == 2 && typeof opts == 'function'){
    next = opts;
    opts = {};
  }

  var killCommand = IS_WIN ? 'taskkill ' : 'kill ';

  //For Unix/Mac add the signal to command if provided
  if(!IS_WIN && opts && opts.signal){
    killCommand += '-' + opts.signal + ' ';
  }

  var command = killCommand + ( IS_WIN ? '/F /PID ' + pid : pid );
  ChildProcess.exec(command, function (err, stdout, stderr) {
    if (typeof next !== 'function') return;
    if ((err || stderr)) {
      next(err || stderr.toString());
    }
    else {
      stdout = stdout.toString();

      // on windows, you can still get the killed process if you look it up immediately after you killed it
      // so wait for an extra 200ms
      if (IS_WIN) {
        setTimeout(function () {
          next(null, stdout);
        }, 200);
      }
      else {
        next(null, stdout);
      }
    }
  });
};

/**
 * Parse the stdout into readable object.
 * @param {String} output
 */

function parseGrid(output) {
  if (!output) {
    return output;
  }
  return formatOutput(TableParser.parse(output));
}

/**
 * format the structure, extract pid, command, arguments, ppid
 * @param data
 * @return {Array}
 */

function formatOutput(data) {
  var formatedData = [];
  data.forEach(function (d) {
    var pid = ( d.PID && d.PID[0] ) || ( d.ProcessId && d.ProcessId[0] ) || undefined;
    var cmd = d.CMD || d.CommandLine || d.COMMAND || undefined;
    var ppid = ( d.PPID && d.PPID[0] ) || undefined;

    if (pid && cmd) {
      var command = cmd[0];
      var args = '';

      if (cmd.length > 1) {
        args = cmd.slice(1);
      }

      formatedData.push({
        pid: pid,
        command: command,
        arguments: args,
        ppid: ppid
      });
    }
  });

  return formatedData;
}

@@ -1750,16 +1750,32 @@
 ocessId,
+ParentProcessId,
 CommandL
@@ -5401,32 +5401,81 @@
 & d.PPID%5B0%5D ) %7C%7C
+ ( d.ParentProcessId && d.ParentProcessId%5B0%5D ) %7C%7C
  undefined;%0A%0A   

Add support for windows parent process ID