console.log(``);
console.log(`CRT Terminator Firmware Update Utility v1.0, 2025-01-25`);
console.log(`Provided firmware are (C) Oulainen Multimedia Group, licensed only for use with CRT Terminator products.`);
console.log(``);

const getch = async() => {
  process.stdin.setRawMode(true);
  return new Promise(resolve => process.stdin.once('data', key => {
    process.stdin.setRawMode(false);
    if (key[0] == 3 || key[0] == 27) {
      console.log('Aborted by user');
      process.exit(1);
    }
    resolve(key);
  }));
};

const run_get_stdout = async(cmd) => {
  return new Promise((resolve, reject) => {
    require('child_process').exec(cmd, (error, stdout, stderr) => {
      resolve(stdout);
    });
  });
};

const run_print_stdout = async(cmd, args) => {
  return new Promise((resolve, reject) => {  
    var child = require('child_process').spawn(cmd, args);

    var stdout = '', stderr = '';
    child.stdout.setEncoding('utf8');
    child.stdout.on('data', function(data) {
      stdout += data;
      while (stdout.includes('\n')) {
        console.log(stdout.slice(0, stdout.indexOf('\n')));
        stdout = stdout.slice(stdout.indexOf('\n')+1);
      }
    });

    child.stderr.setEncoding('utf8');
    child.stderr.on('data', function(data) {
      stderr += data;
      while (stderr.includes('\n')) {
        console.log(stderr.slice(0, stderr.indexOf('\n')));
        stderr = stderr.slice(stderr.indexOf('\n')+1);
      }
    });

    child.on('close', function(code) {
      if (stdout.length > 0) console.log(stdout);
      if (stderr.length > 0) console.log(stderr);
      resolve(code);
    });
  });
};

const get_files_in_directory = async(dir) => {
  return new Promise((resolve, reject) => {
    require('fs').readdir(dir, (err, files) => {
      resolve(files);
    });
  });
}

const get_selection = async(num) => {
  for(;;) {
    var key = await getch();
    var keyIndex = key[0] - 97; // - 'a'
    if (keyIndex >= 0 && keyIndex < num) return keyIndex;
    console.log(`Invalid selection "${key}". Try again?`);
  }
}

function fwToHumanReadable(fw) {
  return fw.replace('-', ' v').replace('.fs', '');
}

function sleep(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

(async () => {
  var ret = await run_get_stdout('openfpgaloader-0.11.0\\openFPGALoader.exe -c ft232 --detect');
  if (!ret.includes('idcode 0x100481b') || !ret.includes('GW1N(R)-9C')) {
    console.log('CRT Terminator FPGA was not detected. Double-check that USB cable is connected and that WinUSB driver is installed.')
    console.log('Press any key to quit.');
    await getch();
    process.exit(1);
  }
  console.log('CRT Terminator board detected.');

  console.log(`Choose which firmware to install:`);
  console.log(``);
  var fw = await get_files_in_directory('images');
  var i = 0;
  fw.forEach(f => {
    console.log(`  ${String.fromCharCode(97+i)}) ${fwToHumanReadable(f)}`); // 'a'+i
    ++i;
  });
  console.log(``);

  var i = await get_selection(fw.length);
  console.log(`Press Y to start flashing firmware ${fwToHumanReadable(fw[i])}, or any other key to abort.`);
  var key = await getch();
  key = key[0];
  if (!(key == 89 || key == 121)) {
    console.log('Aborted by user. Press any key to quit.');
    await getch();
    process.exit(1);
  }

  console.log(``);
  console.log(`Programming firmware. Do not disconnect USB-C cable until "Programming complete." is printed.`);
  await sleep(4000);
  var ret = await run_print_stdout(`openfpgaloader-0.11.0\\openFPGALoader.exe`, ['--cable', 'ft232', '--write-flash', `images\\${fw[i]}`]);
  if (ret == 0) {
    console.log(``);
    console.log(`Programming complete. It is now safe to disconnect the device.`);
    console.log(`Please remember to restore the +5V and GND jumpers back to their original place.`);
  }
  else console.log(`Firmware update failed!`);
  console.log('Press any key to quit.');
  await getch();
})().then(process.exit);
