#!/bin/sh

':' // Hack to pass parameters to Node before running this file
':' //; [ -f ~/.pm2/custom_options.sh ] && . ~/.pm2/custom_options.sh || : ; exec "`command -v nodejs || command -v node`" $PM2_NODE_OPTIONS "$0" "$@"

var commander = require('commander');
var fs        = require('fs');
var path      = p = require('path');
var util      = require('util');
var cronJob   = require('cron').CronJob;

var Monit     = require('../lib/Monit');
var UX        = require('../lib/CliUx');
var Log       = require('../lib/Log');
var Satan     = require('../lib/Satan');
var CLI       = require('../lib/CLI');
var cst       = require('../constants.js');
var pkg       = require('../package.json');

commander.version(pkg.version)
  .option('-v --version', 'get version')
  .option('-s --silent', 'hide all messages', false)
  .option('-m --mini-list', 'display a compacted list without formatting')
  .option('-f --force', 'force actions')
  .option('-n --name <name>', 'set a <name> for script')
  .option('-i --instances <number>', 'launch [number] instances (for networked app)(load balanced)')
  .option('-o --output <path>', 'specify out log file')
  .option('-e --error <path>', 'specify error log file')
  .option('-p --pid <pid>', 'specify pid file')
  .option('-x --execute-command', 'execute a program using fork system')
  .option('-u --user <username>', 'define user when generating startup script')
  .option('-c --cron <cron_pattern>', 'restart a running process based on a cron pattern')
  .option('-w --write', 'write configuration in local folder')
  .option('--interpreter <interpreter>', 'the interpreter pm2 should use for executing app (bash, python...)')
  .option('--no-daemon', "run pm2 daemon in the foreground if it doesn't exist already")
  .option('--merge-logs', 'merge logs from different instances but keep error and out separated')
  .option('--watch', 'watch application folder for changes')
  .option('--node-args <node_args>', "space delimited arguments to pass to node in cluster mode - e.g. --node-args=\"--debug=7001 --trace-deprecation\"",
      function(val) {
          return val.split(' ');
          })
  .option('--run-as-user <run_as_user>', 'The user or uid to run a managed process as')
  .option('--run-as-group <run_as_group>', 'The group or gid to run a managed process as')
  .usage('[cmd] app');

commander.on('--help', function() {
        console.log('  Basic Examples:');
        console.log('');
        console.log('    Start an app using all CPUs available + set a name :');
        console.log('    $ pm2 start app.js -i max --name "api"');
        console.log('');
        console.log('    Restart the previous app launched, by name :');
        console.log('    $ pm2 restart api');
        console.log('');
        console.log('    Stop the app :');
        console.log('    $ pm2 stop api');
        console.log('');
        console.log('    Restart the app that is stopped :');
        console.log('    $ pm2 restart api');
        console.log('');
        console.log('    Remove the app from the process list :');
        console.log('    $ pm2 delete api');
        console.log('');
        console.log('    Kill daemon pm2 :');
        console.log('    $ pm2 kill');
        console.log('');
        console.log('    Update pm2 :');
        console.log('    $ npm install pm2@latest -g ; pm2 updatePM2');
        console.log('');
        console.log('    More examples in https://github.com/Unitech/pm2#usagefeatures');
        console.log('');
        });

if (process.argv.indexOf('-s') > -1) {
  global.console = {};
  global.console.log = function(){};
  global.console.error = function(){};
}

if (process.argv.indexOf('--no-daemon') > -1) {
  //
  // Start daemon if it does not exist
  //
  // Function checks if --no-daemon option is present,
  // and starts daemon in the same process if it does not exists
  //
  console.log('pm2 launched in no-daemon mode (you can add DEBUG="*" env variable to get more messages)');
  Satan.pingDaemon(function(ab) {
    if (ab == true) {
      console.error('>> pm2 is already daemonized ! You should kill it before launching it in no-daemon mode'.red);
    }
    Satan.start(true);
  });
}
else
  Satan.start(false);


//
// Helper function to fail when unknown command arguments are passed
//
function failOnUnknown(fn) {
  return function(arg) {
    if (arguments.length > 1) {
      console.log(cst.PREFIX_MSG + '\nUnknown command argument: ' + arg);
      commander.outputHelp();
      process.exit(cst.ERROR_EXIT);
    }
    return fn.apply(this, arguments);
  };
}

//
// Start command
//
commander.command('start <file|json|stdin>')
  .option('--watch', 'Watch folder for changes')
  .description('                start and daemonize an app')
  .action(function(cmd) {

    if (cmd == "-") {
      process.stdin.resume();
      process.stdin.setEncoding('utf8');
      process.stdin.on('data', function (cmd) {
        process.stdin.pause();
        CLI.startFromJson(cmd, 'pipe');
      });
    } else if (cmd.indexOf('.json') > 0)
      CLI.startFromJson(cmd, 'file');
    else
      CLI.startFile(cmd,'file');
  });

//
// Stop specific id
//
commander.command('stop <id|name|all|json|stdin>')
  .option('--watch', 'Stop watching folder for changes')
  .description('          stop a process (to start it again, do pm2 restart <app>)')
  .action(function(param) {
    UX.processing.start();

    if (param == "-") {
      process.stdin.resume();
      process.stdin.setEncoding('utf8');
      process.stdin.on('data', function (param) {
        process.stdin.pause();
        CLI.actionFromJson('stopProcessName', param, 'pipe');
      });
    } else if (param.indexOf('.json') > 0)
      CLI.actionFromJson('stopProcessName', param, 'file');
    else if (param == 'all')
      CLI.stopAll();
    else if (isNaN(parseInt(param))) {
      CLI.stopProcessName(param);
    } else {
      console.log(cst.PREFIX_MSG + 'Stopping process by id ' + param);
      CLI.stopId(param);
    }
  });

//
// Stop All processes
//
commander.command('restart <id|name|all|json|stdin>')
  .option('--watch', 'Toggle watching folder for changes')
  .description('       restart a process')
  .action(function(param) {
    UX.processing.start();

    if (param == "-") {
      process.stdin.resume();
      process.stdin.setEncoding('utf8');
      process.stdin.on('data', function (param) {
        process.stdin.pause();
        CLI.actionFromJson('restartProcessName', param, 'pipe');
      });
    } else if (param.indexOf('.json') > 0)
      CLI.actionFromJson('restartProcessName', param, 'file');
    else if (param == 'all')
      CLI.restartAll();
    else if (isNaN(parseInt(param))) {
      console.log(cst.PREFIX_MSG + 'Restarting process by name ' + param);
      CLI.restartProcessByName(param);
    } else {
      console.log(cst.PREFIX_MSG + 'Restarting process by id ' + param);
      CLI.restartProcessById(param);
    }
  });

//
// Reload process(es)
//
commander.command('reload <name|all>')
  .description('                 reload processes (note that its for app using HTTP/HTTPS)')
  .action(function(pm2_id) {
    UX.processing.start();
    if (pm2_id == 'all')
      CLI.reload('reloadProcessId');
    else
      CLI.reloadProcessName(pm2_id, 'reloadProcessId');
  });

//
// Reload process(es)
//
commander.command('gracefulReload <name|all>')
  .description('              gracefully reload a process. Send a "shutdown" message to close all connections.')
  .action(function(pm2_id) {
    UX.processing.start();
    if (pm2_id == 'all')
        CLI.reload('softReloadProcessId');
    else
        CLI.reloadProcessName(pm2_id, 'softReloadProcessId');
  });

//
// Stop and delete a process by name from database
//
commander.command('delete <name|id|script|all|json|stdin>')
  .description(' stop and delete a process from pm2 process list')
  .action(function(name) {
    if (name == "-") {
      process.stdin.resume();
      process.stdin.setEncoding('utf8');
      process.stdin.on('data', function (param) {
        process.stdin.pause();
        CLI.deleteProcess(param, 'pipe');
      });
    } else
      CLI.deleteProcess(name,'');
  });

//
// Send system signal to process
//
commander.command('sendSignal <signal> <pm2_id|name>')
.description('      send a system signal to the target process')
  .action(function(signal, pm2_id) {
    UX.processing.start();

    if (isNaN(parseInt(pm2_id))) {
      console.log(cst.PREFIX_MSG + 'Sending signal to process name ' + pm2_id);
      CLI.sendSignalToProcessName(signal, pm2_id);
    } else {
      console.log(cst.PREFIX_MSG + 'Sending signal to process id ' + pm2_id);
      CLI.sendSignalToProcessId(signal, pm2_id);
    }
  });

//
// Stop and delete a process by name from database
//
commander.command('ping')
  .description('                 ping pm2 daemon - if not up it will launch it')
  .action(failOnUnknown(CLI.ping));

commander.command('updatePM2')
  .description('                 updatePM2 after a npm update')
  .action(function() {
          CLI.updatePM2()
          });

//
// Interact
//
commander.command('interact <secret_key> <public_key> <machine_name>')
  .description('launch interaction with VitalSigns servers')
  .action(CLI.interact);

commander.command('killInteract')
  .description('stop the interaction with VitalSigns servers')
  .action(failOnUnknown(CLI.killInteract));

commander.command('infoInteract')
  .description('get information about the monitoring connection to VitalSigns')
  .action(failOnUnknown(CLI.infoInteract));

commander.command('subscribe <email>')
  .description('enable simple pm2 monitoring with email notification')
  .action(CLI.subscribe);

commander.command('unsubscribe')
  .description('disable simple pm2 monitoring')
  .action(CLI.unsubscribe);


//
// Web interface
//
commander.command('web')
  .description('launch an health API on port ' + cst.WEB_INTERFACE)
  .action(failOnUnknown(function() {
    console.log('Launching web interface on port ' + cst.WEB_INTERFACE);
    CLI.web();
  }));

//
// Save processes to file
//
commander.command('dump')
  .description('dump all processes for resurrecting them later')
  .action(failOnUnknown(function() {
    console.log(cst.PREFIX_MSG + 'Dumping processes');
    UX.processing.start();
    CLI.dump();
  }));

//
// Save processes to file
//
commander.command('resurrect')
  .description('resurrect previously dumped processes')
  .action(failOnUnknown(function() {
    console.log(cst.PREFIX_MSG + 'Resurrecting');
    UX.processing.start();
    CLI.resurrect();
  }));

//
// Set pm2 to startup
//
commander.command('startup <platform>')
  .description('auto resurect process at startup. [platform] = ubuntu, centos or systemd')
  .action(function(platform) {
    CLI.startup(platform);
  });


//
// Sample generate
//
commander.command('generate <name>')
  .description('generate sample JSON')
  .action(function(name) {
    CLI.generateSample(name);
  });

commander.command('describe <id>')
  .description('describe all parameters of a process id')
  .action(CLI.describeProcess);

commander.command('desc <id>')
  .description('describe all parameters of a process id')
  .action(CLI.describeProcess);

//
// List command
//
commander.command('list')
  .description('list all processes')
  .action(failOnUnknown(CLI.list));

commander.command('ls')
  .description('(alias) list all processes')
  .action(failOnUnknown(CLI.list));

commander.command('l')
  .description('(alias) list all processes')
  .action(failOnUnknown(CLI.list));

commander.command('status')
  .description('(alias) list all processes')
  .action(failOnUnknown(CLI.list));


// List in raw json
commander.command('jlist')
  .description('list all processes in JSON format')
  .action(failOnUnknown(function() {
    CLI.jlist();
  }));

// List in prettified Json
commander.command('prettylist')
  .description('print json in a prettified JSON')
  .action(failOnUnknown(function() {
    CLI.jlist(true);
  }));

//
// Monitoring command
//
commander.command('monit')
  .description('launch termcaps monitoring')
  .action(failOnUnknown(CLI.monit));

commander.command('m')
  .description('(alias) launch termcaps monitoring')
  .action(failOnUnknown(CLI.monit));

//
// Flushing command
//
commander.command('flush')
  .description('flush logs')
  .action(failOnUnknown(function() {
    CLI.flush();
  }));

//
// Reload all logs
//
commander.command('reloadLogs')
  .description('reload all logs')
  .action(function() {
    CLI.reloadLogs();
  });

//
// Log streaming
//
commander.command('logs [id|name]')
  .description('stream logs file. Default stream all logs')
  .action(function(id) {
    CLI.streamLogs(id);
  });


//
// Kill
//
commander.command('kill')
  .description('kill daemon')
  .action(failOnUnknown(function(arg) {
    CLI.killDaemon();
  }));


//
// Catch all
//
commander.command('*')
  .action(function() {
    console.log(cst.PREFIX_MSG + '\nCommand not found');
    commander.outputHelp();
    process.exit(cst.ERROR_EXIT);
  });

//
// Display help
//
if (process.argv.length == 2) {
  commander.parse(process.argv);
  commander.outputHelp();
  process.exit(cst.ERROR_EXIT);
}


//
// Wait Satan is connected to God to launch parsing
//
// This message is triggered once the RPC Client is connected to the Daemon
// in file Satan.js, method Satan.launchRPC
//
process.once('satan:client:ready', function() {
    CLI.getVersion(function(err, remote_version) {
        if (!err && (pkg.version != remote_version))
            console.log('>>>> In-memory PM2 is out-of-date, do :\n>>>> $ pm2 updatePM2');
        commander.parse(process.argv);
    });
});

//
// Init
//
(function init() {
  fs.exists(cst.DEFAULT_FILE_PATH, function(exist) {
    if (!exist) {
      console.log('Initializing folder for pm2 on %s', cst.DEFAULT_FILE_PATH);
      fs.mkdirSync(cst.DEFAULT_FILE_PATH);
      fs.mkdirSync(cst.DEFAULT_LOG_PATH);
      fs.mkdirSync(cst.DEFAULT_PID_PATH);
    }
  });

  /**
   * Create configuration file if not present
   */
  fs.exists(cst.PM2_CONF_FILE, function(exist) {
    if (!exist) {
      console.log('Creating PM2 configuration file in %s', cst.PM2_CONF_FILE);
      fs
        .createReadStream(path.join(__dirname, cst.SAMPLE_CONF_FILE))
        .pipe(fs.createWriteStream(cst.PM2_CONF_FILE));
    }
  });
})();

(function testHarmony() {
  //
  // Harmony test
  //
  try {
    var assert = require('assert')
    , s = new Set();
    s.add('a');
    assert.ok(s.has('a'));
    console.log('● ES6 mode'.green);
  } catch(e) {}
})();
