/*
* @Author: zoujie.wzj
* @Date:   2016-01-23 18:25:37
* @Last Modified by: Sahel LUCAS--SAOUDI
* @Last Modified on: 2021-11-12
*/

'use strict'

const path = require('path')
const utils = require('./utils')

function matchName (text, name) {
  if (!name) {
    return true
  }
  // make sure text.match is valid, fix #30
  if (text && text.match) {
    return text.match(name)
  }
  return false
}

function fetchBin (cmd) {
  const pieces = cmd.split(path.sep)
  const last = pieces[pieces.length - 1]
  if (last) {
    pieces[pieces.length - 1] = last.split(' ')[0]
  }
  const fixed = []
  for (const part of pieces) {
    const optIdx = part.indexOf(' -')
    if (optIdx >= 0) {
      // case: /aaa/bbb/ccc -c
      fixed.push(part.substring(0, optIdx).trim())
      break
    } else if (part.endsWith(' ')) {
      // case: node /aaa/bbb/ccc.js
      fixed.push(part.trim())
      break
    }
    fixed.push(part)
  }
  return fixed.join(path.sep)
}

function fetchName (fullpath) {
  if (process.platform === 'darwin') {
    const idx = fullpath.indexOf('.app/')
    if (idx >= 0) {
      return path.basename(fullpath.substring(0, idx))
    }
  }
  return path.basename(fullpath)
}

const finders = {
  darwin (cond) {
    return new Promise((resolve, reject) => {
      let cmd
      if ('pid' in cond) {
        cmd = `ps -p ${cond.pid} -ww -o pid,ppid,uid,gid,args`
      } else {
        cmd = 'ps ax -ww -o pid,ppid,uid,gid,args'
      }

      utils.exec(cmd, function (err, stdout, stderr) {
        if (err) {
          if ('pid' in cond) {
            // when pid not exists, call `ps -p ...` will cause error, we have to
            // ignore the error and resolve with empty array
            resolve([])
          } else {
            reject(err)
          }
        } else {
          err = stderr.toString().trim()
          if (err) {
            reject(err)
            return
          }

          const data = utils.stripLine(stdout.toString(), 1)
          const columns = utils.extractColumns(data, [0, 1, 2, 3, 4], 5).filter(column => {
            if (column[0] && cond.pid) {
              return column[0] === String(cond.pid)
            } else if (column[4] && cond.name) {
              return matchName(column[4], cond.name)
            } else {
              return !!column[0]
            }
          })

          let list = columns.map(column => {
            const cmd = String(column[4])
            const bin = fetchBin(cmd)

            return {
              pid: parseInt(column[0], 10),
              ppid: parseInt(column[1], 10),
              uid: parseInt(column[2], 10),
              gid: parseInt(column[3], 10),
              name: fetchName(bin),
              bin: bin,
              cmd: column[4]
            }
          })

          if (cond.strict && cond.name) {
            list = list.filter(item => item.name === cond.name)
          }

          resolve(list)
        }
      })
    })
  },
  linux: 'darwin',
  sunos: 'darwin',
  freebsd: 'darwin',
  win32 (cond) {
    return new Promise((resolve, reject) => {
      const cmd = 'Get-CimInstance -className win32_process | select Name,ProcessId,ParentProcessId,CommandLine,ExecutablePath'
      const lines = []

      const proc = utils.spawn('powershell.exe', ['/c', cmd], { detached: false, windowsHide: true })
      proc.stdout.on('data', data => {
        lines.push(data.toString())
      })
      proc.on('close', code => {
        if (code !== 0) {
          return reject(new Error('Command \'' + cmd + '\' terminated with code: ' + code))
        }
        const list = utils.parseTable(lines.join(''))
          .filter(row => {
            if ('pid' in cond) {
              return row.ProcessId === String(cond.pid)
            } else if (cond.name) {
              const rowName = row.Name || '' // fix #40
              if (cond.strict) {
                return rowName === cond.name || (rowName.endsWith('.exe') && rowName.slice(0, -4) === cond.name)
              } else {
                // fix #9
                return matchName(row.CommandLine || rowName, cond.name)
              }
            } else {
              return true
            }
          })
          .map(row => ({
            pid: parseInt(row.ProcessId, 10),
            ppid: parseInt(row.ParentProcessId, 10),
            // uid: void 0,
            // gid: void 0,
            bin: row.ExecutablePath,
            name: row.Name || '',
            cmd: row.CommandLine
          }))
        resolve(list)
      })
    })
  },
  android (cond) {
    return new Promise((resolve, reject) => {
      const cmd = 'ps'

      utils.exec(cmd, function (err, stdout, stderr) {
        if (err) {
          if ('pid' in cond) {
            // when pid not exists, call `ps -p ...` will cause error, we have to
            // ignore the error and resolve with empty array
            resolve([])
          } else {
            reject(err)
          }
        } else {
          err = stderr.toString().trim()
          if (err) {
            reject(err)
            return
          }

          const data = utils.stripLine(stdout.toString(), 1)
          const columns = utils.extractColumns(data, [0, 3], 4).filter(column => {
            if (column[0] && cond.pid) {
              return column[0] === String(cond.pid)
            } else if (column[1] && cond.name) {
              return matchName(column[1], cond.name)
            } else {
              return !!column[0]
            }
          })

          let list = columns.map(column => {
            const cmd = String(column[1])
            const bin = fetchBin(cmd)

            return {
              pid: parseInt(column[0], 10),
              // ppid: void 0,
              // uid: void 0,
              // gid: void 0,
              name: fetchName(bin),
              bin,
              cmd
            }
          })

          if (cond.strict && cond.name) {
            list = list.filter(item => item.name === cond.name)
          }

          resolve(list)
        }
      })
    })
  }
}

function findProcess (cond) {
  const platform = process.platform

  return new Promise((resolve, reject) => {
    if (!(platform in finders)) {
      return reject(new Error(`platform ${platform} is unsupported`))
    }

    let find = finders[platform]
    if (typeof find === 'string') {
      find = finders[find]
    }

    find(cond).then(resolve, reject)
  })
}

module.exports = findProcess
