'use strict'
const fs = require('fs')
const path = require('path')
const { spawn } = require('child_process')
const debug = require('debug')('0x')
const traceStacksToTicks = require('../lib/trace-stacks-to-ticks')
const { promisify } = require('util')

const { SOFT_EXIT_SIGNALS } = require('../lib/preload/soft-exit')

const {
  getTargetFolder,
  tidy,
  pathTo,
  spawnOnPort,
  when
} = require('../lib/util')

module.exports = promisify(linux)

function linux (args, sudo, cb) {
  const { status, outputDir, workingDir, name, onPort, pathToNodeBinary } = args
  const perf = pathTo('perf')
  if (!perf) {
    cb(Error('Unable to locate perf - make sure it\'s in your PATH'))
    return
  }
  if (!sudo) {
    status('Stacks are captured using perf(1), which requires sudo access\n')
    return spawn('sudo', ['true'])
      .on('exit', function () { linux(args, true, cb) })
  }

  const uid = parseInt(Math.random() * 1e9, 10).toString(36)
  const perfdat = '/tmp/perf-' + uid + '.data'
  const kernelTracingDebug = args.kernelTracingDebug

  const proc = spawn('sudo', [
    '-E',
    'perf',
    'record',
    !kernelTracingDebug ? '-q' : '',
    '-F 99',
    '-g',
    '-o',
    perfdat,
    '--',
    pathToNodeBinary,
    '--perf-basic-prof',
    '-r', path.join(__dirname, '..', 'lib', 'preload', 'soft-exit'),
    ...(onPort ? ['-r', path.join(__dirname, '..', 'lib', 'preload', 'detect-port.js')] : [])
  ].filter(Boolean).concat(args.argv), {
    stdio: ['ignore', 'inherit', 'inherit', 'ignore', 'ignore', 'pipe']
  }).on('exit', function (code) {
    args.onProcessExit(code)
    if (code !== null && code !== 0 && code !== 143 && code !== 130) {
      tidy(args)
      console.error('Tracing subprocess error, code: ' + code)
      return
    }
    filterInternalFunctions(perfdat)
  })

  const folder = getTargetFolder({ outputDir, workingDir, name, pid: proc.pid })

  if (onPort) status('Profiling\n')
  else status('Profiling')

  if (onPort) {
    when(proc.stdio[5], 'data').then((port) => {
      const whenPort = spawnOnPort(onPort, port)
      whenPort.then(() => proc.kill('SIGINT'))
      whenPort.catch((err) => {
        proc.kill()
        cb(err)
      })
    })
  }

  const handleExit = () => {
    spawn('sudo', ['kill', '-SIGINT', '' + proc.pid], {
      stdio: 'inherit'
    })
  }

  for (let i = 0; i < SOFT_EXIT_SIGNALS.length; i++) {
    process.once(SOFT_EXIT_SIGNALS[i], handleExit)
  }

  function analyze (manual) {
    if (analyze.called) { return }
    analyze.called = true

    if (!manual) {
      debug('Caught SOFT_EXIT_SIGNAL, generating flamegraph')
      status('Caught SOFT_EXIT_SIGNAL, generating flamegraph')
      proc.on('exit', generate)
    } else {
      debug('Process exited, generating flamegraph')
      status('Process exited, generating flamegraph')
      generate()
    }

    function generate () {
      const stacks = spawn('sudo', ['perf', 'script', '-i', perfdat], {
        stdio: [
          'ignore',
          fs.openSync(folder + '/stacks.' + proc.pid + '.out', 'w'),
          kernelTracingDebug ? process.stderr : 'ignore'
        ]
      })

      stacks.on('exit', function () {
        cb(null, {
          ticks: traceStacksToTicks(folder + '/stacks.' + proc.pid + '.out'),
          pid: proc.pid,
          folder: folder,
          // TODO: Inlined functions through linux_perf was not implemented yet
          inlined: []
        })
      })
    }
  }

  function filterInternalFunctions (perfFile) {
    // Filtering out Node.js internal functions
    const sed = spawn('sudo', [
      'sed',
      '-i',
      '-e',
      '/( __libc_start| LazyCompile | v8::internal::| Builtin:| Stub:| LoadIC:|[unknown]| LoadPolymorphicIC:)/d',
      '-e',
      's/ LazyCompile:[*~]?/ /',
      perfFile
    ], {
      stdio: ['ignore', 'inherit', 'inherit', 'ignore', 'ignore', 'pipe']
    })

    sed.on('exit', function (code) {
      if (code !== null && code !== 0) {
        console.error('`sed` subprocess error, code: ' + code)
        return
      }
      analyze(true)
    })
  }
}
