#!/usr/bin/env node

var program = require('commander');
var spawn = require('child_process').spawn;
var chokidar = require('chokidar');
var fs = require('fs');
var kill = require('tree-kill');

program
  .option('-s, --suite <suite>', 'The suite to run Authelia for. This suite represents a configuration for Authelia and a set of tests for that configuration.')
  .parse(process.argv)

if (!program.suite) {
  throw new Error('Please provide a suite.');
}

const ENVIRONMENT_FILENAME = '.suite';
const AUTHELIA_INTERRUPT_FILENAME = '.authelia-interrupt';
const CONFIG_FILEPATH = `test/suites/${program.suite}/config.yml`; 


var tsWatcher = chokidar.watch(['server', 'shared/**/*.ts', 'node_modules', AUTHELIA_INTERRUPT_FILENAME, CONFIG_FILEPATH], {
  persistent: true,
  ignoreInitial: true,
});

// Properly cleanup server and client if ctrl-c is hit
process.on('SIGINT', function() {
  killServer();
  killClient();
  fs.unlinkSync(ENVIRONMENT_FILENAME);
  process.exit();
});


let serverProcess;
function reloadServer() {
  killServer(() => {
    startServer();
  });
}

function startServer() {
  if (fs.existsSync(AUTHELIA_INTERRUPT_FILENAME)) {
    console.log('Authelia is interrupted. Consider removing ' + AUTHELIA_INTERRUPT_FILENAME + ' if it\'s not expected.');
    return;
  }
  exec('./node_modules/.bin/tslint', ['-c', 'server/tslint.json', '-p', 'server/tsconfig.json'])
    .then(function() {
      serverProcess = spawn('./scripts/run-dev-server.sh', [CONFIG_FILEPATH]);
      serverProcess.stdout.pipe(process.stdout);
      serverProcess.stderr.pipe(process.stderr);
    });
}

let clientProcess;
function startClient() {
  clientProcess = spawn('npm', ['run', 'start'], {
    cwd: './client',
    env: {
      ...process.env,
      'BROWSER': 'none'
    }
  });
  clientProcess.stdout.pipe(process.stdout);
  clientProcess.stderr.pipe(process.stderr);
}

function killServer(onExit) {
  if (serverProcess) {
    serverProcess.on('exit', () => {
      serverProcess = undefined;
      if (onExit) onExit();
    });
    try {
      kill(serverProcess.pid, 'SIGKILL');
    } catch (e) {
      console.error(e);
      if (onExit) onExit();
    }
  } else {
    if (onExit) onExit();
  }
}

function killClient(onExit) {
  if (clientProcess) {
    clientProcess.on('exit', () => {
      clientProcess = undefined;
      if (onExit) onExit();
    });
    try {
      kill(clientProcess.pid, 'SIGKILL');
    } catch (e) {
      console.error(e);
      if (onExit) onExit();
    }
  } else {
    if (onExit) onExit();
  }
}

function generateConfigurationSchema() {
  return ixec('./node_modules/.bin/typescript-json-schema',
    ['-o', 'server/src/lib/configuration/Configuration.schema.json', '--strictNullChecks', '--required', 'server/tsconfig.json', 'Configuration']);
}

function reload(path) {
  console.log(`File ${path} has been changed, reloading...`);
  if (path.startsWith('server/src/lib/configuration/schema')) {
    console.log('Schema needs to be regenerated.');
    generateConfigurationSchema();
  }
  else if (path === AUTHELIA_INTERRUPT_FILENAME) {
    if (fs.existsSync(path)) {
      console.log('Authelia is being interrupted.');
      killServer();
    } else {
      console.log('Authelia is restarting.');
      startServer();
    }
    return;
  }
  reloadServer();
}

function exec(command, args) {
  return new Promise((resolve, reject) => {
    const cmd = spawn(command, args);

    cmd.stdout.pipe(process.stdout);
    cmd.stderr.pipe(process.stderr);
    cmd.on('close', (code) => {
      if (code == 0) {
        resolve();
        return;
      }
      reject(new Error('Status code ' + code));
    });
  });
}

async function main() {
  console.log(`Create suite file ${ENVIRONMENT_FILENAME}.`);
  fs.writeFileSync(ENVIRONMENT_FILENAME, program.suite);

  console.log(`Render nginx configuration...`);
  await exec('./example/compose/nginx/portal/render.js');

  console.log(`Prepare environment with docker-compose...`);
  await exec('./scripts/utils/prepare-environment.sh');
  
  console.log('Start watching...');
  tsWatcher.on('add', reload);
  tsWatcher.on('unlink', reload);
  tsWatcher.on('change', reload);
  
  startServer();
  startClient();
}

main()