'use strict';

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"); }); }; }

var _require = require('events');

const EventEmitter = _require.EventEmitter;

const os = require('os');
const Farm = require('worker-farm/lib/farm');
const promisify = require('./utils/promisify');
const logger = require('./Logger');

let shared = null;

class WorkerFarm extends Farm {
  constructor(options) {
    let opts = {
      maxConcurrentWorkers: getNumWorkers()
    };

    let workerPath = parseInt(process.versions.node, 10) < 8 ? require.resolve('../lib/worker') : require.resolve('../src/worker');

    super(opts, workerPath);

    this.localWorker = this.promisifyWorker(require('./worker'));
    this.remoteWorker = this.promisifyWorker(this.setup(['init', 'run']));

    this.started = false;
    this.warmWorkers = 0;
    this.init(options);
  }

  init(options) {
    this.localWorker.init(options);
    this.initRemoteWorkers(options);
  }

  promisifyWorker(worker) {
    let res = {};

    for (let key in worker) {
      res[key] = promisify(worker[key].bind(worker));
    }

    return res;
  }

  initRemoteWorkers(options) {
    var _this = this;

    return _asyncToGenerator(function* () {
      _this.started = false;
      _this.warmWorkers = 0;

      let promises = [];
      for (let i = 0; i < _this.options.maxConcurrentWorkers; i++) {
        promises.push(_this.remoteWorker.init(options));
      }

      yield Promise.all(promises);
      if (_this.options.maxConcurrentWorkers > 0) {
        _this.started = true;
      }
    })();
  }

  receive(data) {
    if (data.event) {
      this.emit(data.event, ...data.args);
    } else if (data.type === 'logger') {
      if (this.shouldUseRemoteWorkers()) {
        logger.handleMessage(data);
      }
    } else if (this.children[data.child]) {
      super.receive(data);
    }
  }

  shouldUseRemoteWorkers() {
    return this.started && this.warmWorkers >= this.activeChildren;
  }

  run(...args) {
    var _this2 = this;

    return _asyncToGenerator(function* () {
      // Child process workers are slow to start (~600ms).
      // While we're waiting, just run on the main thread.
      // This significantly speeds up startup time.
      if (_this2.shouldUseRemoteWorkers()) {
        return _this2.remoteWorker.run(...args, false);
      } else {
        // Workers have started, but are not warmed up yet.
        // Send the job to a remote worker in the background,
        // but use the result from the local worker - it will be faster.
        if (_this2.started) {
          _this2.remoteWorker.run(...args, true).then(function () {
            _this2.warmWorkers++;
          }, function () {
            // ignore error
          });
        }

        return _this2.localWorker.run(...args, false);
      }
    })();
  }

  end() {
    // Force kill all children
    this.ending = true;
    for (let child in this.children) {
      this.stopChild(child);
    }

    this.ending = false;
    shared = null;
  }

  static getShared(options) {
    if (!shared) {
      shared = new WorkerFarm(options);
    } else {
      shared.init(options);
    }

    return shared;
  }
}

for (let key in EventEmitter.prototype) {
  WorkerFarm.prototype[key] = EventEmitter.prototype[key];
}

function getNumWorkers() {
  if (process.env.PARCEL_WORKERS) {
    return parseInt(process.env.PARCEL_WORKERS, 10);
  }

  let cores;
  try {
    cores = require('physical-cpu-count');
  } catch (err) {
    cores = os.cpus().length;
  }
  return cores || 1;
}

module.exports = WorkerFarm;