'use strict';
// @ts-check
// ==================================================================================
// processes.js
// ----------------------------------------------------------------------------------
// Description:   System Information - library
//                for Node.js
// Copyright:     (c) 2014 - 2026
// Author:        Sebastian Hildebrandt
// ----------------------------------------------------------------------------------
// License:       MIT
// ==================================================================================
// 10. Processes
// ----------------------------------------------------------------------------------

const os = require('os');
const fs = require('fs');
const path = require('path');
const exec = require('child_process').exec;
const execSync = require('child_process').execSync;

const util = require('./util');

let _platform = process.platform;

const _linux = _platform === 'linux' || _platform === 'android';
const _darwin = _platform === 'darwin';
const _windows = _platform === 'win32';
const _freebsd = _platform === 'freebsd';
const _openbsd = _platform === 'openbsd';
const _netbsd = _platform === 'netbsd';
const _sunos = _platform === 'sunos';

const _processes_cpu = {
  all: 0,
  all_utime: 0,
  all_stime: 0,
  list: {},
  ms: 0,
  result: {}
};
const _services_cpu = {
  all: 0,
  all_utime: 0,
  all_stime: 0,
  list: {},
  ms: 0,
  result: {}
};
const _process_cpu = {
  all: 0,
  all_utime: 0,
  all_stime: 0,
  list: {},
  ms: 0,
  result: {}
};

const _winStatusValues = {
  0: 'unknown',
  1: 'other',
  2: 'ready',
  3: 'running',
  4: 'blocked',
  5: 'suspended blocked',
  6: 'suspended ready',
  7: 'terminated',
  8: 'stopped',
  9: 'growing'
};

function parseTimeUnix(time) {
  let result = time;
  let parts = time.replace(/ +/g, ' ').split(' ');
  if (parts.length === 5) {
    result = parts[4] + '-' + ('0' + ('JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC'.indexOf(parts[1].toUpperCase()) / 3 + 1)).slice(-2) + '-' + ('0' + parts[2]).slice(-2) + ' ' + parts[3];
  }
  return result;
}

function parseElapsedTime(etime) {
  let current = new Date();
  current = new Date(current.getTime() - current.getTimezoneOffset() * 60000);

  const elapsed = etime.split('-');

  const timeIndex = elapsed.length - 1;
  const days = timeIndex > 0 ? parseInt(elapsed[timeIndex - 1]) : 0;

  const timeStr = elapsed[timeIndex].split(':');
  const hours = timeStr.length === 3 ? parseInt(timeStr[0] || 0) : 0;
  const mins = parseInt(timeStr[timeStr.length === 3 ? 1 : 0] || 0);
  const secs = parseInt(timeStr[timeStr.length === 3 ? 2 : 1] || 0);
  const ms = (((days * 24 + hours) * 60 + mins) * 60 + secs) * 1000;

  let res = new Date(current.getTime());
  let result = res.toISOString().substring(0, 10) + ' ' + res.toISOString().substring(11, 19);
  try {
    res = new Date(current.getTime() - ms);
    result = res.toISOString().substring(0, 10) + ' ' + res.toISOString().substring(11, 19);
  } catch (e) {
    util.noop();
  }
  return result;
}

// --------------------------
// PS - services
// pass a comma separated string with services to check (mysql, apache, postgresql, ...)
// this function gives an array back, if the services are running.

function services(srv, callback) {
  // fallback - if only callback is given
  if (util.isFunction(srv) && !callback) {
    callback = srv;
    srv = '';
  }

  return new Promise((resolve) => {
    process.nextTick(() => {
      if (typeof srv !== 'string') {
        if (callback) {
          callback([]);
        }
        return resolve([]);
      }

      if (srv) {
        let srvString = '';
        try {
          srvString.__proto__.toLowerCase = util.stringToLower;
          srvString.__proto__.replace = util.stringReplace;
          srvString.__proto__.toString = util.stringToString;
          srvString.__proto__.substr = util.stringSubstr;
          srvString.__proto__.substring = util.stringSubstring;
          srvString.__proto__.trim = util.stringTrim;
          srvString.__proto__.startsWith = util.stringStartWith;
        } catch (e) {
          Object.setPrototypeOf(srvString, util.stringObj);
        }

        const s = util.sanitizeShellString(srv);
        const l = util.mathMin(s.length, 2000);
        for (let i = 0; i <= l; i++) {
          if (s[i] !== undefined) {
            srvString = srvString + s[i];
          }
        }

        srvString = srvString.trim().toLowerCase().replace(/, /g, '|').replace(/,+/g, '|');
        if (srvString === '') {
          srvString = '*';
        }
        if (util.isPrototypePolluted() && srvString !== '*') {
          srvString = '------';
        }
        let srvs = srvString.split('|');
        let result = [];
        let dataSrv = [];

        if (_linux || _freebsd || _openbsd || _netbsd || _darwin) {
          if ((_linux || _freebsd || _openbsd || _netbsd) && srvString === '*') {
            try {
              const tmpsrv = execSync('systemctl --all --type=service --no-legend 2> /dev/null', util.execOptsLinux).toString().split('\n');
              srvs = [];
              for (const s of tmpsrv) {
                const name = s.split('.service')[0];
                if (name && s.indexOf(' not-found ') === -1) {
                  srvs.push(name.trim());
                }
              }
              srvString = srvs.join('|');
            } catch (d) {
              try {
                srvString = '';
                const tmpsrv = execSync('service --status-all 2> /dev/null', util.execOptsLinux).toString().split('\n');
                for (const s of tmpsrv) {
                  const parts = s.split(']');
                  if (parts.length === 2) {
                    srvString += (srvString !== '' ? '|' : '') + parts[1].trim();
                  }
                }
                srvs = srvString.split('|');
              } catch (e) {
                try {
                  const srvStr = execSync('ls /etc/init.d/ -m 2> /dev/null', util.execOptsLinux).toString().split('\n').join('');
                  srvString = '';
                  if (srvStr) {
                    const tmpsrv = srvStr.split(',');
                    for (const s of tmpsrv) {
                      const name = s.trim();
                      if (name) {
                        srvString += (srvString !== '' ? '|' : '') + name;
                      }
                    }
                    srvs = srvString.split('|');
                  }
                } catch (f) {
                  srvString = '';
                  srvs = [];
                }
              }
            }
          }
          if (_darwin && srvString === '*') {
            // service enumeration not yet suported on mac OS
            if (callback) {
              callback(result);
            }
            resolve(result);
          }
          let args = _darwin ? ['-caxo', 'pcpu,pmem,pid,command'] : ['-axo', 'pcpu,pmem,pid,command'];
          if (srvString !== '' && srvs.length > 0) {
            util.execSafe('ps', args).then((stdout) => {
              if (stdout) {
                let lines = stdout.replace(/ +/g, ' ').replace(/,+/g, '.').split('\n');
                srvs.forEach(function (srv) {
                  let ps;
                  if (_darwin) {
                    ps = lines.filter(function (e) {
                      return e.toLowerCase().indexOf(srv) !== -1;
                    });
                  } else {
                    ps = lines.filter(function (e) {
                      return (
                        e.toLowerCase().indexOf(' ' + srv.toLowerCase() + ':') !== -1 ||
                        e.toLowerCase().indexOf('(' + srv.toLowerCase() + ' ') !== -1 ||
                        e.toLowerCase().indexOf('(' + srv.toLowerCase() + ')') !== -1 ||
                        e.toLowerCase().indexOf(' ' + srv.toLowerCase().replace(/[0-9.]/g, '') + ':') !== -1 ||
                        e.toLowerCase().indexOf('/' + srv.toLowerCase()) !== -1
                      );
                    });
                  }
                  const pids = [];
                  for (const p of ps) {
                    const pid = p.trim().split(' ')[2];
                    if (pid) {
                      pids.push(parseInt(pid, 10));
                    }
                  }
                  result.push({
                    name: srv,
                    running: ps.length > 0,
                    startmode: '',
                    pids: pids,
                    cpu: parseFloat(
                      ps
                        .reduce(function (pv, cv) {
                          return pv + parseFloat(cv.trim().split(' ')[0]);
                        }, 0)
                        .toFixed(2)
                    ),
                    mem: parseFloat(
                      ps
                        .reduce(function (pv, cv) {
                          return pv + parseFloat(cv.trim().split(' ')[1]);
                        }, 0)
                        .toFixed(2)
                    )
                  });
                });
                if (_linux) {
                  // calc process_cpu - ps is not accurate in linux!
                  let cmd = 'cat /proc/stat | grep "cpu "';
                  for (let i in result) {
                    for (let j in result[i].pids) {
                      cmd += ';cat /proc/' + result[i].pids[j] + '/stat';
                    }
                  }
                  exec(cmd, { maxBuffer: 1024 * 102400 }, function (error, stdout) {
                    let curr_processes = stdout.toString().split('\n');

                    // first line (all - /proc/stat)
                    let all = parseProcStat(curr_processes.shift());

                    // process
                    let list_new = {};
                    let resultProcess = {};
                    curr_processes.forEach((element) => {
                      resultProcess = calcProcStatLinux(element, all, _services_cpu);

                      if (resultProcess.pid) {
                        let listPos = -1;
                        for (let i in result) {
                          for (let j in result[i].pids) {
                            if (parseInt(result[i].pids[j]) === parseInt(resultProcess.pid)) {
                              listPos = i;
                            }
                          }
                        }
                        if (listPos >= 0) {
                          result[listPos].cpu += resultProcess.cpuu + resultProcess.cpus;
                        }

                        // save new values
                        list_new[resultProcess.pid] = {
                          cpuu: resultProcess.cpuu,
                          cpus: resultProcess.cpus,
                          utime: resultProcess.utime,
                          stime: resultProcess.stime,
                          cutime: resultProcess.cutime,
                          cstime: resultProcess.cstime
                        };
                      }
                    });

                    // store old values
                    _services_cpu.all = all;
                    _services_cpu.list = Object.assign({}, list_new);
                    _services_cpu.ms = Date.now() - _services_cpu.ms;
                    _services_cpu.result = Object.assign({}, result);
                    if (callback) {
                      callback(result);
                    }
                    resolve(result);
                  });
                } else {
                  if (callback) {
                    callback(result);
                  }
                  resolve(result);
                }
              } else {
                args = ['-o', 'comm'];
                util.execSafe('ps', args).then((stdout) => {
                  if (stdout) {
                    let lines = stdout.replace(/ +/g, ' ').replace(/,+/g, '.').split('\n');
                    srvs.forEach(function (srv) {
                      let ps = lines.filter(function (e) {
                        return e.indexOf(srv) !== -1;
                      });
                      result.push({
                        name: srv,
                        running: ps.length > 0,
                        startmode: '',
                        cpu: 0,
                        mem: 0
                      });
                    });
                    if (callback) {
                      callback(result);
                    }
                    resolve(result);
                  } else {
                    srvs.forEach(function (srv) {
                      result.push({
                        name: srv,
                        running: false,
                        startmode: '',
                        cpu: 0,
                        mem: 0
                      });
                    });
                    if (callback) {
                      callback(result);
                    }
                    resolve(result);
                  }
                });
              }
            });
          } else {
            if (callback) {
              callback(result);
            }
            resolve(result);
          }
        }
        if (_windows) {
          try {
            let wincommand = 'Get-CimInstance Win32_Service';
            if (srvs[0] !== '*') {
              wincommand += ' -Filter "';
              srvs.forEach((srv) => {
                wincommand += `Name='${srv}' or `;
              });
              wincommand = `${wincommand.slice(0, -4)}"`;
            }
            wincommand += ' | select Name,Caption,Started,StartMode,ProcessId | fl';
            util.powerShell(wincommand).then((stdout, error) => {
              if (!error) {
                let serviceSections = stdout.split(/\n\s*\n/);
                serviceSections.forEach((element) => {
                  if (element.trim() !== '') {
                    let lines = element.trim().split('\r\n');
                    let srvName = util.getValue(lines, 'Name', ':', true).toLowerCase();
                    let srvCaption = util.getValue(lines, 'Caption', ':', true).toLowerCase();
                    let started = util.getValue(lines, 'Started', ':', true);
                    let startMode = util.getValue(lines, 'StartMode', ':', true);
                    let pid = util.getValue(lines, 'ProcessId', ':', true);
                    if (srvString === '*' || srvs.indexOf(srvName) >= 0 || srvs.indexOf(srvCaption) >= 0) {
                      result.push({
                        name: srvName,
                        running: started.toLowerCase() === 'true',
                        startmode: startMode,
                        pids: [pid],
                        cpu: 0,
                        mem: 0
                      });
                      dataSrv.push(srvName);
                      dataSrv.push(srvCaption);
                    }
                  }
                });

                if (srvString !== '*') {
                  const srvsMissing = srvs.filter((e) => dataSrv.indexOf(e) === -1);
                  srvsMissing.forEach((srvName) => {
                    result.push({
                      name: srvName,
                      running: false,
                      startmode: '',
                      pids: [],
                      cpu: 0,
                      mem: 0
                    });
                  });
                }
                if (callback) {
                  callback(result);
                }
                resolve(result);
              } else {
                srvs.forEach((srvName) => {
                  result.push({
                    name: srvName,
                    running: false,
                    startmode: '',
                    cpu: 0,
                    mem: 0
                  });
                });
                if (callback) {
                  callback(result);
                }
                resolve(result);
              }
            });
          } catch {
            if (callback) {
              callback(result);
            }
            resolve(result);
          }
        }
      } else {
        if (callback) {
          callback([]);
        }
        resolve([]);
      }
    });
  });
}

exports.services = services;

function parseProcStat(line) {
  const parts = line.replace(/ +/g, ' ').split(' ');
  const user = parts.length >= 2 ? parseInt(parts[1]) : 0;
  const nice = parts.length >= 3 ? parseInt(parts[2]) : 0;
  const system = parts.length >= 4 ? parseInt(parts[3]) : 0;
  const idle = parts.length >= 5 ? parseInt(parts[4]) : 0;
  const iowait = parts.length >= 6 ? parseInt(parts[5]) : 0;
  const irq = parts.length >= 7 ? parseInt(parts[6]) : 0;
  const softirq = parts.length >= 8 ? parseInt(parts[7]) : 0;
  const steal = parts.length >= 9 ? parseInt(parts[8]) : 0;
  const guest = parts.length >= 10 ? parseInt(parts[9]) : 0;
  const guest_nice = parts.length >= 11 ? parseInt(parts[10]) : 0;
  return user + nice + system + idle + iowait + irq + softirq + steal + guest + guest_nice;
}

function calcProcStatLinux(line, all, _cpu_old) {
  let statparts = line.replace(/ +/g, ' ').split(')');
  if (statparts.length >= 2) {
    let parts = statparts[1].split(' ');
    if (parts.length >= 16) {
      let pid = parseInt(statparts[0].split(' ')[0]);
      let utime = parseInt(parts[12]);
      let stime = parseInt(parts[13]);
      let cutime = parseInt(parts[14]);
      let cstime = parseInt(parts[15]);

      // calc
      let cpuu = 0;
      let cpus = 0;
      if (_cpu_old.all > 0 && _cpu_old.list[pid]) {
        cpuu = ((utime + cutime - _cpu_old.list[pid].utime - _cpu_old.list[pid].cutime) / (all - _cpu_old.all)) * 100; // user
        cpus = ((stime + cstime - _cpu_old.list[pid].stime - _cpu_old.list[pid].cstime) / (all - _cpu_old.all)) * 100; // system
      } else {
        cpuu = ((utime + cutime) / all) * 100; // user
        cpus = ((stime + cstime) / all) * 100; // system
      }
      return {
        pid: pid,
        utime: utime,
        stime: stime,
        cutime: cutime,
        cstime: cstime,
        cpuu: cpuu,
        cpus: cpus
      };
    } else {
      return {
        pid: 0,
        utime: 0,
        stime: 0,
        cutime: 0,
        cstime: 0,
        cpuu: 0,
        cpus: 0
      };
    }
  } else {
    return {
      pid: 0,
      utime: 0,
      stime: 0,
      cutime: 0,
      cstime: 0,
      cpuu: 0,
      cpus: 0
    };
  }
}

function calcProcStatWin(procStat, all, _cpu_old) {
  // calc
  let cpuu = 0;
  let cpus = 0;
  if (_cpu_old.all > 0 && _cpu_old.list[procStat.pid]) {
    cpuu = ((procStat.utime - _cpu_old.list[procStat.pid].utime) / (all - _cpu_old.all)) * 100; // user
    cpus = ((procStat.stime - _cpu_old.list[procStat.pid].stime) / (all - _cpu_old.all)) * 100; // system
  } else {
    cpuu = (procStat.utime / all) * 100; // user
    cpus = (procStat.stime / all) * 100; // system
  }
  return {
    pid: procStat.pid,
    utime: procStat.utime,
    stime: procStat.stime,
    cpuu: cpuu > 0 ? cpuu : 0,
    cpus: cpus > 0 ? cpus : 0
  };
}

// --------------------------
// running processes

function processes(callback) {
  let parsedhead = [];

  function getName(command) {
    command = command || '';
    let result = command.split(' ')[0];
    if (result.substr(-1) === ':') {
      result = result.substr(0, result.length - 1);
    }
    if (result.substr(0, 1) !== '[') {
      let parts = result.split('/');
      if (isNaN(parseInt(parts[parts.length - 1]))) {
        result = parts[parts.length - 1];
      } else {
        result = parts[0];
      }
    }
    return result;
  }

  function parseLine(line) {
    let offset = 0;
    let offset2 = 0;

    function checkColumn(i) {
      offset = offset2;
      if (parsedhead[i]) {
        offset2 = line.substring(parsedhead[i].to + offset, 10000).indexOf(' ');
      } else {
        offset2 = 10000;
      }
    }

    checkColumn(0);
    const pid = parseInt(line.substring(parsedhead[0].from + offset, parsedhead[0].to + offset2));
    checkColumn(1);
    const ppid = parseInt(line.substring(parsedhead[1].from + offset, parsedhead[1].to + offset2));
    checkColumn(2);
    const cpu = parseFloat(line.substring(parsedhead[2].from + offset, parsedhead[2].to + offset2).replace(/,/g, '.'));
    checkColumn(3);
    const mem = parseFloat(line.substring(parsedhead[3].from + offset, parsedhead[3].to + offset2).replace(/,/g, '.'));
    checkColumn(4);
    const priority = parseInt(line.substring(parsedhead[4].from + offset, parsedhead[4].to + offset2));
    checkColumn(5);
    const vsz = parseInt(line.substring(parsedhead[5].from + offset, parsedhead[5].to + offset2));
    checkColumn(6);
    const rss = parseInt(line.substring(parsedhead[6].from + offset, parsedhead[6].to + offset2));
    checkColumn(7);
    const nice = parseInt(line.substring(parsedhead[7].from + offset, parsedhead[7].to + offset2)) || 0;
    checkColumn(8);
    const started = !_sunos
      ? parseElapsedTime(line.substring(parsedhead[8].from + offset, parsedhead[8].to + offset2).trim())
      : parseTimeUnix(line.substring(parsedhead[8].from + offset, parsedhead[8].to + offset2).trim());
    checkColumn(9);
    let state = line.substring(parsedhead[9].from + offset, parsedhead[9].to + offset2).trim();
    state =
      state[0] === 'R'
        ? 'running'
        : state[0] === 'S'
          ? 'sleeping'
          : state[0] === 'T'
            ? 'stopped'
            : state[0] === 'W'
              ? 'paging'
              : state[0] === 'X'
                ? 'dead'
                : state[0] === 'Z'
                  ? 'zombie'
                  : state[0] === 'D' || state[0] === 'U'
                    ? 'blocked'
                    : 'unknown';
    checkColumn(10);
    let tty = line.substring(parsedhead[10].from + offset, parsedhead[10].to + offset2).trim();
    if (tty === '?' || tty === '??') {
      tty = '';
    }
    checkColumn(11);
    const user = line.substring(parsedhead[11].from + offset, parsedhead[11].to + offset2).trim();
    checkColumn(12);
    let cmdPath = '';
    let command = '';
    let params = '';
    let fullcommand = line.substring(parsedhead[12].from + offset, parsedhead[12].to + offset2).trim();
    if (fullcommand.substr(fullcommand.length - 1) === ']') {
      fullcommand = fullcommand.slice(0, -1);
    }
    if (fullcommand.substr(0, 1) === '[') {
      command = fullcommand.substring(1);
    } else {
      const p1 = fullcommand.indexOf('(');
      const p2 = fullcommand.indexOf(')');
      const p3 = fullcommand.indexOf('/');
      const p4 = fullcommand.indexOf(':');
      if (p1 < p2 && p1 < p3 && p3 < p2) {
        command = fullcommand.split(' ')[0];
        command = command.replace(/:/g, '');
      } else {
        if (p4 > 0 && (p3 === -1 || p3 > 3)) {
          command = fullcommand.split(' ')[0];
          command = command.replace(/:/g, '');
        } else {
          // try to figure out where parameter starts
          let firstParamPos = fullcommand.indexOf(' -');
          let firstParamPathPos = fullcommand.indexOf(' /');
          firstParamPos = firstParamPos >= 0 ? firstParamPos : 10000;
          firstParamPathPos = firstParamPathPos >= 0 ? firstParamPathPos : 10000;
          const firstPos = Math.min(firstParamPos, firstParamPathPos);
          let tmpCommand = fullcommand.substr(0, firstPos);
          const tmpParams = fullcommand.substr(firstPos);
          const lastSlashPos = tmpCommand.lastIndexOf('/');
          if (lastSlashPos >= 0) {
            cmdPath = tmpCommand.substr(0, lastSlashPos);
            tmpCommand = tmpCommand.substr(lastSlashPos + 1);
          }

          if (firstPos === 10000 && tmpCommand.indexOf(' ') > -1) {
            const parts = tmpCommand.split(' ');
            if (fs.existsSync(path.join(cmdPath, parts[0]))) {
              command = parts.shift();
              params = (parts.join(' ') + ' ' + tmpParams).trim();
            } else {
              command = tmpCommand.trim();
              params = tmpParams.trim();
            }
          } else {
            command = tmpCommand.trim();
            params = tmpParams.trim();
          }
        }
      }
    }

    return {
      pid: pid,
      parentPid: ppid,
      name: _linux ? getName(command) : command,
      cpu: cpu,
      cpuu: 0,
      cpus: 0,
      mem: mem,
      priority: priority,
      memVsz: vsz,
      memRss: rss,
      nice: nice,
      started: started,
      state: state,
      tty: tty,
      user: user,
      command: command,
      params: params,
      path: cmdPath
    };
  }

  function parseProcesses(lines) {
    let result = [];
    if (lines.length > 1) {
      let head = lines[0];
      parsedhead = util.parseHead(head, 8);
      lines.shift();
      lines.forEach((line) => {
        if (line.trim() !== '') {
          result.push(parseLine(line));
        }
      });
    }
    return result;
  }
  function parseProcesses2(lines) {
    function formatDateTime(time) {
      const month = ('0' + (time.getMonth() + 1).toString()).slice(-2);
      const year = time.getFullYear().toString();
      const day = ('0' + time.getDate().toString()).slice(-2);
      const hours = ('0' + time.getHours().toString()).slice(-2);
      const mins = ('0' + time.getMinutes().toString()).slice(-2);
      const secs = ('0' + time.getSeconds().toString()).slice(-2);

      return year + '-' + month + '-' + day + ' ' + hours + ':' + mins + ':' + secs;
    }

    function parseElapsed(etime) {
      let started = '';
      if (etime.indexOf('d') >= 0) {
        const elapsed_parts = etime.split('d');
        started = formatDateTime(new Date(Date.now() - (elapsed_parts[0] * 24 + elapsed_parts[1] * 1) * 60 * 60 * 1000));
      } else if (etime.indexOf('h') >= 0) {
        const elapsed_parts = etime.split('h');
        started = formatDateTime(new Date(Date.now() - (elapsed_parts[0] * 60 + elapsed_parts[1] * 1) * 60 * 1000));
      } else if (etime.indexOf(':') >= 0) {
        const elapsed_parts = etime.split(':');
        started = formatDateTime(new Date(Date.now() - (elapsed_parts.length > 1 ? (elapsed_parts[0] * 60 + elapsed_parts[1]) * 1000 : elapsed_parts[0] * 1000)));
      }
      return started;
    }

    let result = [];
    lines.forEach((line) => {
      if (line.trim() !== '') {
        line = line.trim().replace(/ +/g, ' ').replace(/,+/g, '.');
        const parts = line.split(' ');
        const command = parts.slice(9).join(' ');
        const pmem = parseFloat(((1.0 * parseInt(parts[3]) * 1024) / os.totalmem()).toFixed(1));
        const started = parseElapsed(parts[5]);

        result.push({
          pid: parseInt(parts[0]),
          parentPid: parseInt(parts[1]),
          name: getName(command),
          cpu: 0,
          cpuu: 0,
          cpus: 0,
          mem: pmem,
          priority: 0,
          memVsz: parseInt(parts[2]),
          memRss: parseInt(parts[3]),
          nice: parseInt(parts[4]),
          started: started,
          state:
            parts[6] === 'R'
              ? 'running'
              : parts[6] === 'S'
                ? 'sleeping'
                : parts[6] === 'T'
                  ? 'stopped'
                  : parts[6] === 'W'
                    ? 'paging'
                    : parts[6] === 'X'
                      ? 'dead'
                      : parts[6] === 'Z'
                        ? 'zombie'
                        : parts[6] === 'D' || parts[6] === 'U'
                          ? 'blocked'
                          : 'unknown',
          tty: parts[7],
          user: parts[8],
          command: command
        });
      }
    });
    return result;
  }

  return new Promise((resolve) => {
    process.nextTick(() => {
      let result = {
        all: 0,
        running: 0,
        blocked: 0,
        sleeping: 0,
        unknown: 0,
        list: []
      };

      let cmd = '';

      if ((_processes_cpu.ms && Date.now() - _processes_cpu.ms >= 500) || _processes_cpu.ms === 0) {
        if (_linux || _freebsd || _openbsd || _netbsd || _darwin || _sunos) {
          if (_linux) {
            cmd = 'export LC_ALL=C; ps -axo pid:11,ppid:11,pcpu:6,pmem:6,pri:5,vsz:11,rss:11,ni:5,etime:30,state:5,tty:15,user:20,command; unset LC_ALL';
          }
          if (_freebsd || _openbsd || _netbsd) {
            cmd = 'export LC_ALL=C; ps -axo pid,ppid,pcpu,pmem,pri,vsz,rss,ni,etime,state,tty,user,command; unset LC_ALL';
          }
          if (_darwin) {
            cmd = 'ps -axo pid,ppid,pcpu,pmem,pri,vsz=temp_title_1,rss=temp_title_2,nice,etime=temp_title_3,state,tty,user,command -r';
          }
          if (_sunos) {
            cmd = 'ps -Ao pid,ppid,pcpu,pmem,pri,vsz,rss,nice,stime,s,tty,user,comm';
          }
          try {
            exec(cmd, { maxBuffer: 1024 * 102400 }, (error, stdout) => {
              if (!error && stdout.toString().trim()) {
                result.list = parseProcesses(stdout.toString().split('\n')).slice();
                result.all = result.list.length;
                result.running = result.list.filter((e) => {
                  return e.state === 'running';
                }).length;
                result.blocked = result.list.filter((e) => {
                  return e.state === 'blocked';
                }).length;
                result.sleeping = result.list.filter((e) => {
                  return e.state === 'sleeping';
                }).length;

                if (_linux) {
                  // calc process_cpu - ps is not accurate in linux!
                  cmd = 'cat /proc/stat | grep "cpu "';
                  result.list.forEach((element) => {
                    cmd += ';cat /proc/' + element.pid + '/stat';
                  });
                  exec(cmd, { maxBuffer: 1024 * 102400 }, (error, stdout) => {
                    let curr_processes = stdout.toString().split('\n');

                    // first line (all - /proc/stat)
                    let all = parseProcStat(curr_processes.shift());

                    // process
                    let list_new = {};
                    let resultProcess = {};
                    curr_processes.forEach((element) => {
                      resultProcess = calcProcStatLinux(element, all, _processes_cpu);

                      if (resultProcess.pid) {
                        // store pcpu in outer array
                        let listPos = result.list
                          .map((e) => {
                            return e.pid;
                          })
                          .indexOf(resultProcess.pid);
                        if (listPos >= 0) {
                          result.list[listPos].cpu = resultProcess.cpuu + resultProcess.cpus;
                          result.list[listPos].cpuu = resultProcess.cpuu;
                          result.list[listPos].cpus = resultProcess.cpus;
                        }

                        // save new values
                        list_new[resultProcess.pid] = {
                          cpuu: resultProcess.cpuu,
                          cpus: resultProcess.cpus,
                          utime: resultProcess.utime,
                          stime: resultProcess.stime,
                          cutime: resultProcess.cutime,
                          cstime: resultProcess.cstime
                        };
                      }
                    });

                    // store old values
                    _processes_cpu.all = all;
                    _processes_cpu.list = Object.assign({}, list_new);
                    _processes_cpu.ms = Date.now() - _processes_cpu.ms;
                    _processes_cpu.result = Object.assign({}, result);
                    if (callback) {
                      callback(result);
                    }
                    resolve(result);
                  });
                } else {
                  if (callback) {
                    callback(result);
                  }
                  resolve(result);
                }
              } else {
                cmd = 'ps -o pid,ppid,vsz,rss,nice,etime,stat,tty,user,comm';
                if (_sunos) {
                  cmd = 'ps -o pid,ppid,vsz,rss,nice,etime,s,tty,user,comm';
                }
                exec(cmd, { maxBuffer: 1024 * 102400 }, (error, stdout) => {
                  if (!error) {
                    let lines = stdout.toString().split('\n');
                    lines.shift();

                    result.list = parseProcesses2(lines).slice();
                    result.all = result.list.length;
                    result.running = result.list.filter((e) => {
                      return e.state === 'running';
                    }).length;
                    result.blocked = result.list.filter((e) => {
                      return e.state === 'blocked';
                    }).length;
                    result.sleeping = result.list.filter((e) => {
                      return e.state === 'sleeping';
                    }).length;
                    if (callback) {
                      callback(result);
                    }
                    resolve(result);
                  } else {
                    if (callback) {
                      callback(result);
                    }
                    resolve(result);
                  }
                });
              }
            });
          } catch {
            if (callback) {
              callback(result);
            }
            resolve(result);
          }
        } else if (_windows) {
          try {
            util
              .powerShell(
                `Get-CimInstance Win32_Process | select-Object ProcessId,ParentProcessId,ExecutionState,Caption,CommandLine,ExecutablePath,UserModeTime,KernelModeTime,WorkingSetSize,Priority,PageFileUsage,
                @{n="CreationDate";e={$_.CreationDate.ToString("yyyy-MM-dd HH:mm:ss")}} | ConvertTo-Json -compress`
              )
              .then((stdout, error) => {
                if (!error) {
                  const procs = [];
                  const procStats = [];
                  const list_new = {};
                  let allcpuu = 0;
                  let allcpus = 0;
                  let processArray = [];
                  try {
                    stdout = stdout.trim().replace(/^\uFEFF/, '');
                    processArray = JSON.parse(stdout);
                  } catch {}
                  processArray.forEach((element) => {
                    const pid = element.ProcessId;
                    const parentPid = element.ParentProcessId;
                    const statusValue = element.ExecutionState || null;
                    const name = element.Caption;
                    const commandLine = element.CommandLine;
                    // get additional command line data
                    const commandPath = element.ExecutablePath;
                    const utime = element.UserModeTime;
                    const stime = element.KernelModeTime;
                    const memw = element.WorkingSetSize;
                    allcpuu = allcpuu + utime;
                    allcpus = allcpus + stime;
                    result.all++;
                    if (!statusValue) {
                      result.unknown++;
                    }
                    if (statusValue === '3') {
                      result.running++;
                    }
                    if (statusValue === '4' || statusValue === '5') {
                      result.blocked++;
                    }

                    procStats.push({
                      pid: pid,
                      utime: utime,
                      stime: stime,
                      cpu: 0,
                      cpuu: 0,
                      cpus: 0
                    });
                    procs.push({
                      pid: pid,
                      parentPid: parentPid,
                      name: name,
                      cpu: 0,
                      cpuu: 0,
                      cpus: 0,
                      mem: (memw / os.totalmem()) * 100,
                      priority: element.Priority | null,
                      memVsz: element.PageFileUsage || null,
                      memRss: Math.floor((element.WorkingSetSize || 0) / 1024),
                      nice: 0,
                      started: element.CreationDate,
                      state: statusValue ? _winStatusValues[statusValue] : _winStatusValues[0],
                      tty: '',
                      user: '',
                      command: commandLine || name,
                      path: commandPath,
                      params: ''
                    });
                  });

                  result.sleeping = result.all - result.running - result.blocked - result.unknown;
                  result.list = procs;
                  procStats.forEach((element) => {
                    let resultProcess = calcProcStatWin(element, allcpuu + allcpus, _processes_cpu);

                    // store pcpu in outer array
                    let listPos = result.list.map((e) => e.pid).indexOf(resultProcess.pid);
                    if (listPos >= 0) {
                      result.list[listPos].cpu = resultProcess.cpuu + resultProcess.cpus;
                      result.list[listPos].cpuu = resultProcess.cpuu;
                      result.list[listPos].cpus = resultProcess.cpus;
                    }

                    // save new values
                    list_new[resultProcess.pid] = {
                      cpuu: resultProcess.cpuu,
                      cpus: resultProcess.cpus,
                      utime: resultProcess.utime,
                      stime: resultProcess.stime
                    };
                  });

                  // store old values
                  _processes_cpu.all = allcpuu + allcpus;
                  _processes_cpu.all_utime = allcpuu;
                  _processes_cpu.all_stime = allcpus;
                  _processes_cpu.list = Object.assign({}, list_new);
                  _processes_cpu.ms = Date.now() - _processes_cpu.ms;
                  _processes_cpu.result = Object.assign({}, result);
                }
                if (callback) {
                  callback(result);
                }
                resolve(result);
              });
          } catch {
            if (callback) {
              callback(result);
            }
            resolve(result);
          }
        } else {
          if (callback) {
            callback(result);
          }
          resolve(result);
        }
      } else {
        if (callback) {
          callback(_processes_cpu.result);
        }
        resolve(_processes_cpu.result);
      }
    });
  });
}

exports.processes = processes;

// --------------------------
// PS - process load
// get detailed information about a certain process
// (PID, CPU-Usage %, Mem-Usage %)

function processLoad(proc, callback) {
  // fallback - if only callback is given
  if (util.isFunction(proc) && !callback) {
    callback = proc;
    proc = '';
  }

  return new Promise((resolve) => {
    process.nextTick(() => {
      proc = proc || '';

      if (typeof proc !== 'string') {
        if (callback) {
          callback([]);
        }
        return resolve([]);
      }

      let processesString = '';
      try {
        processesString.__proto__.toLowerCase = util.stringToLower;
        processesString.__proto__.replace = util.stringReplace;
        processesString.__proto__.toString = util.stringToString;
        processesString.__proto__.substr = util.stringSubstr;
        processesString.__proto__.substring = util.stringSubstring;
        processesString.__proto__.trim = util.stringTrim;
        processesString.__proto__.startsWith = util.stringStartWith;
      } catch {
        Object.setPrototypeOf(processesString, util.stringObj);
      }

      const s = util.sanitizeShellString(proc);
      const l = util.mathMin(s.length, 2000);

      for (let i = 0; i <= l; i++) {
        if (s[i] !== undefined) {
          processesString = processesString + s[i];
        }
      }

      processesString = processesString.trim().toLowerCase().replace(/, /g, '|').replace(/,+/g, '|');
      if (processesString === '') {
        processesString = '*';
      }
      if (util.isPrototypePolluted() && processesString !== '*') {
        processesString = '------';
      }
      let processes = processesString.split('|');
      let result = [];

      const procSanitized = util.isPrototypePolluted() ? '' : util.sanitizeShellString(proc) || '*';

      // from here new
      // let result = {
      //   'proc': procSanitized,
      //   'pid': null,
      //   'cpu': 0,
      //   'mem': 0
      // };
      if (procSanitized && processes.length && processes[0] !== '------') {
        if (_windows) {
          try {
            util.powerShell('Get-CimInstance Win32_Process | select ProcessId,Caption,UserModeTime,KernelModeTime,WorkingSetSize | ConvertTo-Json -compress').then((stdout, error) => {
              if (!error) {
                const procStats = [];
                const list_new = {};
                let allcpuu = 0;
                let allcpus = 0;
                let processArray = [];
                try {
                  stdout = stdout.trim().replace(/^\uFEFF/, '');
                  processArray = JSON.parse(stdout);
                } catch {}

                // go through all processes
                processArray.forEach((element) => {
                  const pid = element.ProcessId;
                  const name = element.Caption;
                  const utime = element.UserModeTime;
                  const stime = element.KernelModeTime;
                  const mem = element.WorkingSetSize;
                  allcpuu = allcpuu + utime;
                  allcpus = allcpus + stime;

                  procStats.push({
                    pid: pid,
                    name,
                    utime: utime,
                    stime: stime,
                    cpu: 0,
                    cpuu: 0,
                    cpus: 0,
                    mem
                  });
                  let pname = '';
                  let inList = false;
                  processes.forEach((proc) => {
                    if (name.toLowerCase().indexOf(proc.toLowerCase()) >= 0 && !inList) {
                      inList = true;
                      pname = proc;
                    }
                  });

                  if (processesString === '*' || inList) {
                    let processFound = false;
                    result.forEach((item) => {
                      if (item.proc.toLowerCase() === pname.toLowerCase()) {
                        item.pids.push(pid);
                        item.mem += (mem / os.totalmem()) * 100;
                        processFound = true;
                      }
                    });
                    if (!processFound) {
                      result.push({
                        proc: pname,
                        pid: pid,
                        pids: [pid],
                        cpu: 0,
                        mem: (mem / os.totalmem()) * 100
                      });
                    }
                  }
                });

                if (processesString !== '*') {
                  // add missing processes
                  let processesMissing = processes.filter((name) => procStats.filter((item) => item.name.toLowerCase().indexOf(name) >= 0).length === 0);
                  processesMissing.forEach((procName) => {
                    result.push({
                      proc: procName,
                      pid: null,
                      pids: [],
                      cpu: 0,
                      mem: 0
                    });
                  });
                }

                // calculate proc stats for each proc
                procStats.forEach((element) => {
                  let resultProcess = calcProcStatWin(element, allcpuu + allcpus, _process_cpu);

                  let listPos = -1;
                  for (let j = 0; j < result.length; j++) {
                    if (result[j].pid === resultProcess.pid || result[j].pids.indexOf(resultProcess.pid) >= 0) {
                      listPos = j;
                    }
                  }
                  if (listPos >= 0) {
                    result[listPos].cpu += resultProcess.cpuu + resultProcess.cpus;
                  }

                  // save new values
                  list_new[resultProcess.pid] = {
                    cpuu: resultProcess.cpuu,
                    cpus: resultProcess.cpus,
                    utime: resultProcess.utime,
                    stime: resultProcess.stime
                  };
                });

                // store old values
                _process_cpu.all = allcpuu + allcpus;
                _process_cpu.all_utime = allcpuu;
                _process_cpu.all_stime = allcpus;
                _process_cpu.list = Object.assign({}, list_new);
                _process_cpu.ms = Date.now() - _process_cpu.ms;
                _process_cpu.result = JSON.parse(JSON.stringify(result));
                if (callback) {
                  callback(result);
                }
                resolve(result);
              }
            });
          } catch {
            if (callback) {
              callback(result);
            }
            resolve(result);
          }
        }

        if (_darwin || _linux || _freebsd || _openbsd || _netbsd) {
          const params = ['-axo', 'pid,ppid,pcpu,pmem,comm'];
          util.execSafe('ps', params).then((stdout) => {
            if (stdout) {
              const procStats = [];
              const lines = stdout
                .toString()
                .split('\n')
                .filter((line) => {
                  if (processesString === '*') {
                    return true;
                  }
                  if (line.toLowerCase().indexOf('grep') !== -1) {
                    return false;
                  } // remove this??
                  let found = false;
                  processes.forEach((item) => {
                    found = found || line.toLowerCase().indexOf(item.toLowerCase()) >= 0;
                  });
                  return found;
                });
              lines.shift();
              lines.forEach((line) => {
                const data = line.trim().replace(/ +/g, ' ').split(' ');
                if (data.length > 4) {
                  const linuxName = data[4].indexOf('/') >= 0 ? data[4].substring(0, data[4].indexOf('/')) : data[4];
                  const name = _linux ? linuxName : data[4].substring(data[4].lastIndexOf('/') + 1);
                  procStats.push({
                    name,
                    pid: parseInt(data[0]) || 0,
                    ppid: parseInt(data[1]) || 0,
                    cpu: parseFloat(data[2].replace(',', '.')),
                    mem: parseFloat(data[3].replace(',', '.'))
                  });
                }
              });

              procStats.forEach((item) => {
                let listPos = -1;
                let inList = false;
                let name = item.name;
                for (let j = 0; j < result.length; j++) {
                  if (item.name.toLowerCase().indexOf(result[j].proc.toLowerCase()) >= 0) {
                    listPos = j;
                  }
                }
                processes.forEach((proc) => {
                  if (item.name.toLowerCase().indexOf(proc.toLowerCase()) >= 0 && !inList) {
                    inList = true;
                    name = proc;
                  }
                });
                if (processesString === '*' || inList) {
                  if (listPos < 0) {
                    if (name) {
                      result.push({
                        proc: name,
                        pid: item.pid,
                        pids: [item.pid],
                        cpu: item.cpu,
                        mem: item.mem
                      });
                    }
                  } else {
                    if (item.ppid < 10) {
                      result[listPos].pid = item.pid;
                    }
                    result[listPos].pids.push(item.pid);
                    result[listPos].cpu += item.cpu;
                    result[listPos].mem += item.mem;
                  }
                }
              });

              if (processesString !== '*') {
                // add missing processes
                let processesMissing = processes.filter((name) => {
                  return (
                    procStats.filter((item) => {
                      return item.name.toLowerCase().indexOf(name) >= 0;
                    }).length === 0
                  );
                });
                processesMissing.forEach((procName) => {
                  result.push({
                    proc: procName,
                    pid: null,
                    pids: [],
                    cpu: 0,
                    mem: 0
                  });
                });
              }
              if (_linux) {
                // calc process_cpu - ps is not accurate in linux!
                result.forEach((item) => {
                  item.cpu = 0;
                });
                let cmd = 'cat /proc/stat | grep "cpu "';
                for (let i in result) {
                  for (let j in result[i].pids) {
                    cmd += ';cat /proc/' + result[i].pids[j] + '/stat';
                  }
                }
                exec(cmd, { maxBuffer: 1024 * 102400 }, (error, stdout) => {
                  let curr_processes = stdout.toString().split('\n');

                  // first line (all - /proc/stat)
                  let all = parseProcStat(curr_processes.shift());

                  // process
                  let list_new = {};
                  let resultProcess = {};
                  curr_processes.forEach((element) => {
                    resultProcess = calcProcStatLinux(element, all, _process_cpu);

                    if (resultProcess.pid) {
                      // find result item
                      let resultItemId = -1;
                      for (let i in result) {
                        if (result[i].pids.indexOf(resultProcess.pid) >= 0) {
                          resultItemId = i;
                        }
                      }
                      // store pcpu in outer result
                      if (resultItemId >= 0) {
                        result[resultItemId].cpu += resultProcess.cpuu + resultProcess.cpus;
                      }

                      // save new values
                      list_new[resultProcess.pid] = {
                        cpuu: resultProcess.cpuu,
                        cpus: resultProcess.cpus,
                        utime: resultProcess.utime,
                        stime: resultProcess.stime,
                        cutime: resultProcess.cutime,
                        cstime: resultProcess.cstime
                      };
                    }
                  });

                  result.forEach((item) => {
                    item.cpu = Math.round(item.cpu * 100) / 100;
                  });

                  _process_cpu.all = all;
                  _process_cpu.list = Object.assign({}, list_new);
                  _process_cpu.ms = Date.now() - _process_cpu.ms;
                  _process_cpu.result = Object.assign({}, result);
                  if (callback) {
                    callback(result);
                  }
                  resolve(result);
                });
              } else {
                if (callback) {
                  callback(result);
                }
                resolve(result);
              }
            } else {
              if (callback) {
                callback(result);
              }
              resolve(result);
            }
          });
        }
      }
    });
  });
}

exports.processLoad = processLoad;
