'use strict';

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

var _regenerator;

function _load_regenerator() {
  return _regenerator = _interopRequireDefault(require('babel-runtime/regenerator'));
}

var _path;

function _load_path() {
  return _path = _interopRequireDefault(require('path'));
}

var _ramda;

function _load_ramda() {
  return _ramda = _interopRequireDefault(require('ramda'));
}

var _child_process;

function _load_child_process() {
  return _child_process = require('child_process');
}

var _deserializeError;

function _load_deserializeError() {
  return _deserializeError = _interopRequireDefault(require('deserialize-error'));
}

var _specsResults;

function _load_specsResults() {
  return _specsResults = require('../consumer/specs-results');
}

var _constants;

function _load_constants() {
  return _constants = require('../constants');
}

var _analytics;

function _load_analytics() {
  return _analytics = require('../analytics/analytics');
}

var _logger;

function _load_logger() {
  return _logger = _interopRequireDefault(require('../logger/logger'));
}

var _externalError;

function _load_externalError() {
  return _externalError = _interopRequireDefault(require('../error/external-error'));
}

var _externalBuildError;

function _load_externalBuildError() {
  return _externalBuildError = _interopRequireDefault(require('../consumer/component/exceptions/external-build-error'));
}

var _externalTestError;

function _load_externalTestError() {
  return _externalTestError = _interopRequireDefault(require('../consumer/component/exceptions/external-test-error'));
}

var _bitId;

function _load_bitId() {
  return _bitId = require('../bit-id');
}

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }

exports.default = function () {
  var _ref2 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee(_ref) {
    var ids = _ref.ids,
        forkLevel = _ref.forkLevel,
        _ref$includeUnmodifie = _ref.includeUnmodified,
        includeUnmodified = _ref$includeUnmodifie === undefined ? false : _ref$includeUnmodifie,
        verbose = _ref.verbose;
    var allRunnersP, allRunnersResults, finalResults;
    return (_regenerator || _load_regenerator()).default.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            if (!(!ids || (_ramda || _load_ramda()).default.isEmpty(ids))) {
              _context.next = 4;
              break;
            }

            (_analytics || _load_analytics()).Analytics.addBreadCrumb('specs-runner.run', 'running tests on one child process without ids');
            (_logger || _load_logger()).default.debug('specs-runner.run', 'running tests on one child process without ids');
            return _context.abrupt('return', runOnChildProcess({
              includeUnmodified,
              verbose
            }));

          case 4:
            if (!(forkLevel === (_constants || _load_constants()).TESTS_FORK_LEVEL.ONE)) {
              _context.next = 8;
              break;
            }

            (_analytics || _load_analytics()).Analytics.addBreadCrumb('specs-runner.run', 'running tests on one child process with ids');
            (_logger || _load_logger()).default.debug('specs-runner.run', 'running tests on one child process with ids');
            return _context.abrupt('return', runOnChildProcess({
              ids,
              includeUnmodified: false, // no meaning to pass this when there is specific ids
              verbose
            }));

          case 8:
            (_analytics || _load_analytics()).Analytics.addBreadCrumb('specs-runner.run', 'running tests on child process for each component');
            (_logger || _load_logger()).default.debug('specs-runner.run', 'running tests on child process for each component');
            allRunnersP = ids.map(function (id) {
              return runOnChildProcess({
                ids: [id],
                includeUnmodified: false, // no meaning to pass this when there is specific ids
                verbose
              });
            });
            _context.next = 13;
            return Promise.all(allRunnersP);

          case 13:
            allRunnersResults = _context.sent;

            if (!(!allRunnersResults || !allRunnersResults.length)) {
              _context.next = 16;
              break;
            }

            return _context.abrupt('return', undefined);

          case 16:
            finalResults = allRunnersResults.reduce(function (acc, curr) {
              if (!curr || !curr[0]) return acc;
              acc.push(curr[0]);
              return acc;
            }, []);
            return _context.abrupt('return', finalResults);

          case 18:
          case 'end':
            return _context.stop();
        }
      }
    }, _callee, this);
  }));

  function run(_x) {
    return _ref2.apply(this, arguments);
  }

  return run;
}();

function getDebugPort() {
  var debugPortArgName = '--debug-brk';
  try {
    var execArgv = process.execArgv.map(function (arg) {
      return arg.split('=');
    });
    var execArgvObj = (_ramda || _load_ramda()).default.fromPairs(execArgv);
    if (execArgvObj[debugPortArgName]) return parseInt(execArgvObj[debugPortArgName]);
  } catch (e) {
    return null;
  }

  return null;
}

function runOnChildProcess(_ref3) {
  var ids = _ref3.ids,
      includeUnmodified = _ref3.includeUnmodified,
      verbose = _ref3.verbose;

  return new Promise(function (resolve, reject) {
    var debugPort = getDebugPort();
    var openPort = debugPort ? debugPort + 1 : null;
    var baseEnv = {
      __verbose__: verbose,
      __includeUnmodified__: includeUnmodified
    };
    // Don't use ternary condition since if we put it as undefined
    // It will pass to the fork as "undefined" (string) instad of not passing it at all
    // __ids__: ids ? ids.join() : undefined,
    if (ids) {
      baseEnv.__ids__ = ids.join();
    }

    // Merge process.env from the main process
    var env = Object.assign({}, process.env, baseEnv);

    var child = (0, (_child_process || _load_child_process()).fork)((_path || _load_path()).default.join(__dirname, 'worker.js'), {
      execArgv: openPort ? [`--debug=${openPort.toString()}`] : [],
      silent: false,
      env
    });

    child.on('exit', function (code) {
      if (code !== 0) reject();
    });
    process.on('exit', function () {
      child.kill('SIGKILL');
    });

    child.on('message', function (results) {
      // if (type === 'error') return reject(payload);
      // if (payload.specPath) payload.specPath = testFile.relative;
      var deserializedResults = deserializeResults(results);
      if (!deserializedResults) return resolve(undefined);
      if (deserializedResults.type === 'error') {
        return reject(deserializedResults.error);
      }
      return resolve(deserializedResults.results);
    });

    child.on('error', function (e) {
      reject(e);
    });
  });
}

function deserializeResults(results) {
  if (!results) return undefined;
  if (results.type === 'error') {
    var deserializedError = (0, (_deserializeError || _load_deserializeError()).default)(results.error);
    // Special desrialization for external errors
    if (deserializedError.originalError) {
      var deserializedOriginalError = (0, (_deserializeError || _load_deserializeError()).default)(deserializedError.originalError);
      if (results.error.name === (_externalBuildError || _load_externalBuildError()).default.name) {
        deserializedError = new (_externalBuildError || _load_externalBuildError()).default(deserializedOriginalError, deserializedError.id);
      } else if (results.error.name === (_externalTestError || _load_externalTestError()).default.name) {
        deserializedError = new (_externalTestError || _load_externalTestError()).default(deserializedOriginalError, deserializedError.id);
      } else {
        deserializedError = new (_externalError || _load_externalError()).default(deserializedOriginalError);
      }
    }
    var finalResults = {
      type: 'error',
      error: deserializedError
    };
    return finalResults;
  }
  var deserializeFailure = function deserializeFailure(failure) {
    if (!failure) return undefined;
    var deserializedFailure = failure;
    if (failure.err) {
      try {
        deserializedFailure.err = (0, (_deserializeError || _load_deserializeError()).default)(failure.err);
      } catch (e) {
        (_logger || _load_logger()).default.debug(`fail parsing error ${deserializedFailure.err}`);
      }
    }
    return deserializedFailure;
  };

  var deserializeResult = function deserializeResult(result) {
    result.componentId = new (_bitId || _load_bitId()).BitId(result.componentId); // when BitId is received from a fork it loses its class and appears as an object
    if (!result.failures) return result;
    result.failures = result.failures.map(deserializeFailure);
    return result;
  };

  var deserializedResults = results.results.map(deserializeResult);
  return {
    type: 'results',
    results: deserializedResults
  };
}