'use strict';

/**
 * @file Utilities for PM2
 * @author Alexandre Strzelewicz <as@unitech.io>
 * @project PM2
 */
var p             = require('path');
var Common        = require('../Common');
var treekill      = require('../TreeKill');
var cst           = require('../../constants.js');
var debug         = require('debug')('god:methods');
var os            = require('os');

/**
 * Kill process group in a cross platform way
 * @method crossPlatformGroupKill
 * @param int pid
 * @param string sig
 */
function crossPlatformGroupKill(pid, sig) {
    // This would have not been needed if node.js were able
    // to handle group process kill on Windows (pid < -1).
    if (os.platform() === 'win32') treekill(pid, sig);
    else process.kill(-pid, sig);
}

/**
 * Description
 * @method exports
 * @param {} God
 * @return
 */
module.exports = function(God) {

  /**
   * Description
   * @method logAndGenerateError
   * @param {} err
   * @return NewExpression
   */
  God.logAndGenerateError = function(err) {
    // Is an Error object
    if (err instanceof Error) {
      console.trace(err);
      return err;
    }
    // Is a JSON or simple string
    console.error(err);
    return new Error(err);
  };

  /**
   * Utility functions
   * @method getProcesses
   * @return MemberExpression
   */
  God.getProcesses = function() {
    return God.clusters_db;
  };

  /**
   * Description
   * @method getFormatedProcesses
   * @return arr
   */
  God.getFormatedProcesses = function getFormatedProcesses() {
    var db = Common.clone(God.clusters_db);
    var arr = [];

    for (var key in db) {
      if (db[key]) {
        arr.push({
          pid     : db[key].process.pid,
          name    : db[key].pm2_env.name,
          pm2_env : db[key].pm2_env,
          pm_id   : db[key].pm2_env.pm_id
        });
      }
    }
    db = null;
    return arr;
  };

  /**
   * Description
   * @method findProcessById
   * @param {} id
   * @return ConditionalExpression
   */
  God.findProcessById = function findProcessById(id) {
    return God.clusters_db[id] ? God.clusters_db[id] : null;
  };

  /**
   * Description
   * @method findByName
   * @param {} name
   * @return arr
   */
  God.findByName = function(name) {
    var db = God.clusters_db;
    var arr = [];

    if (name == 'all') {
      for (var key in db) {
        // Avoid _old_proc process style
        if (typeof(God.clusters_db[key].pm2_env.pm_id) === 'number')
          arr.push(db[key]);
      }
      return arr;
    }

    for (var key in db) {
      if (God.clusters_db[key].pm2_env.name == name ||
          God.clusters_db[key].pm2_env.pm_exec_path == p.resolve(name)) {
        arr.push(db[key]);
      }
    }
    return arr;
  };

  /**
   * Description
   * @method findByScript
   * @param {} script
   * @param {} cb
   * @return
   */
  God.findByScript = function(script, cb) {
    var db = Common.clone(God.clusters_db);
    var arr = [];

    for (var key in db) {
      if (p.basename(db[key].pm2_env.pm_exec_path) == script) {
        arr.push(db[key].pm2_env);
      }
    }
    cb(null, arr.length == 0 ? null : arr);
  };

  /**
   * Description
   * @method findByPort
   * @param {} port
   * @param {} cb
   * @return
   */
  God.findByPort = function(port, cb) {
    var db = God.clusters_db;
    var arr = [];

    for (var key in db) {
      if (db[key].pm2_env.port && db[key].pm2_env.port == port) {
        arr.push(db[key].pm2_env);
      }
    }
    cb(null, arr.length == 0 ? null : arr);
  };

  /**
   * Description
   * @method findByFullPath
   * @param {} path
   * @param {} cb
   * @return
   */
  God.findByFullPath = function(path, cb) {
    var db = God.clusters_db;
    var procs = [];

    for (var key in db) {
      if (db[key].pm2_env.pm_exec_path == path) {
        procs.push(db[key]);
      }
    }
    cb(null, procs.length == 0 ? null : procs);
  };

  /**
   * Check if a process is alive in system processes
   * Return TRUE if process online
   * @method checkProcess
   * @param {} pid
   * @return
   */
  God.checkProcess = function(pid) {
    if (!pid) return false;

    try {
      // Sending 0 signal do not kill the process
      process.kill(pid, 0);
      return true;
    }
    catch (err) {
      return false;
    }
  };

  /**
   * Description
   * @method processIsDead
   * @param {} pid
   * @param {} cb
   * @return Literal
   */
  God.processIsDead = function(pid, cb, sigkill) {
    if (!pid) return cb({type : 'param:missing', msg : 'no pid passed'});

    var timeout = null;

    var timer = setInterval(function() {
      if (God.checkProcess(pid) === false) {
        console.log('Process with pid %d killed', pid);
        clearTimeout(timeout);
        clearInterval(timer);
        return cb(null, true);
      }
      console.log('Process with pid %d still not killed, retrying...', pid);
      return false;
    }, 100);

    timeout = setTimeout(function() {
      clearInterval(timer);
      if (sigkill) {
        console.log('Process with pid %d could not be killed', pid);
        return cb({type : 'timeout', msg : 'timeout'});
      }
      else {
        console.log('Process with pid %d still alive after %sms, sending it SIGKILL now...', pid, cst.KILL_TIMEOUT);
        try {
          crossPlatformGroupKill(parseInt(pid),'SIGKILL');
        } catch(e) { console.error('Process cannot be killed', e.message || e.stack || e); }
        return God.processIsDead(pid, cb, true);
      }
    }, cst.KILL_TIMEOUT);
    return false;
  };

  /**
   * Description
   * @method killProcess
   * @param int pid
   * @param Object pm2_env
   * @param function cb
   * @return CallExpression
   */
  God.killProcess = function(pid, pm2_env, cb) {
    if (!pid) return cb({msg : 'no pid passed or null'});

    var mode = pm2_env.exec_mode;

    try {
      if(pm2_env.treekill !== true)
        process.kill(parseInt(pid));
      else {
        if (mode.indexOf('cluster') === 0)
          treekill(parseInt(pid));
        else
          crossPlatformGroupKill(parseInt(pid), 'SIGINT');
      }
    } catch(e) {
      console.error('%s pid can not be killed', pid, e);
      return cb(null, 'Cannot be killed');
    }

    return God.processIsDead(pid, cb);
  };

  /**
   * Description
   * @method getNewId
   * @return UpdateExpression
   */
  God.getNewId = function() {
    return God.next_id++;
  };

  /**
   * When a process is restarted or reloaded reset fields
   * to monitor unstable starts
   * @method resetState
   * @param {} pm2_env
   * @return
   */
  God.resetState = function(pm2_env) {
    pm2_env.created_at = Date.now();
    pm2_env.unstable_restarts = 0;
  };

  /**
   * Description
   * @method deepReset
   * @param {} pm2_env
   * @return
   */
  God.deepReset = function(pm2_env) {
    pm2_env.created_at = Date.now();
    pm2_env.unstable_restarts = 0;
  };

  /**
   * Description
   * @method forcegc
   * @return
   */
  God.forceGc = function(opts, cb) {
    if (global.gc) {
      global.gc();
      debug('Garbage collection triggered successfully');
      if (cb) cb(null, {success: true});
    }
    else {
      debug('Garbage collection failed');
      if (cb) cb(null, {success: false});
    }
  };

};
