text: var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
      function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
      return new (P || (P = Promise))(function (resolve, reject) {
          function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
          function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
          function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
          step((generator = generator.apply(thisArg, _arguments || [])).next());
      });
  };
  import DebugLogger from "debug";
  import { multicast, Observable, Subject } from "observable-fns";
  import { allSettled } from "../ponyfills";
  import { defaultPoolSize } from "./implementation";
  import { PoolEventType } from "./pool-types";
  import { Thread } from "./thread";
  export { PoolEventType, Thread };
  let nextPoolID = 1;
  function createArray(size) {
      const array = [];
      for (let index = 0; index < size; index++) {
          array.push(index);
      }
      return array;
  }
  function delay(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
  }
  function flatMap(array, mapper) {
      return array.reduce((flattened, element) => [...flattened, ...mapper(element)], []);
  }
  function slugify(text) {
      return text.replace(/\W/g, " ").trim().replace(/\s+/g, "-");
  }
  function spawnWorkers(spawnWorker, count) {
      return createArray(count).map(() => ({
          init: spawnWorker(),
          runningTasks: []
      }));
  }
  class WorkerPool {
      constructor(spawnWorker, optionsOrSize) {
          this.eventSubject = new Subject();
          this.initErrors = [];
          this.isClosing = false;
          this.nextTaskID = 1;
          this.taskQueue = [];
          const options = typeof optionsOrSize === "number"
              ? { size: optionsOrSize }
              : optionsOrSize || {};
          const { size = defaultPoolSize } = options;
          this.debug = DebugLogger(`threads:pool:${slugify(options.name || String(nextPoolID++))}`);
          this.options = options;
          this.workers = spawnWorkers(spawnWorker, size);
          this.eventObservable = multicast(Observable.from(this.eventSubject));
          Promise.all(this.workers.map(worker => worker.init)).then(() => this.eventSubject.next({
              type: PoolEventType.initialized,
              size: this.workers.length
          }), error => {
              this.debug("Error while initializing pool worker:", error);
              this.eventSubject.error(error);
              this.initErrors.push(error);
          });
      }
      findIdlingWorker() {
          const { concurrency = 1 } = this.options;
          return this.workers.find(worker => worker.runningTasks.length < concurrency);
      }
      runPoolTask(worker, task) {
          return __awaiter(this, void 0, void 0, function* () {
              const workerID = this.workers.indexOf(worker) + 1;
              this.debug(`Running task #${task.id} on worker #${workerID}...`);
              this.eventSubject.next({
                  type: PoolEventType.taskStart,
                  taskID: task.id,
                  workerID
              });
              try {
                  const returnValue = yield task.run(yield worker.init);
                  this.debug(`Task #${task.id} completed successfully`);
                  this.eventSubject.next({
                      type: PoolEventType.taskCompleted,
                      returnValue,
                      taskID: task.id,
                      workerID
                  });
              }
              catch (error) {
                  this.debug(`Task #${task.id} failed`);
                  this.eventSubject.next({
                      type: PoolEventType.taskFailed,
                      taskID: task.id,
                      error,
                      workerID
                  });
              }
          });
      }
      run(worker, task) {
          return __awaiter(this, void 0, void 0, function* () {
              const runPromise = (() => __awaiter(this, void 0, void 0, function* () {
                  const removeTaskFromWorkersRunningTasks = () => {
                      worker.runningTasks = worker.runningTasks.filter(someRunPromise => someRunPromise !== runPromise);
                  };
                  // Defer task execution by one tick to give handlers time to subscribe
                  yield delay(0);
                  try {
                      yield this.runPoolTask(worker, task);
                  }
                  finally {
                      removeTaskFromWorkersRunningTasks();
                      if (!this.isClosing) {
                          this.scheduleWork();
                      }
                  }
              }))();
              worker.runningTasks.push(runPromise);
          });
      }
      scheduleWork() {
          this.debug(`Attempt de-queueing a task in order to run it...`);
          const availableWorker = this.findIdlingWorker();
          if (!availableWorker)
              return;
          const nextTask = this.taskQueue.shift();
          if (!nextTask) {
              this.debug(`Task queue is empty`);
              this.eventSubject.next({ type: PoolEventType.taskQueueDrained });
              return;
          }
          this.run(availableWorker, nextTask);
      }
      taskCompletion(taskID) {
          return new Promise((resolve, reject) => {
              const eventSubscription = this.events().subscribe(event => {
                  if (event.type === PoolEventType.taskCompleted && event.taskID === taskID) {
                      eventSubscription.unsubscribe();
                      resolve(event.returnValue);
                  }
                  else if (event.type === PoolEventType.taskFailed && event.taskID === taskID) {
                      eventSubscription.unsubscribe();
                      reject(event.error);
                  }
                  else if (event.type === PoolEventType.terminated) {
                      eventSubscription.unsubscribe();
                      reject(Error("Pool has been terminated before task was run."));
                  }
              });
          });
      }
      settled(allowResolvingImmediately = false) {
          return __awaiter(this, void 0, void 0, function* () {
              const getCurrentlyRunningTasks = () => flatMap(this.workers, worker => worker.runningTasks);
              const taskFailures = [];
              const failureSubscription = this.eventObservable.subscribe(event => {
                  if (event.type === PoolEventType.taskFailed) {
                      taskFailures.push(event.error);
                  }
              });
              if (this.initErrors.length > 0) {
                  return Promise.reject(this.initErrors[0]);
              }
              if (allowResolvingImmediately && this.taskQueue.length === 0) {
                  yield allSettled(getCurrentlyRunningTasks());
                  return taskFailures;
              }
              yield new Promise((resolve, reject) => {
                  const subscription = this.eventObservable.subscribe({
                      next(event) {
                          if (event.type === PoolEventType.taskQueueDrained) {
                              subscription.unsubscribe();
                              resolve();
                          }
                      },
                      error: reject // make a pool-wide error reject the completed() result promise
                  });
              });
              yield allSettled(getCurrentlyRunningTasks());
              failureSubscription.unsubscribe();
              return taskFailures;
          });
      }
      completed(allowResolvingImmediately = false) {
          return __awaiter(this, void 0, void 0, function* () {
              const settlementPromise = this.settled(allowResolvingImmediately);
              const earlyExitPromise = new Promise((resolve, reject) => {
                  const subscription = this.eventObservable.subscribe({
                      next(event) {
                          if (event.type === PoolEventType.taskQueueDrained) {
                              subscription.unsubscribe();
                              resolve(settlementPromise);
                          }
                          else if (event.type === PoolEventType.taskFailed) {
                              subscription.unsubscribe();
                              reject(event.error);
                          }
                      },
                      error: reject // make a pool-wide error reject the completed() result promise
                  });
              });
              const errors = yield Promise.race([
                  settlementPromise,
                  earlyExitPromise
              ]);
              if (errors.length > 0) {
                  throw errors[0];
              }
          });
      }
      events() {
          return this.eventObservable;
      }
      queue(taskFunction) {
          const { maxQueuedJobs = Infinity } = this.options;
          if (this.isClosing) {
              throw Error(`Cannot schedule pool tasks after terminate() has been called.`);
          }
          if (this.initErrors.length > 0) {
              throw this.initErrors[0];
          }
          const taskID = this.nextTaskID++;
          const taskCompletion = this.taskCompletion(taskID);
          taskCompletion.catch((error) => {
              // Prevent unhandled rejections here as we assume the user will use
              // `pool.completed()`, `pool.settled()` or `task.catch()` to handle errors
              this.debug(`Task #${taskID} errored:`, error);
          });
          const task = {
              id: taskID,
              run: taskFunction,
              cancel: () => {
                  if (this.taskQueue.indexOf(task) === -1)
                      return;
                  this.taskQueue = this.taskQueue.filter(someTask => someTask !== task);
                  this.eventSubject.next({
                      type: PoolEventType.taskCanceled,
                      taskID: task.id
                  });
              },
              then: taskCompletion.then.bind(taskCompletion)
          };
          if (this.taskQueue.length >= maxQueuedJobs) {
              throw Error("Maximum number of pool tasks queued. Refusing to queue another one.\n" +
                  "This usually happens for one of two reasons: We are either at peak " +
                  "workload right now or some tasks just won't finish, thus blocking the pool.");
          }
          this.debug(`Queueing task #${task.id}...`);
          this.taskQueue.push(task);
          this.eventSubject.next({
              type: PoolEventType.taskQueued,
              taskID: task.id
          });
          this.scheduleWork();
          return task;
      }
      terminate(force) {
          return __awaiter(this, void 0, void 0, function* () {
              this.isClosing = true;
              if (!force) {
                  yield this.completed(true);
              }
              this.eventSubject.next({
                  type: PoolEventType.terminated,
                  remainingQueue: [...this.taskQueue]
              });
              this.eventSubject.complete();
              yield Promise.all(this.workers.map((worker) => __awaiter(this, void 0, void 0, function* () { return Thread.terminate(yield worker.init); })));
          });
      }
  }
  WorkerPool.EventType = PoolEventType;
  /**
   * Thread pool constructor. Creates a new pool and spawns its worker threads.
   */
  function PoolConstructor(spawnWorker, optionsOrSize) {
      // The function exists only so we don't need to use `new` to create a pool (we still can, though).
      // If the Pool is a class or not is an implementation detail that should not concern the user.
      return new WorkerPool(spawnWorker, optionsOrSize);
  }
  PoolConstructor.EventType = PoolEventType;
  /**
   * Thread pool constructor. Creates a new pool and spawns its worker threads.
   */
  export const Pool = PoolConstructor;
  