
var CLI                  = module.exports = {};

var commander            = require('commander');
var fs                   = require('fs');
var path                 = require('path');
var async                = require('async');
var debug                = require('debug')('pm2:monit');
var semver               = require('semver');
var util                 = require('util');
var vm                   = require('vm');
var chalk                = require('chalk');
var exec                 = require('child_process').exec;
var p                    = path;

var Monit                = require('./Monit');
var UX                   = require('./CliUx');
var Log                  = require('./Log');
var Satan                = require('./Satan');
var Common               = require('./Common');
var cst                  = require('../constants.js');
var extItps              = require('./interpreter.json');
var InteractorDaemonizer = require('./Interactor/InteractorDaemonizer');
var json5                = require('./tools/json5.js');
var Config               = require('./tools/Config');
var Utility              = require('./Utility.js');

var Modularizer          = require('./Modularizer.js');
var Configuration        = require('../lib/Configuration.js');

var Deploy               = require('pm2-deploy');

var exitCli    = Common.exitCli;
var printError = Common.printError;
var printOut   = Common.printOut;

CLI.pm2Init = function() {
  if (!fs.existsSync(cst.PM2_ROOT_PATH)) {
    fs.mkdirSync(cst.PM2_ROOT_PATH);
    fs.mkdirSync(cst.DEFAULT_LOG_PATH);
    fs.mkdirSync(cst.DEFAULT_PID_PATH);
  }
  if (!fs.existsSync(cst.PM2_CONF_FILE)) {
    fs
      .createReadStream(path.join(__dirname, cst.SAMPLE_CONF_FILE))
      .pipe(fs.createWriteStream(cst.PM2_CONF_FILE));
  }
  if (cst.PM2_HOME && !fs.existsSync(cst.PM2_HOME)) {
    try {
      fs.mkdirSync(cst.PM2_HOME);
      fs.mkdirSync(cst.DEFAULT_LOG_PATH);
      fs.mkdirSync(cst.DEFAULT_PID_PATH);
    } catch(e) {}
  }
  if (!fs.existsSync(cst.PM2_MODULE_CONF_FILE)) {
    try {
      fs.writeFileSync(cst.PM2_MODULE_CONF_FILE, "{}");
    } catch (e) {
      console.error(e.stack || e);
    }
  }
  if (!fs.existsSync(p.join(cst.PM2_HOME, 'touch'))) {
    var dt = fs.readFileSync(path.join(__dirname, cst.KEYMETRICS_BANNER));
    console.log(dt.toString());
    try {
      fs.writeFileSync(p.join(cst.PM2_HOME, 'touch'), Date.now());
    } catch(e) {
    }
  }
  if (process.stdout._handle
      && process.stdout._handle.setBlocking)
    process.stdout._handle.setBlocking(true);
};

/**
 * API Methods
 */
CLI.connect = Satan.start;

CLI.launchBus = Satan.launchBus;

CLI.disconnectBus = Satan.disconnectBus;

CLI.disconnect = function(cb) {
  if (!cb) cb = function() {};
  Satan.disconnectRPC(cb);
};

/**
 * Entry point to start an app / json file
 */
CLI.start = function(cmd, opts, cb) {
  if (typeof(opts) == "function") {
    cb = opts;
    opts = {};
  }

  if ((typeof(cmd) === 'string' && cmd.indexOf('.json') != -1) || typeof(cmd) === 'object')
    CLI._startJson(cmd, opts, 'file', cb);
  else
    CLI._startScript(cmd, opts, cb);
};

/**
 * Method to START / RESTART a script
 * @method startFile
 * @param {string} script script name (will be resolved according to location)
 * @return
 */
CLI._startScript = function(script, opts, cb) {
  if (typeof opts == "function") {
    cb = opts;
    opts = {};
  }

  var conf = Config.transCMDToConf(opts);
  var appConf = {};

  if (!!opts.executeCommand)
    conf.exec_mode = 'fork';
  else if (opts.instances !== undefined)
    conf.exec_mode = 'cluster';
  else
    conf.exec_mode = 'fork';

  if (typeof conf.name == 'function'){
    delete conf.name;
  }

  delete conf.args;

  var argsIndex;

  if (opts.rawArgs && (argsIndex = opts.rawArgs.indexOf('--')) >= 0) {
    conf.args = opts.rawArgs.slice(argsIndex + 1);
  }
  else if (opts.scriptArgs) {
    conf.args = opts.scriptArgs;
  }

  conf.script = script;

  if ((appConf = verifyConfs(conf)) == null)
    return exitCli(cst.ERROR_EXIT);

  conf = appConf[0];

  /**
   * If -w option, write configuration to configuration.json file
   */
  if (appConf.write) {
    var dst_path = path.join(process.env.PWD, conf.name + '-pm2.json');
    printOut(cst.PREFIX_MSG + 'Writing configuration to', chalk.blue(dst_path));
    // pretty JSON
    try {
      fs.writeFileSync(dst_path, JSON.stringify(conf, null, 2));
    } catch (e) {
      console.error(e.stack || e);
    }
  }

  /**
   * If start <app_name> start/restart application
   */
  function restartExistingProcessName(cb) {
     if (!isNaN(parseInt(script)) ||
         (typeof script === 'string' && script.indexOf('/') != -1) ||
         (typeof script === 'string' && path.extname(script) != ''))
       return cb(null);

    if (script !== 'all') {
      Common.getProcessIdByName(script, function(err, ids) {
        if (ids.length > 0) {
          CLI._restart(script, function(err, list) {
            if (err) return cb(err);
            printOut(cst.PREFIX_MSG + 'Process successfully started');
            return cb(true, list);
          });
        }
        else return cb(null);
      });
    }
    else {
      CLI._restart('all', function(err, list) {
        if (err) return cb(err);
        printOut(cst.PREFIX_MSG + 'Process successfully started');
        return cb(true, list);
      });
    }
  };

  function restartExistingProcessId(cb) {
    if (isNaN(parseInt(script))) return cb(null);

    CLI._restart(script, function(err, list) {
      if (err) return cb(err);
      printOut(cst.PREFIX_MSG + 'Process successfully started');
      return cb(true, list);
    });
  };

  function restartExistingProcessPath(cb) {
    Satan.executeRemote('findByFullPath', path.resolve(process.cwd(), script), function(err, exec) {
      if (exec && (exec[0].pm2_env.status == cst.STOPPED_STATUS ||
                   exec[0].pm2_env.status == cst.STOPPING_STATUS ||
                   exec[0].pm2_env.status == cst.ERRORED_STATUS)) {
        var app_name = exec[0].pm2_env.name;

        CLI._restart(app_name, function(err, list) {
          printOut(cst.PREFIX_MSG + 'Process successfully started');
          return cb(true, list);
        });
        return false;
      }
      else if (exec && !opts.force) {
        printError(cst.PREFIX_MSG_ERR + 'Script already launched, add -f option to force re-execution');
        return cb(new Error('Script already launched'));
      }

      try {
        var resolved_paths = resolvePaths(conf);
      } catch(e) {
        return cb(e);
      }

      printOut(cst.PREFIX_MSG + 'Starting %s in %s (%d instance' + (resolved_paths.instances > 1 ? 's' : '') + ')',
               script, resolved_paths.exec_mode, resolved_paths.instances);

      if (!resolved_paths.env) resolved_paths.env = {};
      var additional_env = Modularizer.getAdditionalConf(resolved_paths.name);
      util._extend(resolved_paths.env, additional_env);

      Satan.executeRemote('prepare', resolved_paths, function(err, data) {
        if (err) {
          printError(cst.PREFIX_MSG_ERR + 'Error while launching application', err.stack || err);
          return cb({msg : err});
        }

        printOut(cst.PREFIX_MSG + 'Done.');
        return cb(true, data);
      });
      return false;
    });
  };

  async.series([
    restartExistingProcessName,
    restartExistingProcessId,
    restartExistingProcessPath
  ], function(err, data) {

    if (err instanceof Error) {
      return cb ? cb(err) : exitCli(cst.ERROR_EXIT);
    }

    var ret = {};
    data.forEach(function(_dt) {
      if (_dt !== undefined)
        ret = _dt;
    });

    return cb ? cb(null, ret) : speedList();
  });
};

/**
 * Process and start a JSON file
 * @method startJson
 * @param {string} cmd
 * @param {object} opts
 * @param {string} jsonVia
 * @param {function} cb
 */
CLI._startJson = function(cmd, opts, jsonVia, cb) {
  var appConf;
  var deployConf = null;
  var apps_info = [];

  var action = 'restart';

  if (typeof(cb) === 'undefined' && typeof(jsonVia) === 'function')
    cb = jsonVia;
  if (typeof(cmd) === 'object')
    appConf = cmd;
  else if (jsonVia == 'pipe')
    appConf = json5.parse(cmd);
  else {
    var data = null;
    try {
      data = fs.readFileSync(cmd);
    } catch(e) {
      printError(cst.PREFIX_MSG_ERR + 'JSON ' + cmd +' not found');
      return cb ? cb(e) : exitCli(cst.ERROR_EXIT);
    }
    appConf = parseConfig(data, cmd);
  }

  // v2 JSON declaration
  if (appConf.deploy) deployConf = appConf.deploy;
  if (appConf.apps) appConf = appConf.apps;

  if (!Array.isArray(appConf)) appConf = [appConf]; //convert to array

  if ((appConf = verifyConfs(appConf)) == null)
    return cb ? cb({success:false}) : exitCli(cst.ERROR_EXIT);

  // Get App list
  var apps_name = [];

  appConf.forEach(function(app) {
    apps_name.push(app.name);
  });

  function startApps(app_name_to_start, cb) {
    var apps_to_start = [];

    appConf.forEach(function(app, i) {
      if (app_name_to_start.indexOf(app.name) != -1) {
        apps_to_start.push(appConf[i]);
      }
    });

    async.eachLimit(apps_to_start, cst.CONCURRENT_ACTIONS, function(app, next) {

      if (opts.cwd)
        app.cwd = opts.cwd;
      if (opts.force_name)
        app.name = opts.force_name;
      if (opts.started_as_module)
        app.pmx_module = true;

      if (!app.env)
        app.env = {};
      var additional_env = Modularizer.getAdditionalConf(app.name);
      util._extend(app.env, additional_env);

      mergeEnvironmentVariables(app, opts.env, deployConf);
      var resolved_paths = null;
      try {
        resolved_paths = resolvePaths(app);
      } catch (e) {
        printError(e);
        return cb ? cb({msg : e.message || e}) : exitCli(cst.ERROR_EXIT);
      }

      // Set watch to true for app if argument passed to CLI
      if (opts.watch)
        resolved_paths.watch = true;

      Satan.executeRemote('prepare', resolved_paths, function(err, data) {
        printOut(cst.PREFIX_MSG + 'Process launched');
        apps_info = apps_info.concat(data);
        next();
      });

    }, function(err) {
      return cb ? cb(err || null, apps_info) : speedList();
    });
    return false;
  }

  Satan.executeRemote('getMonitorData', {}, function(err, list) {
    if (err) {
      printError(err);
      return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
    }

    async.eachLimit(list, cst.CONCURRENT_ACTIONS, function(proc, next) {
      // If app name already exists
      if (apps_name.indexOf(proc.name) == -1)
        return next();

      if (action == 'reload') {
        CLI._reloadProcessName(proc.pm2_env.name, 'reloadProcessId', function(err, ret) {
          if (err) printError(err);

          Satan.notifyGod('reload', proc.pm2_env.pm_id);

          // And Remove from array to spy
          apps_name.splice(apps_name.indexOf(proc.name), 1);
          return next();
        });
      } else if (action == 'gracefulReload') {
        CLI._reloadProcessName(proc.pm2_env.name, 'softReloadProcessId', function(err, ret) {
          if (err) printError(err);
          // And Remove from array to spy

          Satan.notifyGod('graceful reload', proc.pm2_env.pm_id);

          apps_name.splice(apps_name.indexOf(proc.name), 1);
          return next();
        });
      } else {
        // Get `env` from appConf by name
        async.filter(appConf, function(app, callback){
          callback(app.name == proc.name);
        }, function(apps){
          var envs = apps.map(function(app){
            // Binds env_diff to env and returns it.
            return mergeEnvironmentVariables(app, opts.env, deployConf);
          });
          // Assigns own enumerable properties of all
          // Notice: if people use the same name in different apps,
          //         duplicated envs will be overrode by the last one
          var env = envs.reduce(function(e1, e2){
            return util._extend(e1, e2);
          });

          // Pass `env` option
          CLI._restart(proc.pm2_env.name, env, function(err, ret) {
            if (err) printError(err);

            Satan.notifyGod('restart', proc.pm2_env.pm_id);
            // And Remove from array to spy
            apps_name.splice(apps_name.indexOf(proc.name), 1);
            return next();
          });
        });
      }
    }, function(err) {
      if (err) return cb ? cb(new Error(err)) : exitCli(cst.ERROR_EXIT);
      // Start missing apps
      return startApps(apps_name, function(err, apps) {
        return cb ? cb(err, apps) : speedList(err ? 1 : 0);
      });
    });
    return false;
  });
};

/**
 * Deploy command
 */
CLI.deploy = function(file, commands, cb) {
  if (file == 'help') {
    deployHelp();
    return cb ? cb() : exitCli(cst.SUCCESS_EXIT);
  }

  var args = commands.rawArgs;
  var env;

  args.splice(0, args.indexOf('deploy') + 1);

  // Find ecosystem file by default
  if (file.indexOf('.json') == -1) {
    env = args[0];
    file = Utility.whichFileExists(['ecosystem.js', 'ecosystem.json', 'ecosystem.json5', 'package.json']);

    if (!file) {
      printError('Not any default deployment file exists');
      return cb ? cb('Not any default ecosystem file present') : exitCli(cst.ERROR_EXIT);
    }
  }
  else
    env = args[1];

  try {
    var json_conf = parseConfig(fs.readFileSync(file), file);
  } catch (e) {
    printError(e);
    return cb ? cb(e) : exitCli(cst.ERROR_EXIT);
  }

  if (!env) {
    deployHelp();
    return cb ? cb() : exitCli(cst.SUCCESS_EXIT);
  }

  if (!json_conf.deploy || !json_conf.deploy[env]) {
    printError('%s environment is not defined in %s file', env, file);
    return cb ? cb('%s environment is not defined in %s file') : exitCli(cst.ERROR_EXIT);
  }

  if (!json_conf.deploy[env]['post-deploy']) {
    json_conf.deploy[env]['post-deploy'] = 'pm2 startOrRestart ' + file + ' --env ' + env;
  }

  Deploy.deployForEnv(json_conf.deploy, env, args, function(err, data) {
    if (err) {
      printError('Deploy failed');
      return cb ? cb(err) : exitCli(cst.ERROR_EXIT);
    }
    printOut('--> Success');
    return cb ? cb(null, data) : exitCli(cst.SUCCESS_EXIT);
  });
};

/**
 * Get version of the daemonized PM2
 * @method getVersion
 * @callback cb
 */
CLI.getVersion = function(cb) {
  Satan.executeRemote('getVersion', {}, function(err, version) {
    return cb ? cb.apply(null, arguments) : exitCli(cst.SUCCESS_EXIT);
  });
};

/**
 * Apply a RPC method on the json file
 * @method actionFromJson
 * @param {string} action RPC Method
 * @param {string|object} file file
 * @param {string} jsonVia action type (=only 'pipe' ?)
 * @param {Function}
 */
CLI.actionFromJson = function(action, file, jsonVia, cb) {
  var appConf;

  //accept programmatic calls
  if (jsonVia == 'file') {
    var data = fs.readFileSync(file);
    appConf  = parseConfig(data, file);
    // v2 JSON declaration
    if (appConf.apps) appConf = appConf.apps;
  } else if (jsonVia == 'pipe') {
    appConf = json5.parse(file);
  } else if(typeof file == 'object') {
    cb = typeof jsonVia == 'function' ? jsonVia : cb;
    appConf = file;
  } else {
    printError('Bad call to actionFromJson, jsonVia should be one of file, pipe');
    return exitCli(cst.ERROR_EXIT);
  }

  if (!Array.isArray(appConf)) appConf = [appConf]; //convert to array

  if ((appConf = verifyConfs(appConf)) == null)
    return exitCli(cst.ERROR_EXIT);

  async.eachLimit(appConf, cst.CONCURRENT_ACTIONS, function(proc, next1) {
    var name = '';
    var new_env;

    if (commander)
      new_env = mergeEnvironmentVariables(proc, commander.env);
    else
      new_env = mergeEnvironmentVariables(proc);

    if (!proc.name)
      name = p.basename(proc.script);
    else
      name = proc.name;

    Common.getProcessIdByName(name, function(err, ids) {
      if (err) {
        printError(err);
        return next1();
      }
      if (!ids) return next1();

      async.eachLimit(ids, cst.CONCURRENT_ACTIONS, function(id, next2) {
        var opts = {};

        //stopProcessId could accept options to?
        if (action == 'restartProcessId') {
          opts = {id : id, env : new_env};
        } else {
          opts = id;
        }

        //this is useful to transfer watch options directly to Satan
        //@todo always treat opts as an object in Satan.executeRemote
        if (proc.watch && typeof opts == 'object')
          opts.watch = proc.watch;

        Satan.executeRemote(action, opts, function(err, res) {
          if (err) {
            printError(err);
            return next2();
          }

          if (action == 'restartProcessId') {
            Satan.notifyGod('restart', id);
          } else if (action == 'deleteProcessId') {
            Satan.notifyGod('delete', id);
          } else if (action == 'stopProcessId') {
            Satan.notifyGod('stop', id);
          }

          printOut(cst.PREFIX_MSG + 'Process ' + id + ' restarted');
          return next2();
        });
      }, function(err) {
        return next1(null, {success:true});
      });
    });
  }, function(err) {
    if (cb) return cb(null, {success:true});
    else return setTimeout(speedList, 100);
  });
};

/**
 * Startup script generation
 * @method startup
 * @param {string} platform type (centos|redhat|amazon|gentoo|systemd)
 */
CLI.startup = function(platform, opts, cb) {
  if (process.getuid() != 0) {
    return exec('whoami', function(err, stdout, stderr) {
      console.log(cst.PREFIX_MSG + 'You have to run this command as root. Execute the following command:');
      console.log(chalk.grey('      sudo su -c "env PATH=$PATH:' + p.dirname(process.execPath) + ' pm2 startup ' + platform + ' -u ' + stdout.trim() + ' --hp ' + process.env.HOME + '"'));
      cb ? cb({msg: 'You have to run this with elevated rights'}) : exitCli(cst.ERROR_EXIT);
    });
  }

  var scriptFile = '/etc/init.d/pm2-init.sh',
      script = cst.UBUNTU_STARTUP_SCRIPT;

  if (platform == 'redhat') {
    platform = 'centos';
  } else if (platform == 'systemd') {
    scriptFile = '/etc/systemd/system/pm2.service';
  } else if (platform == 'darwin') {
    scriptFile = path.join(process.env.HOME, 'Library/LaunchAgents/io.keymetrics.PM2.plist');
    if (!fs.existsSync(path.dirname(scriptFile))) {
      fs.mkdirSync(path.dirname(scriptFile));
    }
  } else if (platform == 'freebsd') {
    scriptFile = '/etc/rc.d/pm2';
  }

  if (!!~['freebsd', 'systemd', 'centos', 'amazon', 'gentoo', 'darwin'].indexOf(platform))
    script = cst[platform.toUpperCase() + '_STARTUP_SCRIPT'];

  script = fs.readFileSync(path.join(__dirname, script), {encoding: 'utf8'});

  var user = opts.user || 'root';

  script = script.replace(/%PM2_PATH%/g, process.mainModule.filename)
    .replace(/%NODE_PATH%/g, platform != 'darwin' ? p.dirname(process.execPath) : process.env.PATH)
    .replace(/%USER%/g, user);

  if (opts.hp)
    script = script.replace(/%HOME_PATH%/g, p.resolve(opts.hp, '.pm2'));
  else
    script = script.replace(/%HOME_PATH%/g, cst.PM2_ROOT_PATH);

  printOut(cst.PREFIX_MSG + 'Generating system init script in ' + scriptFile);

  try {
    fs.writeFileSync(scriptFile, script);
  } catch (e) {
    console.error(e.stack || e);
  }

  if (!fs.existsSync(scriptFile)) {
    printOut(script);
    printOut(cst.PREFIX_MSG_ERR + ' There is a problem when trying to write file : ' + scriptFile);
    return cb ? cb({msg:'Problem with ' + scriptFile}) : exitCli(cst.ERROR_EXIT);
  }

  var cmd;
  var cmdAsUser;

  printOut(cst.PREFIX_MSG + 'Making script booting at startup...');

  switch (platform) {
  case 'systemd':
    cmdAsUser = [
      'pm2 dump', //We need an empty dump so that the first resurrect works correctly
      'pm2 kill',
    ].join(' && ');
    cmd = [
      'systemctl daemon-reload',
      'systemctl enable pm2',
      'systemctl start pm2'
    ].join(' && ');
    break;
  case 'centos':
  case 'amazon':
    cmd = 'chmod +x ' + scriptFile + '; chkconfig --add ' + p.basename(scriptFile);
    fs.closeSync(fs.openSync('/var/lock/subsys/pm2-init.sh', 'w'));
    printOut(cst.PREFIX_MSG + '/var/lock/subsys/pm2-init.sh lockfile has been added');
    break;
  case 'gentoo':
    cmd = 'chmod +x ' + scriptFile + '; rc-update add ' + p.basename(scriptFile) + ' default';
    break;
  case 'freebsd':
    cmd = 'chmod +x ' + scriptFile;
    break;
  default :
    cmd = 'chmod +x ' + scriptFile + ' && update-rc.d ' + p.basename(scriptFile) + ' defaults';
    break;
  }

  if (platform == 'systemd') {
    cmd = 'su ' + user + ' -c "' + cmdAsUser + '" && su root -c "' + cmd + '"';
  }else if (platform == 'freebsd') {
    cmd = 'su root -c "' + cmd + '"';
  }else if (platform != 'darwin') {
    cmd = 'su -c "' + cmd + '"';
  }else {
    cmd = 'pm2 dump';
  }

  printOut(cst.PREFIX_MSG + '-' + platform + '- Using the command:\n      %s', chalk.grey(cmd));

  exec(cmd, function(err, stdo, stde) {
    if (err) {
      printError(err);
      printError('----- Are you sure you use the right platform command line option ? centos / redhat, amazon, ubuntu, gentoo, systemd or darwin?');
      return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
    }
    printOut(stdo.toString().replace(/[\r\n]$/, ''));
    printOut(cst.PREFIX_MSG + 'Done.');
    return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
  });
};

CLI.logrotate = function(opts, cb) {
  if (process.getuid() != 0) {
    return exec('whoami', function(err, stdout, stderr) {
      printError(cst.PREFIX_MSG + 'You have to run this command as root. Execute the following command:\n' +
                    chalk.grey('      sudo env PATH=$PATH:' + p.dirname(process.execPath) + ' pm2 logrotate -u ' + stdout.trim()));
      cb ? cb({msg: 'You have to run this with elevated rights'}) : exitCli(cst.ERROR_EXIT);
    });
  }

  if(!fs.existsSync('/etc/logrotate.d')) {
    printError(cst.PREFIX_MSG + '/etc/logrotate.d does not exist we can not copy the default configuration.');
    return cb ? cb({msg: '/etc/logrotate.d does not exist'}) : exitCli(cst.ERROR_EXIT);
  }

  var script = fs.readFileSync(path.join(__dirname, cst.LOGROTATE_SCRIPT), {encoding: 'utf8'});

  var user = opts.user || 'root';

  script = script.replace(/%HOME_PATH%/g, cst.PM2_ROOT_PATH)
    .replace(/%USER%/g, user);

  try {
    fs.writeFileSync('/etc/logrotate.d/pm2-'+user, script);
  } catch (e) {
    console.error(e.stack || e);
  }

  printOut(cst.PREFIX_MSG + 'Logrotate configuration added to /etc/logrotate.d/pm2');
  return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
}

/**
 * Ping daemon - if PM2 daemon not launched, it will launch it
 * @method ping
 */
CLI.ping = function(cb) {
  Satan.executeRemote('ping', {}, function(err, res) {
    if (err) {
      printError(err);
      return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
    }
    printOut(res);
    return cb ? cb(null, res) : exitCli(cst.SUCCESS_EXIT);
  });
};

/**
 * Reset meta data
 * @method resetMetaProcess
 */
CLI.reset = function(process_name, cb) {
  function processIds(ids, cb) {
    async.eachLimit(ids, cst.CONCURRENT_ACTIONS, function(id, next) {
      Satan.executeRemote('resetMetaProcessId', id, function(err, res) {
        if (err) console.error(err);
        printOut(cst.PREFIX_MSG + 'Resetting meta for process id %d', id);
        return next();
      });
    }, function(err) {
      if (err) return cb(new Error(err));
      return cb ? cb(null, {success:true}) : speedList();
    });
  };

  if (process_name == 'all') {
    Common.getAllProcessId(function(err, ids) {
      if (err) {
        printError(err);
        return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
      }
      return processIds(ids, cb);
    });
  }
  else if (isNaN(parseInt(process_name))) {
    Common.getProcessIdByName(process_name, function(err, ids) {
      if (err) {
        printError(err);
        return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
      }
      if (ids.length === 0) {
        printError('Unknow process name');
        return cb ? cb({msg:'Unknow process name'}) : exitCli(cst.ERROR_EXIT);
      }
      return processIds(ids, cb);
    });
  } else {
    processIds([process_name], cb);
  }
};

/**
 * Resurrect processes
 * @method resurrect
 * @param {} cb
 * @return
 */
CLI.resurrect = function(cb) {
  try {
    var apps = fs.readFileSync(cst.DUMP_FILE_PATH);
  } catch(e) {
    console.error(cst.PREFIX_MSG + 'No processes saved; DUMP file doesn\'t exist');
    if (cb) return cb(e);
    else return exitCli(cst.ERROR_EXIT);
  }

  (function ex(apps) {
    if (!apps[0]) return cb ? cb(null, apps) : speedList();
    Satan.executeRemote('prepare', apps[0], function(err, dt) {
      if (err)
        printError('Process %s not launched - (script missing)', apps[0].pm_exec_path);
      else
        printOut('Process %s launched', apps[0].pm_exec_path);

      Satan.notifyGod('resurrect', dt[0].pm2_env.pm_id);

      apps.shift();
      return ex(apps);
    });
    return false;
  })(json5.parse(apps));
};

/**
 * Description
 * @method updatePM2
 * @param {} cb
 * @return
 */

CLI.updatePM2 = CLI.update = function(cb) {
  printOut('Be sure to have the latest version by doing `npm install pm2@latest -g` before doing this procedure.');

  // Dump PM2 processes
  Satan.executeRemote('notifyKillPM2', {}, function() {});
  CLI.dump(function(err) {
    debug('Dumping successfull', err);
    CLI.killDaemon(function() {
      debug('------------------ Everything killed', arguments);
      Satan.launchDaemon(function(err, child) {
        Satan.launchRPC(function() {
          CLI.resurrect(function() {
            printOut(chalk.blue.bold('>>>>>>>>>> PM2 updated'));
            require('./Modularizer.js').launchAll(function() {
              return cb ? cb(null, {success:true}) : speedList();
            });
          });
        });
      });
    });
  });

  return false;
};

/**
 * Dump current processes managed by pm2 into DUMP_FILE_PATH file
 * @method dump
 * @param {} cb
 * @return
 */
CLI.dump = function(cb) {
  var env_arr = [];
  Satan.executeRemote('getMonitorData', {}, function(err, list) {
    if (err) {
      printError('Error retrieving process list: ' + err);
      return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
    }

    /**
     * Description
     * @method fin
     * @param {} err
     * @return
     */
    function fin(err) {
      try {
        fs.writeFileSync(cst.DUMP_FILE_PATH, json5.stringify(env_arr));
      } catch (e) {
        console.error(e.stack || e);
      }
      if (cb) return cb(null, {success:true});
      else return exitCli(cst.SUCCESS_EXIT);
    }

    (function ex(apps) {
      if (!apps[0]) return fin(null);
      delete apps[0].pm2_env.instances;
      delete apps[0].pm2_env.pm_id;
      if (!apps[0].pm2_env.pmx_module)
        env_arr.push(apps[0].pm2_env);
      apps.shift();
      return ex(apps);
    })(list);
  });
};

/**
 * Launch API interface
 * @method web
 * @return
 */
CLI.web = function(cb) {
  var filepath = p.resolve(p.dirname(module.filename), 'HttpInterface.js');

  CLI.start(filepath, {
    name : 'pm2-http-interface',
    execMode : 'fork_mode'
  }, function(err, proc) {
    if (err) {
      printError(cst.PREFIX_MSG_ERR + 'Error while launching application', err.stack || err);
      return cb ? cb({msg:err}) : speedList();
    }
    printOut(cst.PREFIX_MSG + 'Process launched');
    return cb ? cb(null, proc) : speedList();
  });
};

CLI.gracefulReload = function(process_name, cb) {
  if (process_name == 'all')
    CLI._reloadAll('softReloadProcessId', cb);
  else if (process_name.indexOf('.json') > 0)
    CLI._jsonStartOrAction('gracefulReload', process_name, commander);
  else
    CLI._reloadProcessName(process_name, 'softReloadProcessId', cb);
};

CLI.reload = function(process_name, cb) {
  if (process_name == 'all')
    CLI._reloadAll('reloadProcessId', cb);
  else if (process_name.indexOf('.json') > 0)
    CLI._jsonStartOrAction('reload', process_name, commander);
  else
    CLI._reloadProcessName(process_name, 'reloadProcessId', cb);
};

/**
 * CLI method for reloading
 * @method reload
 * @param {string} reload_method RPC method to hit (can be reloadProcessId or softReloadProcessId)
 * @return
 */
CLI._reloadAll = function (reload_method, cb) {
  Common.getAllProcess(function(err, procs) {
    if (err) {
      printError(err);
      return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
    }

    async.eachLimit(procs, cst.CONCURRENT_ACTIONS, function(proc, next) {
      if ((proc.pm2_env.status == cst.STOPPED_STATUS ||
           proc.pm2_env.status == cst.STOPPING_STATUS ||
           proc.pm2_env.status == cst.ERRORED_STATUS)) {
        return next();
      }

      if (proc.pm2_env.exec_mode != 'cluster_mode') {
        console.log(cst.PREFIX_MSG_WARNING + '%s app can\'t be reloaded - restarting it', proc.pm2_env.name);
        return CLI._restart(proc.pm2_env.name, next);
      }

      Satan.executeRemote(reload_method, proc.pm2_env.pm_id, function(err, list) {
        printOut(cst.PREFIX_MSG + 'Process %s successfully reloaded', proc.pm2_env.name);
        Satan.notifyGod('reload', proc.pm2_env.pm_id);
        return next();
      });
      return false;
    }, function(err) {
      return cb ? cb(null, procs) : speedList();
    });
    return false;
  });
};

/**
 * CLI method for reloading
 * @method reloadProcessName
 * @param {string} process_name name of processes to reload
 * @param {string} reload_method RPC method to hit (can be reloadProcessId or softReloadProcessId)
 * @return
 */
CLI._reloadProcessName = function(process_name, reload_method, cb) {
  printOut(cst.PREFIX_MSG + 'Reloading process by name %s', process_name);

  Common.getProcessByName(process_name, function(err, processes) {

    if (err) {
      return cb ? cb({msg : err}) : exitCli(cst.ERROR_EXIT);
    }

    if (processes.length === 0) {
      printError('No processes with this name: %s', process_name);
      return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
    }

    async.eachLimit(processes, cst.CONCURRENT_ACTIONS, function(proc, next) {
      // if (proc.state == cst.STOPPED_STATUS ||
      //     proc.state == cst.STOPPING_STATUS ||
      //     proc.state == cst.ERRORED_STATUS) {
      //   return next();
      // }

      if (proc.pm2_env.exec_mode != 'cluster_mode') {
        console.log(cst.PREFIX_MSG_WARNING + '%s app can\'t be reloaded - restarting it', process_name);

        Satan.notifyGod('restart', proc.pm2_env.pm_id);

        return CLI.restart(process_name, next);
      }

      Satan.executeRemote(reload_method, proc.pm2_env.pm_id, function(err, res) {
        if (err)
          return next(err);

        Satan.notifyGod('reload', proc.pm2_env.pm_id);

        printOut(cst.PREFIX_MSG + 'Process %s successfully reloaded', proc.pm2_env.name);
        return next();
      });
    }, function(err) {
      if (err) {
        printError(err.stack);
        return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
      }
      printOut(cst.PREFIX_MSG + 'All processes reloaded');
      return cb ? cb(null, processes) : setTimeout(speedList, 300);
    });
    return false;
  });
};

/**
 * Execute remote command
 */
CLI.remote = function(command, opts, cb) {
  CLI[command](opts.name, function(err_cmd, ret) {
    if (err_cmd)
      console.error(err_cmd);
    console.log('Command %s finished', command);
    return cb(err_cmd, ret);
  });
};

/**
 * This remote method allows to pass multiple arguments
 * to PM2
 * It is used for the new scoped PM2 action system
 */
CLI.remoteV2 = function(command, opts, cb) {
  if (CLI[command].length == 1)
    return CLI[command](cb);

  opts.args.push(cb);
  return CLI[command].apply(this, opts.args);
};

/**
 * Start or restart|reload|gracefulReload a JSON configuration file
 * @param {string} action    restart|reload
 * @param {string} json_conf JS file path
 * @param {string} opts      option like environment type and co
 * @callback cb optional
 * @param cb
 */
CLI._jsonStartOrAction = function(action, json_conf, opts, cb) {
  try {
    var data = fs.readFileSync(json_conf);
  } catch(e) {
    printError('Configuration file %s is missing. Action canceled.', json_conf);
    return cb ? cb(e) : exitCli(cst.ERROR_EXIT);
  }

  var appConf = parseConfig(data, json_conf), deployConf = null;
  // v2 JSON declaration
  if (appConf.deploy) deployConf = appConf.deploy;
  if (appConf.apps) appConf = appConf.apps;

  if ((appConf = verifyConfs(appConf)) == null)
    return exitCli(cst.ERROR_EXIT);

  var apps_name = [];

  appConf.forEach(function(app) {
    apps_name.push(app.name);
  });

  function startApps(app_name_to_start, cb) {
    var apps_to_start = [];

    appConf.forEach(function(app, i) {
      if (app_name_to_start.indexOf(app.name) != -1) {
        apps_to_start.push(appConf[i]);
      }
    });

    async.eachLimit(apps_to_start, cst.CONCURRENT_ACTIONS, function(app, next) {
      mergeEnvironmentVariables(app, opts.env, deployConf);
      var resolved_paths = null;
      try {
        resolved_paths = resolvePaths(app);
      } catch (e) {
        printError(e);
        return cb ? cb({msg : e.message || e}) : exitCli(cst.ERROR_EXIT);
      }

      Satan.executeRemote('prepare', resolved_paths, function(err, data) {
        return next();
      });

    }, function(err) {
      return cb(null, {success:true});
    });
    return false;
  }

  Satan.executeRemote('getMonitorData', {}, function(err, list) {
    if (err) {
      printError(err);
      return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
    }

    async.eachLimit(list, cst.CONCURRENT_ACTIONS, function(proc, next) {
      if (apps_name.indexOf(proc.name) == -1)
        return next();

      if (action == 'reload') {
        CLI._reloadProcessName(proc.pm2_env.name, 'reloadProcessId', function(err, ret) {
          if (err) printError(err);

          Satan.notifyGod('reload', proc.pm2_env.pm_id);

          // And Remove from array to spy
          apps_name.splice(apps_name.indexOf(proc.name), 1);
          return next();
        });
      } else if (action == 'gracefulReload') {
        CLI._reloadProcessName(proc.pm2_env.name, 'softReloadProcessId', function(err, ret) {
          if (err) printError(err);
          // And Remove from array to spy

          Satan.notifyGod('graceful reload', proc.pm2_env.pm_id);

          apps_name.splice(apps_name.indexOf(proc.name), 1);
          return next();
        });
      } else {
        // Get `env` from appConf by name
        async.filter(appConf, function(app, callback){ callback(app.name == proc.name); }, function(apps){
          var envs = apps.map(function(app){
            // Binds env_diff to env and returns it.
            return mergeEnvironmentVariables(app, opts.env, deployConf);
          });
          // Assigns own enumerable properties of all
          // Notice: if people use the same name in different apps,
          //         duplicated envs will be overrode by the last one
          var env = envs.reduce(function(e1, e2){
            return util._extend(e1, e2);
          });

          // Pass `env` option
          CLI._restart(proc.pm2_env.name, env, function(err, ret) {
            if (err) printError(err);

            Satan.notifyGod('restart', proc.pm2_env.pm_id);
            // And Remove from array to spy
            apps_name.splice(apps_name.indexOf(proc.name), 1);
            return next();
          });
        });
      }
      return false;
    }, function(err) {
      if (err) return cb ? cb(new Error(err)) : exitCli(cst.ERROR_EXIT);
      // Start missing apps
      return startApps(apps_name, function() {
        return cb ? cb(null, {success:true}) : speedList();
      });
    });
    return false;
  });
};

/**
 * This methods is used for stop, delete and restart
 * Module cannot be stopped or deleted but can be restarted
 */
CLI._operate = function(action_name, process_name, envs, cb) {
  // Make sure all options exist

  if (!envs)
    envs = {};

  if (typeof(envs) == 'function'){
    cb = envs;
    envs = {};
  }


  /**
   * Operate action on specific process id
   */
  function processIds(ids, cb) {
    async.eachLimit(ids, cst.CONCURRENT_ACTIONS, function(id, next) {
      var opts = id;
      if (action_name == 'restartProcessId') {
        // I dont get why we did this
        // env : process.env.PM2_PROGRAMMATIC === 'true' ? {} : util._extend(process.env, envs)
        opts = {
          id  : id,
          env : util._extend(process.env, envs)
        };
      }

      Satan.executeRemote(action_name, opts, function(err, res) {
        if (err) {
          printError(cst.PREFIX_MSG_ERR + 'Process %s not found', id);
          return next('Process not found');
        }

        if (action_name == 'restartProcessId') {
          Satan.notifyGod('restart', id);
        } else if (action_name == 'deleteProcessId') {
          Satan.notifyGod('delete', id);
        } else if (action_name == 'stopProcessId') {
          Satan.notifyGod('stop', id);
        }

        printOut(cst.PREFIX_MSG + action_name + ' process id %d', id);
        return next();
      });
    }, function(err) {
      if (err) return cb ? cb(new Error(err)) : exitCli(cst.ERROR_EXIT);
      return cb ? cb(null, {success:true}) : speedList();
    });
  };

  if (process_name == 'all') {
    Common.getAllProcessId(function(err, ids) {
      if (err) {
        printError(err);
        return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
      }
      if (!ids || ids.length === 0) {
        printError(cst.PREFIX_MSG_WARNING + 'No process found');
        return cb ? cb({ success : false, msg : 'process name not found'}) : exitCli(cst.ERROR_EXIT);
      }

      return processIds(ids, cb);
    });
  }
  else if (isNaN(process_name)) {

    /**
     * We can not stop or delete a module but we can restart it
     * to refresh configuration variable
     */
    var allow_module_restart = action_name == 'restartProcessId' ? true : false;

    Common.getProcessIdByName(process_name, allow_module_restart, function(err, ids, full_detail) {
      if (err) {
        printError(err);
        return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
      }
      if (!ids || ids.length === 0) {
        printError(cst.PREFIX_MSG_ERR + 'Process %s not found', process_name);
        return cb ? cb({ success : false, msg : 'process name not found'}) : exitCli(cst.ERROR_EXIT);
      }

      /**
       * Determine if the process to restart is a module
       * if yes load configuration variables and merge with the current environment
       */
      var additional_env = Modularizer.getAdditionalConf(process_name);
      util._extend(envs, additional_env);

      return processIds(ids, cb);
    });
  } else {
    processIds([process_name], cb);
  }
};


CLI.restart = function(process_name, cb) {
  if (typeof(process_name) === 'number')
    process_name = process_name.toString();

  if (process_name == "-") {
    process.stdin.resume();
    process.stdin.setEncoding('utf8');
    process.stdin.on('data', function (param) {
      process.stdin.pause();
      CLI.actionFromJson('restartProcessId', param, 'pipe', cb);
    });
  }
  else if (process_name.indexOf('.json') > 0)
    CLI.actionFromJson('restartProcessId', process_name, 'file', cb);
  else
    CLI._restart(process_name, process.env, cb);
};

CLI._restart = function(process_name, envs, cb) {
  CLI._operate('restartProcessId', process_name, envs, cb);
};

/**
 * Description
 * @method deleteProcess
 * @param {} process_name
 * @param {} jsonVia
 * @return
 */
CLI.delete = function(process_name, jsonVia, cb) {
  if (typeof(jsonVia) === "function") {
    cb = jsonVia;
    jsonVia = null;
  }
  if (typeof(process_name) === "number") {
    process_name = process_name.toString();
  }

  printOut(cst.PREFIX_MSG + 'Deleting %s process', process_name);

  if (jsonVia == 'pipe')
    return CLI.actionFromJson('deleteProcessId', process_name, 'pipe', cb);
  if (process_name.indexOf('.json') > 0)
    return CLI.actionFromJson('deleteProcessId', process_name, 'file', cb);
  else {
    CLI._delete(process_name, cb);
  }
};

CLI._delete = function(process_name, cb) {
  CLI._operate('deleteProcessId', process_name, cb);
};

CLI.stop = function(process_name, cb) {
  if (typeof(process_name) === 'number')
    process_name = process_name.toString();

  printOut(cst.PREFIX_MSG + 'Stopping ' + process_name);

  if (process_name == "-") {
    process.stdin.resume();
    process.stdin.setEncoding('utf8');
    process.stdin.on('data', function (param) {
      process.stdin.pause();
      CLI.actionFromJson('stopProcessId', param, 'pipe', cb);
    });
  }
  else if (process_name.indexOf('.json') > 0)
    CLI.actionFromJson('stopProcessId', process_name, 'file', cb);
  else {
    CLI._stop(process_name, cb);
  }
};

CLI._stop = function(process_name, cb) {
  CLI._operate('stopProcessId', process_name, cb);
};

/**
 * Description
 * @method generateSample
 * @param {} name
 * @return
 */
CLI.generateSample = function() {
  var sample = fs.readFileSync(path.join(__dirname, cst.SAMPLE_FILE_PATH));
  var dt     = sample.toString();
  var f_name = 'ecosystem.json';

  try {
    fs.writeFileSync(path.join(process.env.PWD, f_name), dt);
  } catch (e) {
    console.error(e.stack || e);
  }
  printOut('File %s generated', path.join(process.env.PWD, f_name));
  exitCli(cst.SUCCESS_EXIT);
};

/**
 * Description
 * @method list
 * @return
 */
CLI.list = function(cb) {
  Satan.executeRemote('getMonitorData', {}, function(err, list) {
    if (err) {
      printError(err);
      return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
    }
    return cb ? cb(null, list) : speedList();
  });
};

/**
 * Description
 * @method jlist
 * @param {} debug
 * @return
 */
CLI.jlist = function(debug) {
  Satan.executeRemote('getMonitorData', {}, function(err, list) {
    if (err) {
      printError(err);
      exitCli(cst.ERROR_EXIT);
    }

    if (debug) {
      printOut(util.inspect(list, false, null, false));
    }
    else {
      printOut(JSON.stringify(list));
    }

      exitCli(cst.SUCCESS_EXIT);

  });
};

/**
 * Scale up/down a process
 * @method scale
 */
CLI.scale = function(app_name, number, cb) {

  function addProcs(proc, value, cb) {
    (function ex(proc, number) {
      if (number-- === 0) return cb();
      printOut(cst.PREFIX_MSG + 'Scaling up application');
      Satan.executeRemote('duplicateProcessId', proc.pm2_env.pm_id, ex.bind(this, proc, number));
    })(proc, number);
  }

  function rmProcs(procs, value, cb) {
    var i = 0;

    (function ex(procs, number) {
      if (number++ === 0) return cb();
      CLI._operate('deleteProcessId', procs[i++].pm2_env.pm_id, ex.bind(this, procs, number));
    })(procs, number);
  }

  function end() {
    return cb ? cb(null, {success:true}) : speedList();
  }

  Common.getProcessByName(app_name, function(err, procs) {
    if (err) {
      printError(err);
      return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
    }

    if (!procs || procs.length === 0) {
      printError(cst.PREFIX_MSG_ERR + 'Application %s not found', app_name);
      return cb ? cb({msg: 'App not found'}) : exitCli(cst.ERROR_EXIT);
    }

    if (procs[0].pm2_env.exec_mode !== 'cluster_mode') {
      printError(cst.PREFIX_MSG_ERR + 'Application %s is not in cluster mode', app_name);
      return cb ? cb({msg: 'App not in cluster mode'}) : exitCli(cst.ERROR_EXIT);
    }

    var proc_number = procs.length;

    if (typeof(number) === 'string' && number.indexOf('+') >= 0) {
      number = parseInt(number, 10);
      return addProcs(procs[0], number, end);
    }
    else if (typeof(number) === 'string' && number.indexOf('-') >= 0) {
      number = parseInt(number, 10);
      return rmProcs(procs[0], number, end);
    }
    else {
      number = parseInt(number, 10);
      number = number - proc_number;

      if (number < 0)
        return rmProcs(procs, number, end);
      else if (number > 0)
        return addProcs(procs[0], number, end);
      else {
        printError(cst.PREFIX_MSG_ERR + 'Nothing to do');
        return cb ? cb({msg: 'Same process number'}) : exitCli(cst.ERROR_EXIT);
      }
    }
  });
};

/**
 * Description
 * @method flush
 * @return
 */
CLI.flush = function(cb) {
  printOut(cst.PREFIX_MSG + 'Flushing ' + cst.PM2_LOG_FILE_PATH);
  fs.closeSync(fs.openSync(cst.PM2_LOG_FILE_PATH, 'w'));

  Satan.executeRemote('getMonitorData', {}, function(err, list) {
    if (err) {
      printError(err);
      return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
    }
    list.forEach(function(l) {
      printOut(cst.PREFIX_MSG + 'Flushing');
      printOut(cst.PREFIX_MSG + l.pm2_env.pm_out_log_path);
      printOut(cst.PREFIX_MSG + l.pm2_env.pm_err_log_path);

      if (l.pm2_env.pm_log_path) {
        printOut(cst.PREFIX_MSG + l.pm2_env.pm_log_path);
        fs.closeSync(fs.openSync(l.pm2_env.pm_log_path, 'w'));
      }

      fs.closeSync(fs.openSync(l.pm2_env.pm_out_log_path, 'w'));
      fs.closeSync(fs.openSync(l.pm2_env.pm_err_log_path, 'w'));
    });
    printOut(cst.PREFIX_MSG + 'Logs flushed');
    return cb ? cb(null, list) : exitCli(cst.SUCCESS_EXIT);
  });
};

/**
 * Description
 * @method describeProcess
 * @param {} pm2_id
 * @return
 */
CLI.describe = function(pm2_id, cb) {
  var found_proc = [];

  Satan.executeRemote('getMonitorData', {}, function(err, list) {
    if (err) {
      printError('Error retrieving process list: ' + err);
      exitCli(cst.ERROR_EXIT);
    }

    list.forEach(function(proc) {
      if ((!isNaN(parseInt(pm2_id))    && proc.pm_id == pm2_id) ||
          (typeof(pm2_id) === 'string' && proc.name  == pm2_id)) {
        found_proc.push(proc);
      }
    });

    if (found_proc.length === 0) {
      printError(cst.PREFIX_MSG_WARNING + '%s doesn\'t exist', pm2_id);
      return cb ? cb(null, []) : exitCli(cst.ERROR_EXIT);
    }

    if (!cb) {
      found_proc.forEach(function(proc) {
        UX.describeTable(proc);
      });
    }

    return cb ? cb(null, found_proc) : exitCli(cst.SUCCESS_EXIT);
  });
};

/**
 * Description
 * @method reloadLogs
 * @return
 */
CLI.reloadLogs = function(cb) {
  printOut('Reloading all logs...');
  Satan.executeRemote('reloadLogs', {}, function(err, logs) {
    if (err) {
      printError(err);
      return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
    }
    printOut('All logs reloaded');
    return cb ? cb(null, logs) : exitCli(cst.SUCCESS_EXIT);
  });
};

/**
 * Description
 * @method sendSignalToProcessName
 * @param {} signal
 * @param {} process_name
 * @return
 */
CLI.sendSignalToProcessName = function(signal, process_name, cb) { Satan.executeRemote('sendSignalToProcessName', {
  signal : signal,
  process_name : process_name
}, function(err, list) {
  if (err) {
    printError(err);
    return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
  }
  printOut('successfully sent signal %s to process name %s', signal, process_name);
  return cb ? cb(null, list) : speedList();
});
                                                                 };

/**
 * Description
 * @method sendSignalToProcessId
 * @param {} signal
 * @param {} process_id
 * @return
 */
CLI.sendSignalToProcessId = function(signal, process_id, cb) {
  Satan.executeRemote('sendSignalToProcessId', {
    signal : signal,
    process_id : process_id
  }, function(err, list) {
    if (err) {
      printError(err);
      return cb ? cb({msg:err}) : exitCli(cst.ERROR_EXIT);
    }
    printOut('successfully sent signal %s to process id %s', signal, process_id);
    return cb ? cb(null, list) : speedList();
  });
};

/**
 * Description
 * @method monit
 * @return
 */
CLI.monit = function(cb) {
  if (cb) return cb({msg: 'Monit cant be called programmatically'});
  Monit.init();

  function launchMonitor() {

    Satan.executeRemote('getMonitorData', {}, function(err, list) {
      debug('CLI.monit - getMonitorData', err);

      if (err) {
        console.error('Error retrieving process list: ' + err);
        exitCli(cst.ERROR_EXIT);
      }

      Monit.refresh(list);

      setTimeout(function() {
        launchMonitor();
      }, 400);
    });
  }

  launchMonitor();
};

/**
 * Description
 * @method streamLogs
 * @param {String} id
 * @param {Number} lines
 * @param {Boolean} raw
 * @return
 */

CLI.streamLogs = function(id, lines, raw, timestamp, exclusive) {
  var files_list = [];

  // If no argument is given, we stream logs for all running apps
  id = id || 'all';
  lines = lines !== undefined ? lines : 20;
  lines = lines < 0 ? -(lines) : lines;

  // Avoid duplicates and check if path is different from '/dev/null'
  var pushIfUnique = function(entry) {
    var exists = false;

    if (entry.path.toLowerCase
       && entry.path.toLowerCase() !== '/dev/null') {

      files_list.some(function(file) {
        if (file.path === entry.path)
          exists = true;
        return exists;
      });

      if (exists)
        return;

      files_list.push(entry);
    }
  };

  // Get the list of all running apps
  Satan.executeRemote('getMonitorData', {}, function(err, list) {
    if (err) {
      printError(err);
      exitCli(cst.ERROR_EXIT);
    }

    if (lines === 0)
      return Log.stream(id, raw, timestamp, exclusive);
    if (!raw)
      printOut(chalk['inverse'](util.format.call(this, '[PM2] Tailing last %d lines for [%s] process%s', lines, id, id === 'all' ? 'es' : '', '\n')));

    // Populate the array `files_list` with the paths of all files we need to tail
    list.forEach(function(proc) {
      if (proc.pm2_env
         && (id === 'all'
            || proc.pm2_env.name == id
            || proc.pm2_env.pm_id == id)) {
        if (proc.pm2_env.pm_out_log_path && exclusive !== 'err')
          pushIfUnique({
            path     : proc.pm2_env.pm_out_log_path,
            app_name : proc.pm2_env.name + '-' + proc.pm2_env.pm_id,
            type     : 'out'});
        if (proc.pm2_env.pm_err_log_path && exclusive !== 'out')
          pushIfUnique({
            path     : proc.pm2_env.pm_err_log_path,
            app_name : proc.pm2_env.name + '-' + proc.pm2_env.pm_id,
            type     : 'err'
          });
      }
    });

    if (!raw && (id === 'all' || id === 'PM2') && exclusive === false) {
      Log.tail([{
        path     : cst.PM2_LOG_FILE_PATH,
        app_name : 'PM2',
        type     : 'PM2'
      }], lines, raw, function() {
        Log.tail(files_list, lines, raw, function() {
          Log.stream(id, raw, timestamp, exclusive);
        });
      });
    }
    else {
      Log.tail(files_list, lines, raw, function() {
        Log.stream(id, raw, timestamp, exclusive);
      });
    }
  });
};

/**
 * Description
 * @method killDaemon
 * @param {} cb
 * @return
 */
CLI.killDaemon = CLI.kill = function(cb) {
  printOut(cst.PREFIX_MSG + 'Stopping PM2...');

  Satan.executeRemote('notifyKillPM2', {}, function() {});
  CLI.killAllModules(function() {
    CLI._operate('deleteProcessId', 'all', function(err, list) {
      printOut(cst.PREFIX_MSG + 'All processes have been stopped and deleted');

      InteractorDaemonizer.killDaemon(function(err, data) {
        Satan.killDaemon(function(err, res) {
          if (err) printError(err);
          printOut(cst.PREFIX_MSG + 'PM2 stopped');
          return cb ? cb(err, res) : exitCli(cst.SUCCESS_EXIT);
        });
      });
    });
  });
};

/***************************
 *
 * Module methods
 *
 **************************/

/**
 * Install / Update a module
 */
CLI.install = function(module_name, cb) {
  Modularizer.install(module_name, function(err, data) {
    if (err)
      return cb ? cb(err) : speedList(cst.ERROR_EXIT);
    return cb ? cb(null, data) : speedList(cst.SUCCESS_EXIT);
  });
};

/**
 * Uninstall a module
 */
CLI.uninstall = function(module_name, cb) {
  Modularizer.uninstall(module_name, function(err, data) {
    if (err)
      return cb ? cb(err) : speedList(cst.ERROR_EXIT);
    return cb ? cb(null, data) : speedList(cst.SUCCESS_EXIT);
  });
};

/**
 * Publish module on NPM + Git push
 */
CLI.publish = function(module_name, cb) {
  Modularizer.publish(function(err, data) {
    if (err)
      return cb ? cb(err) : speedList(cst.ERROR_EXIT);
    return cb ? cb(null, data) : speedList(cst.SUCCESS_EXIT);
  });
};

/**
 * Publish module on NPM + Git push
 */
CLI.generateModuleSample = function(app_name, cb) {
  Modularizer.generateSample(app_name, function(err, data) {
    if (err)
      return cb ? cb(err) : exitCli(cst.ERROR_EXIT);
    return cb ? cb(null, data) : exitCli(cst.SUCCESS_EXIT);
  });
};

CLI.killAllModules = function(cb) {
  Common.getAllModulesId(function(err, modules_id) {
    async.forEachLimit(modules_id, 1, function(id, next) {
      CLI._operate('deleteProcessId', id, next);
    }, function() {
      return cb ? cb() : false;
    });
  });
};

CLI.deleteModule = function(module_name, cb) {
  var found_proc = [];

  Common.getAllProcess(function(err, procs) {
    if (err) {
      Common.printError('Error retrieving process list: ' + err);
      return cb(err);
    }

    procs.forEach(function(proc) {
      if (proc.pm2_env.name == module_name && proc.pm2_env.pmx_module) {
        found_proc.push(proc.pm_id);
      }
    });

    if (found_proc.length == 0)
      return cb();

    CLI._operate('deleteProcessId', found_proc[0], function(err) {
      if (err) return cb(err);
      Common.printOut('In memory process deleted');
      return cb();
    });
  });
};

/**
 * Configuration
 */
function displayConf(target_app, cb) {
  if (typeof(target_app) == 'function') {
    cb = target_app;
    target_app = null;
  }

  Configuration.getAll(function(err, data) {
    UX.dispKeys(data, target_app);
    return cb();
  });
};

CLI.get = function(key, cb) {
  if (!key || key == 'all') {
    displayConf(function(err, data) {
      if (err)
        return cb ? cb({success:false, err:err}) : exitCli(cst.ERROR_EXIT);
      return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
    });
    return false;
  }
  Configuration.get(key, function(err, data) {
    Common.printOut(data);
    return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
  });
};

var Password      = require('./Interactor/Password.js');

CLI.set = function(key, value, cb) {

  /**
   * Specific when setting pm2 password
   * Used for restricted remote actions
   * Also alert Interactor that password has been set
   */
  if (key.indexOf('pm2:passwd') > -1) {
    value = Password.generate(value);
    Configuration.set(key, value, function(err) {
      if (err)
        return cb ? cb({success:false, err:err }) : exitCli(cst.ERROR_EXIT);
      InteractorDaemonizer.launchRPC(function(err) {
        if (err) {
          displayConf('pm2', function() {
            return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
          });
          return false;
        }
        InteractorDaemonizer.rpc.passwordSet(function() {
          InteractorDaemonizer.disconnectRPC(function() {
            displayConf('pm2', function() {
              return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
            });
          });
        });
        return false;
      });
    });
    return false;
  }

  /**
   * Set value
   */
  Configuration.set(key, value, function(err) {
    if (err)
      return cb ? cb({success:false, err:err }) : exitCli(cst.ERROR_EXIT);

    var values = [];

    if (key.indexOf('.') > -1)
      values = key.split('.');

    if (key.indexOf(':') > -1)
      values = key.split(':');

    if (values && values.length > 1) {
      // The first element is the app name (module_conf.json)
      var app_name = values[0];

      process.env.PM2_PROGRAMMATIC = 'true';
      CLI.restart(app_name, function(err, data) {
        process.env.PM2_PROGRAMMATIC = 'false';
        if (!err)
          Common.printOut(cst.PREFIX_MSG + 'Module %s restarted', app_name);
        displayConf(app_name, function() {
          return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
        });
      });
      return false;
    }
    displayConf(app_name, function() {
      return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
    });
  });
};

CLI.multiset = function(serial, cb) {
  Configuration.multiset(serial, function(err, data) {
    if (err)
      return cb ? cb({success:false, err:err}) : exitCli(cst.ERROR_EXIT);

    var values = [];
    var key = serial.match(/(?:[^ "]+|"[^"]*")+/g)[0];

    if (key.indexOf('.') > -1)
      values = key.split('.');

    if (key.indexOf(':') > -1)
      values = key.split(':');

    if (values && values.length > 1) {
      // The first element is the app name (module_conf.json)
      var app_name = values[0];

      process.env.PM2_PROGRAMMATIC = 'true';
      CLI.restart(app_name, function(err, data) {
        process.env.PM2_PROGRAMMATIC = 'false';
        if (!err)
          Common.printOut(cst.PREFIX_MSG + 'Module %s restarted', app_name);
        displayConf(app_name, function() {
          return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
        });
      });
      return false;
    }
    displayConf(app_name, function() {
      return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
    });

  });
};

CLI.unset = function(key, cb) {
  Configuration.unset(key, function(err) {
    if (err) {
      return cb ? cb({success:false, err:err }) : exitCli(cst.ERROR_EXIT);
    }

    displayConf(function() {
      return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
    });
  });
};

CLI.conf = function(key, value, cb) {
  if (typeof(value) === 'function') {
    cb = value;
    value = null;
  };

  // If key + value = set
  if (key && value) {
    CLI.set(key, value, function(err) {
      if (err)
        return cb ? cb({success:false, err:err}) : exitCli(cst.ERROR_EXIT);
      return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
    });
  }
  // If only key = get
  else if (key) {
    CLI.get(key, function(err, data) {
      if (err)
        return cb ? cb({success:false, err:err}) : exitCli(cst.ERROR_EXIT);
      return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
    });
  }
  else {
    displayConf(function(err, data) {
      if (err)
        return cb ? cb({success:false, err:err}) : exitCli(cst.ERROR_EXIT);
      return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
    });
  }
};

/**
 * Launch interactor
 * @method interact
 * @param {string} secret_key
 * @param {string} public_key
 * @param {string} machine_name
 */
CLI.interact = function(secret_key, public_key, machine_name, recycle, cb) {
  if (typeof(recycle) === 'function') {
    cb = recycle;
    recycle = null;
  }
  if (typeof(recycle) !== 'boolean') {
    recycle = false;
  }

  InteractorDaemonizer.launchAndInteract({
    secret_key   : secret_key || null,
    public_key   : public_key || null,
    machine_name : machine_name || null,
    recycle      : recycle || null
  }, function(err, dt) {
    if (err)
      return cb ? cb(err) : exitCli(cst.ERROR_EXIT);
    return cb ? cb(null, dt) : exitCli(cst.SUCCESS_EXIT);
  });
};

/**
 * Kill interactor
 * @method killInteract
 */
CLI.killInteract = function(cb) {
  InteractorDaemonizer.killDaemon(function(err) {
    return cb ? cb({msg:'Interactor not launched'}) : exitCli(cst.SUCCESS_EXIT);
  });
};

/**
 * Get information about interactor connection
 * @method infoInteract
 */
CLI.infoInteract = function(cb) {
  getInteractInfo(function(err, data) {
    if (err) {
      printError('Interactor not launched');
      return cb ? cb({msg:'Interactor not launched'}) : exitCli(cst.ERROR_EXIT);
    }
    printOut(data);
    return cb ? cb(null, data) : exitCli(cst.SUCCESS_EXIT);
  });
};

var Version              = require('./tools/VersionManagement.js');

/**
 * CLI method for updating a repository
 * @method pullAndRestart
 * @param {string} process_name name of processes to pull
 * @return
 */
CLI.pullAndRestart = function (process_name, cb) {
  Version._pull({process_name: process_name, action: 'reload'}, cb);
};

/**
 * CLI method for updating a repository
 * @method pullAndReload
 * @param {string} process_name name of processes to pull
 * @return
 */
CLI.pullAndReload = function (process_name, cb) {
  Version._pull({process_name: process_name, action: 'reload'}, cb);
};

/**
 * CLI method for updating a repository
 * @method pullAndGracefulReload
 * @param {string} process_name name of processes to pull
 * @return
 */
CLI.pullAndGracefulReload = function (process_name, cb) {
  Version._pull({process_name: process_name, action: 'gracefulReload'}, cb);
};

/**
 * CLI method for updating a repository to a specific commit id
 * @method pullCommitId
 * @param {object} opts
 * @return
 */
CLI.pullCommitId = function (opts, cb) {
  Version.pullCommitId(opts.pm2_name, opts.commit_id);
};

/**
 * CLI method for downgrading a repository to the previous commit (older)
 * @method backward
 * @param {string} process_name
 * @return
 */
CLI.backward = Version.backward;

/**
 * CLI method for updating a repository to the next commit (more recent)
 * @method forward
 * @param {string} process_name
 * @return
 */
CLI.forward = Version.forward;


/**
 * CLI method for triggering garbage collection manually
 * @method forcegc
 * @return
 */
CLI.forceGc = CLI.gc = function(cb) {
  Satan.executeRemote('forceGc', {}, function(err, data) {
    if (data && data.success === false) {
      printError(cst.PREFIX_MSG_ERR + 'Garbage collection failed');
      return cb ? cb({success:false}) : exitCli(cst.ERROR_EXIT);
    } else {
      printOut(cst.PREFIX_MSG + 'Garbage collection manually triggered');
      return cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
    }
  });
};


/**
 * CLI method to perform a deep update of PM2
 * @method deepUpdate
 */
CLI.deepUpdate = function(cb) {
  printOut(cst.PREFIX_MSG + 'Updating PM2...');

  var exec = require('shelljs').exec;
  var child = exec("npm i -g pm2@latest; pm2 update", {async : true});

  child.stdout.on('end', function() {
    printOut(cst.PREFIX_MSG + 'PM2 successfully updated');
    cb ? cb(null, {success:true}) : exitCli(cst.SUCCESS_EXIT);
  });
};

//
// Private methods
//

/**
 * Description
 * @method getInteractInfo
 * @param {} cb
 * @return
 */
function getInteractInfo(cb) {
  debug('Getting interaction info');
  InteractorDaemonizer.ping(function(online) {
    if (!online) {
      return cb({msg : 'offline'});
    }
    InteractorDaemonizer.launchRPC(function() {
      InteractorDaemonizer.rpc.getInfos(function(err, infos) {
        if (err) {
          return cb(err);
        }
        InteractorDaemonizer.disconnectRPC(function() {
          return cb(null, infos);
        });
        return false;
      });
    });
    return false;
  });
}

/**
 * Asynchronous interactor checking
 */
var gl_interact_infos = null;

getInteractInfo(function(i_err, interact) {
  if (i_err) {
    gl_interact_infos = null;
    return;
  }
  gl_interact_infos = interact;
});



var gl_retry = 0;

/**
 * Description
 * @method speedList
 * @return
 */
function speedList(code) {
  var self = this;

  Satan.executeRemote('getMonitorData', {}, function(err, list) {
    if (err) {
      if (gl_retry == 0) {
        gl_retry += 1;
        return setTimeout(speedList, 1400);
      }
      console.error('Error retrieving process list: %s.\nA process seems to be on infinite loop, retry in 5 seconds',err);
      return exitCli(cst.ERROR_EXIT);
    }
    if (commander.miniList && !commander.silent)
      UX.miniDisplay(list);
    else if (!commander.silent) {
      if (gl_interact_infos) {
        printOut(chalk.green.bold('●') + ' Agent online - public key: %s - machine name: %s - Web access: https://app.keymetrics.io/', gl_interact_infos.public_key, gl_interact_infos.machine_name);
      }
      UX.dispAsTable(list, gl_interact_infos);
      printOut(chalk.white.italic(' Use `pm2 show <id|name>` to get more details about an app'));
    }

    if (Satan._noDaemonMode) {
      printOut('--no-daemon option enabled = do not exit pm2 CLI');
      printOut('PM2 daemon PID = %s', fs.readFileSync(cst.PM2_PID_FILE_PATH));
      return CLI.streamLogs('all', 0, false, 'HH:mm:ss', false);
    }
    else {
      return exitCli(code ? code : cst.SUCCESS_EXIT);
    }
  });
}

/**
 * Extend the app.env object of with the properties taken from the app.env_[envName] and deploy configuration.
 * @param {Object} app The app object.
 * @param {string} envName The given environment name.
 * @param {Object} deployConf Deployment configuration object (from JSON file or whatever).
 * @returns {Object} The app.env variables object.
 */
function mergeEnvironmentVariables(app, envName, deployConf) {
  var new_conf = util._extend({}, app);
  delete new_conf.env;

  if (!envName) {
    // The environment name was not set with either `pm2 deploy envName` or `--env envName`
    app.env = app.env || {};
  } else {
    var finalEnv = {};

    // First merge variables from deploy.production.env object as least priority.
    if (deployConf && deployConf[envName] && deployConf[envName]['env']) {
      util._extend(finalEnv, deployConf[envName]['env']);
    }

    // Then merge app.env object.
    if (app.env) {
      util._extend(finalEnv, app.env);
    }

    // Then, last and highest priority, merge the app.env_production object.
    if ('env_' + envName in app) {
      util._extend(finalEnv, app['env_' + envName]);
    }

    app.env = finalEnv;
  }

  for(var key in app.env) {
    if(typeof app.env[key] == 'object') {
      app.env[key] = JSON.stringify(app.env[key])
    }
  }

  return util._extend(app.env, new_conf);
}

/**
 * Description
 * @method resolvePaths
 * @param {object} appConf
 * @return app
 */
function resolvePaths(appConf) {
  var cwd = null;

  if (appConf.cwd) {
    cwd = p.resolve(appConf.cwd);
    process.env.PWD = appConf.cwd;
  }

  var app = Common.prepareAppConf(appConf, cwd, console.log);
  if (app instanceof Error) {
    printError(cst.PREFIX_MSG_ERR + app.message);
    throw new Error(app.message);
  }
  return app;
}

/**
 * Verify configurations.
 * @param {Array} appConfs
 * @returns {Array}
 */
function verifyConfs(appConfs){
  if (!appConfs || appConfs.length == 0){
    return [];
  }

  // Make sure it is an Array.
  appConfs = [].concat(appConfs);

  var verifiedConf = [];

  for (var i = 0; i < appConfs.length; i++) {
    var app = appConfs[i];

    // Warn deprecates.
    checkDeprecates(app);

    // Check Exec mode
    checkExecMode(app);

    // Render an app name if not existing.
    prepareAppName(app);

    // Check execute interpreter.
    prepareInterpreter(app);

    debug('Before processing', app);
    // Verify JSON.
    var ret = Config.verifyJSON(app);
    debug('After processing', ret);

    // Show errors if existing.
    if (ret.errors && ret.errors.length > 0){
      ret.errors.forEach(function(err){
        warn(err);
      });
      // Return null == error
      return null;
    }
    verifiedConf.push(ret.config);
  }


  // if (verifiedConf.length == 0) {
  //   warn('No verified configuration yet, peaceful exit.');
  //   return exitCli(cst.SUCCESS_EXIT);
  // }

  return verifiedConf;
}

/**
 * Check if right Node.js version for cluster mode
 * @param {Object} conf
 */
function checkExecMode(conf) {

  if (conf.exec_mode === 'cluster' ||
      conf.exec_mode === 'cluster_mode' ||
      conf.instances && conf.exec_mode === undefined)
    conf.exec_mode = 'cluster_mode';
  else
    conf.exec_mode = 'fork_mode';

  // -x -i 4

  if (!isNaN(conf.instances) && /^fork(_mode)?$/i.test(conf.exec_mode)) {

    warn('You are starting ' +
         chalk.blue(conf.instances) +
         ' processes in ' +
         chalk.blue(conf.exec_mode) +
         ' without load balancing. To enable it remove -x option.');
  }

  if (conf.instances && conf.exec_mode === undefined)
    conf.exec_mode = 'cluster_mode';

  // Tell user about unstability of cluster module + Roadmap
  if (/^cluster(_mode)?$/i.test(conf.exec_mode) &&
      process.version.match(/0.10/) &&
      !process.env.TRAVIS) {
    warn('You should not use the cluster_mode (-i) in production, it\'s still a beta feature. A front HTTP load balancer or interaction with NGINX will be developed in the future.');
  }
}

/**
 * Check deprecates and show warnings.
 * @param {Object} conf
 */
function checkDeprecates(conf){
  if (conf.instances == 'max'){
    warn('Deprecated, we recommend using ' + chalk.blue(0) + ' instead of ' + chalk.blue('max') + ' to indicate maximum of instances.');
    conf.instances = 0;
  }

  // Sanity check, default to number of cores if value can't be parsed
  if (typeof(conf.instances) === 'string')
    conf.instances = parseInt(conf.instances) || 0;

  // Ensure instance param is not a negative value
  // if (conf.instances < 0) {
  //   warn('You passed a negative value to indicate the number of instances... Setting this to maximum instances.');
  //   conf.instances = 0;
  // }
}

/**
 * Render an app name if not existing.
 * @param {Object} conf
 */
function prepareAppName(conf){
  if(!conf.name){
    conf.name = p.basename(conf.script);
    var lastDot = conf.name.lastIndexOf('.');
    if(lastDot > 0){
      conf.name = conf.name.slice(0, lastDot);
    }
  }
}

/**
 * Check execute interpreter.
 * @param {Object} conf
 */
function prepareInterpreter(conf){
  var betterInterpreter = extItps[path.extname(conf.script)];

  if (conf.exec_interpreter && betterInterpreter){
    if (betterInterpreter != conf.exec_interpreter){
      warn('We\'ve notice that you are running the ' + chalk.blue(betterInterpreter) + ' script, but currently using a ' +
           chalk.blue(conf.exec_interpreter) + ' interpreter, may be you need inspect the ' + chalk.blue('--interpreter') + ' option.');
    }
  } else if (!conf.exec_interpreter) {
    conf.exec_interpreter = betterInterpreter || 'none';
  }

}

/**
 * Parses a config file like ecosystem.json. Supported formats: JS, JSON, JSON5.
 * @param {string} confString  contents of the config file
 * @param {string} filename    path to the config file
 * @return {Object} config object
 */
function parseConfig(confString, filename) {
  var code = '(' + confString + ')';
  var sandbox = {};
  if (semver.satisfies(process.version, '>= 0.12.0')) {
    return vm.runInThisContext(code, sandbox, {
      filename: path.resolve(filename),
      displayErrors: false,
      timeout: 1000,
    });
  } else {
    // Use the Node 0.10 API
    return vm.runInNewContext(code, sandbox, filename);
  }
}

/**
 * Show warnings
 * @param {String} warning
 */
function warn(warning){
  printOut(cst.PREFIX_MSG_WARNING + warning);
}

function deployHelp() {
  console.log('');
  console.log('-----> Helper: Deployment with PM2');
  console.log('');
  console.log('  Generate a sample ecosystem.json with the command');
  console.log('  $ pm2 ecosystem');
  console.log('  Then edit the file depending on your needs');
  console.log('');
  console.log('  Commands:');
  console.log('    setup                run remote setup commands');
  console.log('    update               update deploy to the latest release');
  console.log('    revert [n]           revert to [n]th last deployment or 1');
  console.log('    curr[ent]            output current release commit');
  console.log('    prev[ious]           output previous release commit');
  console.log('    exec|run <cmd>       execute the given <cmd>');
  console.log('    list                 list previous deploy commits');
  console.log('    [ref]                deploy to [ref], the "ref" setting, or latest tag');
  console.log('');
  console.log('');
  console.log('  Basic Examples:');
  console.log('');
  console.log('    First initialize remote production host:');
  console.log('    $ pm2 deploy ecosystem.json production setup');
  console.log('');
  console.log('    Then deploy new code:');
  console.log('    $ pm2 deploy ecosystem.json production');
  console.log('');
  console.log('    If I want to revert to the previous commit:');
  console.log('    $ pm2 deploy ecosystem.json production revert 1');
  console.log('');
  console.log('    Execute a command on remote server:');
  console.log('    $ pm2 deploy ecosystem.json production exec "pm2 restart all"');
  console.log('');
  console.log('    PM2 will look by default to the ecosystem.json file so you dont need to give the file name:');
  console.log('    $ pm2 deploy production');
  console.log('    Else you have to tell PM2 the name of your ecosystem file');
  console.log('');
  console.log('    More examples in https://github.com/Unitech/pm2');
  console.log('');
}
