const psList = require('ps-list');
const psTree = require('ps-tree');
const { spawn } = require('child_process');
const path = require('path');
const waitOn = require('wait-on');

class DotnetApp {
  constructor(desktopApp) {
    this._spawned = false;
    this.desktop = desktopApp;
    this.log = desktopApp.log;

    // https://github.com/nodejs/node/issues/13538#issuecomment-307002165
    process.on('exit', this.onExit.bind(this));
  }

  prepareLaunch = async () => {
    const processes = await psList();
    // const rootProcesses = processes.filter(p => p.ppid <= 1);
    // rootProcesses.filter(p => p.cmd.includes('dotnet') || p.cmd.includes('Electron'));
    const mhProcesses = processes.filter(
      p => p.cmd.includes('--MAKER_HUB_ELECTRON'));
    if (mhProcesses.length === 0) return;
    this.log.info('[PREP]', 'found', mhProcesses.length, 'running MakerHub processes...');
    this.killProcs(mhProcesses.map(p => p.pid));
  }

  // Ensure that the dotnet app is running. Awaits the server to begin responding.
  start = async() => {
    if (this._spawned) return this.proc;
    
    await this.spawn();
    await this.resources();

    return this.proc;
  }

  resources = async() => {
    const resources = [this.desktop.urls.be];
    if (this.desktop.appDev) resources.push(this.desktop.urls.fe);
    this.log.info('[WAIT-ON]', resources);
    const loaded = await waitOn({ resources });
    this.log.info('[WAITED]', loaded);
    this._spawned = true;
    return resources;
  }

  // Spawn the dotnet environment
  spawn = async () => {
    await this.prepareLaunch();

    let proc;
    // Generate CLI flags for the app/server
    const flags = [];
    Object.keys(this.desktop.paths).forEach(k => {
      flags.push(`--path.${k}=\"${this.desktop.paths[k]}\"`);
    });
    flags.push('--MAKER_HUB_ELECTRON=true');

    // Actually spawn the dotnet/application server
    if (this.desktop.appDev) {
      proc = this.spawnDev(flags);
    } else {
      proc = this.spawnRelease(flags);
    }
    proc.stdout.pipe(process.stdout);
    proc.stderr.pipe(process.stderr);
    return proc;
  }

  spawnDev = (flags) => {
    // Object.keys(process.env).forEach(k => {
    //   if (k.startsWith('MAKER_')) flags.push(`--${k}=\"${process.env[k]}\""`);
    // });

    // dev bin path = where the code resides
    this.log.info('Spawning Dotnet Development Server', this.desktop.paths.bin, flags);
    return spawn('dotnet', ['watch', 'run'].concat(flags), {
      cwd: this.desktop.paths.bin,
    });
  }

  spawnRelease = (flags) => {
    const fnParts = [this.desktop.productName];
    if (process.platform.startsWith('win')) fnParts.push('exe');
    const exeFn = fnParts.join('.');

    // If not in electronDev, we cannot be using development assets...
    const exeFp = path.join(this.desktop.paths.bin, exeFn);

    this.log.info('Spawning Dotnet Server via', exeFn, flags);
    return spawn(exeFp, flags);
  }

  onExit = () => {
    this.log.info('exit');
    this.shutdown();
  }

  shutdown = () => {
    if (!this.proc) return;
    this.log.info('shutdown', 'server');
    const proc = this.proc;
    psTree(this.proc.pid, (err, procs) => {
      if (!procs) {
        proc.kill();
        return;
      }
      this.killProcs(procs.map(p => p.PID));
    });
    this.log.info('shutdown', 'complete');
    this.proc = null;
  }

  killProcs = (pids) => {
    for (let i=0; i<pids.length; i++) {
      const pid = pids[i];
      console.log('[KILL]', pid);
      process.kill(pid);
    }
  }

  // Scan open ports.
  // n.b., this only logs (does not abort) for now.
  // try {
  //   const status = await nodePortScanner(localHost, [this.bePort, this.bePort + 1, this.bePort + 2]);
  //   electronLog.log('[PORT]', localHost, this.bePort, 'status', status);
  // } catch (e) {
  //   electronLog.log('[PORT', localHost, this.bePort, '[ERROR]', e);
  // }

}

module.exports = DotnetApp;
