"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

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

var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));

var _fsExtra = require("fs-extra");

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

var _pkgFetch = require("pkg-fetch");

var _assert = _interopRequireDefault(require("assert"));

var _help = _interopRequireDefault(require("./help"));

var _common = require("../prelude/common.js");

var _minimist = _interopRequireDefault(require("minimist"));

var _packer = _interopRequireDefault(require("./packer.js"));

var _path = _interopRequireDefault(require("path"));

var _chmod = require("./chmod.js");

var _producer = _interopRequireDefault(require("./producer.js"));

var _refiner = _interopRequireDefault(require("./refiner.js"));

var _fabricator = require("./fabricator.js");

var _package = require("../package.json");

var _walker = _interopRequireDefault(require("./walker.js"));

/* eslint-disable require-atomic-updates */
function isConfiguration(file) {
  return (0, _common.isPackageJson)(file) || file.endsWith('.config.json');
} // http://www.openwall.com/lists/musl/2012/12/08/4


const {
  hostArch,
  hostPlatform,
  isValidNodeRange,
  knownArchs,
  knownPlatforms,
  toFancyArch,
  toFancyPlatform
} = _pkgFetch.system;
const hostNodeRange = 'node' + process.version.match(/^v(\d+)/)[1];

function parseTargets(items) {
  // [ 'node6-macos-x64', 'node6-linux-x64' ]
  const targets = [];

  for (const item of items) {
    const target = {
      nodeRange: hostNodeRange,
      platform: hostPlatform,
      arch: hostArch
    };

    if (item !== 'host') {
      for (const token of item.split('-')) {
        if (!token) continue;

        if (isValidNodeRange(token)) {
          target.nodeRange = token;
          continue;
        }

        const p = toFancyPlatform(token);

        if (knownPlatforms.indexOf(p) >= 0) {
          target.platform = p;
          continue;
        }

        const a = toFancyArch(token);

        if (knownArchs.indexOf(a) >= 0) {
          target.arch = a;
          continue;
        }

        throw (0, _log.wasReported)(`Unknown token '${token}' in '${item}'`);
      }
    }

    targets.push(target);
  }

  return targets;
}

function stringifyTarget(target) {
  const {
    nodeRange,
    platform,
    arch
  } = target;
  return `${nodeRange}-${platform}-${arch}`;
}

function differentParts(targets) {
  const nodeRanges = {};
  const platforms = {};
  const archs = {};

  for (const target of targets) {
    nodeRanges[target.nodeRange] = true;
    platforms[target.platform] = true;
    archs[target.arch] = true;
  }

  const result = {};

  if (Object.keys(nodeRanges).length > 1) {
    result.nodeRange = true;
  }

  if (Object.keys(platforms).length > 1) {
    result.platform = true;
  }

  if (Object.keys(archs).length > 1) {
    result.arch = true;
  }

  return result;
}

function stringifyTargetForOutput(output, target, different) {
  const a = [output];
  if (different.nodeRange) a.push(target.nodeRange);
  if (different.platform) a.push(target.platform);
  if (different.arch) a.push(target.arch);
  return a.join('-');
}

function fabricatorForTarget(target) {
  const {
    nodeRange,
    arch
  } = target;
  return {
    nodeRange,
    platform: hostPlatform,
    arch
  };
}

const dryRunResults = {};

function needWithDryRun(_x) {
  return _needWithDryRun.apply(this, arguments);
}

function _needWithDryRun() {
  _needWithDryRun = (0, _asyncToGenerator2.default)(function* (target) {
    const target2 = Object.assign({
      dryRun: true
    }, target);
    const result = yield (0, _pkgFetch.need)(target2);
    (0, _assert.default)(['exists', 'fetched', 'built'].indexOf(result) >= 0);
    dryRunResults[result] = true;
  });
  return _needWithDryRun.apply(this, arguments);
}

const targetsCache = {};

function needViaCache(_x2) {
  return _needViaCache.apply(this, arguments);
}

function _needViaCache() {
  _needViaCache = (0, _asyncToGenerator2.default)(function* (target) {
    const s = stringifyTarget(target);
    let c = targetsCache[s];
    if (c) return c;
    c = yield (0, _pkgFetch.need)(target);
    targetsCache[s] = c;
    return c;
  });
  return _needViaCache.apply(this, arguments);
}

function exec(_x3) {
  return _exec.apply(this, arguments);
}

function _exec() {
  _exec = (0, _asyncToGenerator2.default)(function* (argv2) {
    // eslint-disable-line complexity
    const argv = (0, _minimist.default)(argv2, {
      boolean: ['b', 'build', 'bytecode', 'd', 'debug', 'h', 'help', 'public', 'v', 'version'],
      string: ['_', 'c', 'config', 'o', 'options', 'output', 'outdir', 'out-dir', 'out-path', 'public-packages', 't', 'target', 'targets'],
      default: {
        bytecode: true
      }
    });

    if (argv.h || argv.help) {
      (0, _help.default)();
      return;
    } // version


    if (argv.v || argv.version) {
      console.log(_package.version);
      return;
    }

    _log.log.info(`pkg@${_package.version}`); // debug


    _log.log.debugMode = argv.d || argv.debug; // forceBuild

    const forceBuild = argv.b || argv.build; // _

    if (!argv._.length) {
      throw (0, _log.wasReported)('Entry file/directory is expected', ['Pass --help to see usage information']);
    }

    if (argv._.length > 1) {
      throw (0, _log.wasReported)('Not more than one entry file/directory is expected');
    } // input


    let input = _path.default.resolve(argv._[0]);

    if (!(yield (0, _fsExtra.exists)(input))) {
      throw (0, _log.wasReported)('Input file does not exist', [input]);
    }

    if ((yield (0, _fsExtra.stat)(input)).isDirectory()) {
      input = _path.default.join(input, 'package.json');

      if (!(yield (0, _fsExtra.exists)(input))) {
        throw (0, _log.wasReported)('Input file does not exist', [input]);
      }
    } // inputJson


    let inputJson, inputJsonName;

    if (isConfiguration(input)) {
      inputJson = JSON.parse((yield (0, _fsExtra.readFile)(input)));
      inputJsonName = inputJson.name;

      if (inputJsonName) {
        inputJsonName = inputJsonName.split('/').pop(); // @org/foo
      }
    } // inputBin


    let inputBin;

    if (inputJson) {
      let bin = inputJson.bin;

      if (bin) {
        if (typeof bin === 'object') {
          if (bin[inputJsonName]) {
            bin = bin[inputJsonName];
          } else {
            bin = bin[Object.keys(bin)[0]]; // TODO multiple inputs to pkg them all?
          }
        }

        inputBin = _path.default.resolve(_path.default.dirname(input), bin);

        if (!(yield (0, _fsExtra.exists)(inputBin))) {
          throw (0, _log.wasReported)('Bin file does not exist (taken from package.json ' + '\'bin\' property)', [inputBin]);
        }
      }
    }

    if (inputJson && !inputBin) {
      throw (0, _log.wasReported)('Property \'bin\' does not exist in', [input]);
    } // inputFin


    const inputFin = inputBin || input; // config

    let config = argv.c || argv.config;

    if (inputJson && config) {
      throw (0, _log.wasReported)('Specify either \'package.json\' or config. Not both');
    } // configJson


    let configJson;

    if (config) {
      config = _path.default.resolve(config);

      if (!(yield (0, _fsExtra.exists)(config))) {
        throw (0, _log.wasReported)('Config file does not exist', [config]);
      }

      configJson = require(config); // may be either json or js

      if (!configJson.name && !configJson.files && !configJson.dependencies && !configJson.pkg) {
        // package.json not detected
        configJson = {
          pkg: configJson
        };
      }
    } // output, outputPath


    let output = argv.o || argv.output;
    const outputPath = argv['out-path'] || argv.outdir || argv['out-dir'];
    let autoOutput = false;

    if (output && outputPath) {
      throw (0, _log.wasReported)('Specify either \'output\' or \'out-path\'. Not both');
    }

    if (!output) {
      let name;

      if (inputJson) {
        name = inputJsonName;

        if (!name) {
          throw (0, _log.wasReported)('Property \'name\' does not exist in', [argv._[0]]);
        }
      } else if (configJson) {
        name = configJson.name;
      }

      if (!name) {
        name = _path.default.basename(inputFin);
      }

      autoOutput = true;

      const ext = _path.default.extname(name);

      output = name.slice(0, -ext.length || undefined);
      output = _path.default.resolve(outputPath || '', output);
    } else {
      output = _path.default.resolve(output);
    } // targets


    const sTargets = argv.t || argv.target || argv.targets || '';

    if (typeof sTargets !== 'string') {
      throw (0, _log.wasReported)(`Something is wrong near ${JSON.stringify(sTargets)}`);
    }

    let targets = parseTargets(sTargets.split(',').filter(t => t));

    if (!targets.length) {
      let jsonTargets;

      if (inputJson && inputJson.pkg) {
        jsonTargets = inputJson.pkg.targets;
      } else if (configJson && configJson.pkg) {
        jsonTargets = configJson.pkg.targets;
      }

      if (jsonTargets) {
        targets = parseTargets(jsonTargets);
      }
    }

    if (!targets.length) {
      if (!autoOutput) {
        targets = parseTargets(['host']);
        (0, _assert.default)(targets.length === 1);
      } else {
        targets = parseTargets(['linux', 'macos', 'win']);
      }

      _log.log.info('Targets not specified. Assuming:', `${targets.map(stringifyTarget).join(', ')}`);
    } // differentParts


    const different = differentParts(targets); // targets[].output

    for (const target of targets) {
      let file;

      if (targets.length === 1) {
        file = output;
      } else {
        file = stringifyTargetForOutput(output, target, different);
      }

      if (target.platform === 'win' && _path.default.extname(file) !== '.exe') file += '.exe';
      target.output = file;
    } // bakes


    const bakes = (argv.options || '').split(',').filter(bake => bake).map(bake => '--' + bake); // check if input is going
    // to be overwritten by output

    for (const target of targets) {
      if (target.output === inputFin) {
        if (autoOutput) {
          target.output += '-' + target.platform;
        } else {
          throw (0, _log.wasReported)('Refusing to overwrite input file', [inputFin]);
        }
      }
    } // fetch targets


    const {
      bytecode
    } = argv;

    for (const target of targets) {
      target.forceBuild = forceBuild;
      yield needWithDryRun(target);
      const f = target.fabricator = fabricatorForTarget(target);
      f.forceBuild = forceBuild;

      if (bytecode) {
        yield needWithDryRun(f);
      }
    }

    if (dryRunResults.fetched && !dryRunResults.built) {
      _log.log.info('Fetching base Node.js binaries to PKG_CACHE_PATH');
    }

    for (const target of targets) {
      target.binaryPath = yield needViaCache(target);
      const f = target.fabricator;

      if (bytecode) {
        f.binaryPath = yield needViaCache(f);

        if (f.platform !== 'win') {
          yield (0, _chmod.plusx)(f.binaryPath);
        }
      }
    } // marker


    let marker;

    if (configJson) {
      marker = {
        config: configJson,
        base: _path.default.dirname(config),
        configPath: config
      };
    } else {
      marker = {
        config: inputJson || {},
        // not `inputBin` because only `input`
        base: _path.default.dirname(input),
        // is the place for `inputJson`
        configPath: input
      };
    }

    marker.toplevel = true; // public

    const params = {};

    if (argv.public) {
      params.publicToplevel = true;
    }

    if (argv['public-packages']) {
      params.publicPackages = argv['public-packages'].split(',');

      if (params.publicPackages.indexOf('*') !== -1) {
        params.publicPackages = ['*'];
      }
    } // records


    let records;
    let entrypoint = inputFin;
    const addition = isConfiguration(input) ? input : undefined;
    const walkResult = yield (0, _walker.default)(marker, entrypoint, addition, params);
    entrypoint = walkResult.entrypoint;
    records = walkResult.records;
    const refineResult = (0, _refiner.default)(records, entrypoint);
    entrypoint = refineResult.entrypoint;
    records = refineResult.records;
    const backpack = (0, _packer.default)({
      records,
      entrypoint,
      bytecode
    });

    _log.log.debug('Targets:', JSON.stringify(targets, null, 2));

    for (const target of targets) {
      if (yield (0, _fsExtra.exists)(target.output)) {
        if ((yield (0, _fsExtra.stat)(target.output)).isFile()) {
          yield (0, _fsExtra.remove)(target.output);
        } else {
          throw (0, _log.wasReported)('Refusing to overwrite non-file output', [target.output]);
        }
      } else {
        yield (0, _fsExtra.mkdirp)(_path.default.dirname(target.output));
      }

      const slash = target.platform === 'win' ? '\\' : '/';
      yield (0, _producer.default)({
        backpack,
        bakes,
        slash,
        target
      });

      if (target.platform !== 'win') {
        yield (0, _chmod.plusx)(target.output);
      }
    }

    (0, _fabricator.shutdown)();
  });
  return _exec.apply(this, arguments);
}