"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.fabricate = fabricate;
exports.fabricateTwice = fabricateTwice;
exports.shutdown = shutdown;

var _log = require("./log.js");

var _child_process = require("child_process");

const script = `
  var vm = require('vm');
  var module = require('module');
  var stdin = Buffer.alloc(0);
  process.stdin.on('data', function (data) {
    stdin = Buffer.concat([ stdin, data ]);
    if (stdin.length >= 4) {
      var sizeOfSnap = stdin.readInt32LE(0);
      if (stdin.length >= 4 + sizeOfSnap + 4) {
        var sizeOfBody = stdin.readInt32LE(4 + sizeOfSnap);
        if (stdin.length >= 4 + sizeOfSnap + 4 + sizeOfBody) {
          var snap = stdin.toString('utf8', 4, 4 + sizeOfSnap);
          var body = Buffer.alloc(sizeOfBody);
          var startOfBody = 4 + sizeOfSnap + 4;
          stdin.copy(body, 0, startOfBody, startOfBody + sizeOfBody);
          stdin = Buffer.alloc(0);
          var code = module.wrap(body);
          var s = new vm.Script(code, {
            filename: snap,
            produceCachedData: true,
            sourceless: true
          });
          if (!s.cachedDataProduced) {
            console.error('Pkg: Cached data not produced.');
            process.exit(2);
          }
          var h = Buffer.alloc(4);
          var b = s.cachedData;
          h.writeInt32LE(b.length, 0);
          process.stdout.write(h);
          process.stdout.write(b);
        }
      }
    }
  });
  process.stdin.resume();
`;
const children = {};

function fabricate(bakes, fabricator, snap, body, cb) {
  bakes = bakes.filter(function (bake) {
    // list of bakes that don't influence the bytecode
    const bake2 = bake.replace(/_/g, '-');
    return !['--prof', '--v8-options', '--trace-opt', '--trace-deopt'].includes(bake2);
  });
  const cmd = fabricator.binaryPath;
  const key = JSON.stringify([cmd, bakes]);
  let child = children[key];

  if (!child) {
    const stderr = _log.log.debugMode ? process.stdout : 'ignore';
    child = children[key] = (0, _child_process.spawn)(cmd, bakes.concat('-e', script), {
      stdio: ['pipe', 'pipe', stderr],
      env: {
        PKG_EXECPATH: 'PKG_INVOKE_NODEJS'
      }
    });
  }

  function kill() {
    delete children[key];
    child.kill();
  }

  let stdout = Buffer.alloc(0);

  function onError(error) {
    removeListeners();
    kill();
    cb(new Error(`Failed to make bytecode ${fabricator.nodeRange}-${fabricator.arch} for file ${snap} error (${error.message})`));
  }

  function onClose(code) {
    removeListeners();
    kill();

    if (code !== 0) {
      return cb(new Error(`Failed to make bytecode ${fabricator.nodeRange}-${fabricator.arch} for file ${snap}`));
    } else {
      console.log(stdout.toString());
      return cb(new Error(`${cmd} closed unexpectedly`));
    }
  }

  function onData(data) {
    stdout = Buffer.concat([stdout, data]);

    if (stdout.length >= 4) {
      const sizeOfBlob = stdout.readInt32LE(0);

      if (stdout.length >= 4 + sizeOfBlob) {
        const blob = Buffer.alloc(sizeOfBlob);
        stdout.copy(blob, 0, 4, 4 + sizeOfBlob);
        removeListeners();
        return cb(undefined, blob);
      }
    }
  }

  child.on('error', onError);
  child.on('close', onClose);
  child.stdin.on('error', onError);
  child.stdout.on('error', onError);
  child.stdout.on('data', onData);

  function removeListeners() {
    child.removeListener('error', onError);
    child.removeListener('close', onClose);
    child.stdin.removeListener('error', onError);
    child.stdout.removeListener('error', onError);
    child.stdout.removeListener('data', onData);
  }

  const h = Buffer.alloc(4);
  let b = Buffer.from(snap);
  h.writeInt32LE(b.length, 0);
  child.stdin.write(h);
  child.stdin.write(b);
  b = body;
  h.writeInt32LE(b.length, 0);
  child.stdin.write(h);
  child.stdin.write(b);
}

function fabricateTwice(bakes, fabricator, snap, body, cb) {
  fabricate(bakes, fabricator, snap, body, (error, buffer) => {
    // node0 can not produce second time, even if first time produced fine,
    // probably because of 'filename' cache. also, there are wierd cases
    // when node4 can not compile as well, for example file 'lib/js-yaml/dumper.js'
    // of package js-yaml@3.9.0 does not get bytecode second time on node4-win-x64
    if (error) return fabricate(bakes, fabricator, snap, body, cb);
    cb(undefined, buffer);
  });
}

function shutdown() {
  for (const key in children) {
    const child = children[key];
    delete children[key];
    child.kill();
  }
}