// This is a babelified port of github.com/sindresorhus/p-queue/blob/v3.0.0/index.js

'use strict'; // Used to compute insertion index to keep queue sorted after insertion

function _typeof(obj) { if (typeof Symbol === “function” && typeof Symbol.iterator === “symbol”) { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === “function” && obj.constructor === Symbol && obj !== Symbol.prototype ? “symbol” : typeof obj; }; } return _typeof(obj); }

function _instanceof(left, right) { if (right != null && typeof Symbol !== “undefined” && right) { return right(left); } else { return left instanceof right; } }

function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError(“Cannot call a class as a function”); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (“value” in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

function lowerBound(array, value, comp) {

var first = 0;
var count = array.length;
while (count > 0) {
    var step = count / 2 | 0;
    var it = first + step;
    if (comp(array[it], value) <= 0) {
        first = ++it;
        count -= step + 1;
    } else {
        count = step;
    }
}
return first;

}

var PriorityQueue =

/*#__PURE__*/
function () {
    function PriorityQueue() {
        _classCallCheck(this, PriorityQueue);
        this._queue = [];
    }
    _createClass(PriorityQueue, [{
        key: "enqueue",
        value: function enqueue(run, options) {
            options = Object.assign({
                priority: 0
            }, options);
            var element = {
                priority: options.priority,
                run: run
            };
            if (this.size && this._queue[this.size - 1].priority >= options.priority) {
                this._queue.push(element);
                return;
            }
            var index = lowerBound(this._queue, element, function (a, b) {
                return b.priority - a.priority;
            });
            this._queue.splice(index, 0, element);
        }
    }, {
        key: "dequeue",
        value: function dequeue() {
            return this._queue.shift().run;
        }
    }, {
        key: "size",
        get: function get() {
            return this._queue.length;
        }
    }]);
    return PriorityQueue;
}();

var PQueue =

/*#__PURE__*/
function () {
    function PQueue(options) {
        _classCallCheck(this, PQueue);
        options = Object.assign({
            carryoverConcurrencyCount: false,
            intervalCap: Infinity,
            interval: 0,
            concurrency: Infinity,
            autoStart: true,
            queueClass: PriorityQueue
        }, options);
        if (!(typeof options.concurrency === 'number' && options.concurrency >= 1)) {
            throw new TypeError("Expected `concurrency` to be a number from 1 and up, got `".concat(options.concurrency, "` (").concat(_typeof(options.concurrency), ")"));
        }
        if (!(typeof options.intervalCap === 'number' && options.intervalCap >= 1)) {
            throw new TypeError("Expected `intervalCap` to be a number from 1 and up, got `".concat(options.intervalCap, "` (").concat(_typeof(options.intervalCap), ")"));
        }
        if (!(typeof options.interval === 'number' && Number.isFinite(options.interval) && options.interval >= 0)) {
            throw new TypeError("Expected `interval` to be a finite number >= 0, got `".concat(options.interval, "` (").concat(_typeof(options.interval), ")"));
        }
        this._carryoverConcurrencyCount = options.carryoverConcurrencyCount;
        this._isIntervalIgnored = options.intervalCap === Infinity || options.interval === 0;
        this._intervalCount = 0;
        this._intervalCap = options.intervalCap;
        this._interval = options.interval;
        this._intervalId = null;
        this._intervalEnd = 0;
        this._timeoutId = null;
        this.queue = new options.queueClass(); // eslint-disable-line new-cap
        this._queueClass = options.queueClass;
        this._pendingCount = 0;
        this._concurrency = options.concurrency;
        this._isPaused = options.autoStart === false;
        this._resolveEmpty = function () { };
        this._resolveIdle = function () { };
    }
    _createClass(PQueue, [{
        key: "_next",
        value: function _next() {
            this._pendingCount--;
            this._tryToStartAnother();
        }
    }, {
        key: "_resolvePromises",
        value: function _resolvePromises() {
            this._resolveEmpty();
            this._resolveEmpty = function () { };
            if (this._pendingCount === 0) {
                this._resolveIdle();
                this._resolveIdle = function () { };
            }
        }
    }, {
        key: "_onResumeInterval",
        value: function _onResumeInterval() {
            this._onInterval();
            this._initializeIntervalIfNeeded();
            this._timeoutId = null;
        }
    }, {
        key: "_intervalPaused",
        value: function _intervalPaused() {
            var _this = this;
            var now = Date.now();
            if (this._intervalId === null) {
                var delay = this._intervalEnd - now;
                if (delay < 0) {
                    // Act as the interval was done
                    // We don't need to resume it here,
                    // because it'll be resumed on line 160
                    this._intervalCount = this._carryoverConcurrencyCount ? this._pendingCount : 0;
                } else {
                    // Act as the interval is pending
                    if (this._timeoutId === null) {
                        this._timeoutId = setTimeout(function () {
                            return _this._onResumeInterval();
                        }, delay);
                    }
                    return true;
                }
            }
            return false;
        }
    }, {
        key: "_tryToStartAnother",
        value: function _tryToStartAnother() {
            if (this.queue.size === 0) {
                // We can clear the interval ("pause")
                // because we can redo it later ("resume")
                clearInterval(this._intervalId);
                this._intervalId = null;
                this._resolvePromises();
                return false;
            }
            if (!this._isPaused) {
                var canInitializeInterval = !this._intervalPaused();
                if (this._doesIntervalAllowAnother && this._doesConcurrentAllowAnother) {
                    this.queue.dequeue()();
                    if (canInitializeInterval) {
                        this._initializeIntervalIfNeeded();
                    }
                    return true;
                }
            }
            return false;
        }
    }, {
        key: "_initializeIntervalIfNeeded",
        value: function _initializeIntervalIfNeeded() {
            var _this2 = this;
            if (this._isIntervalIgnored || this._intervalId !== null) {
                return;
            }
            this._intervalId = setInterval(function () {
                return _this2._onInterval();
            }, this._interval);
            this._intervalEnd = Date.now() + this._interval;
        }
    }, {
        key: "_onInterval",
        value: function _onInterval() {
            if (this._intervalCount === 0 && this._pendingCount === 0) {
                clearInterval(this._intervalId);
                this._intervalId = null;
            }
            this._intervalCount = this._carryoverConcurrencyCount ? this._pendingCount : 0;
            while (this._tryToStartAnother()) { } // eslint-disable-line no-empty
        }
    }, {
        key: "add",
        value: function add(fn, options) {
            var _this3 = this;
            return new Promise(function (resolve, reject) {
                var run = function run() {
                    _this3._pendingCount++;
                    _this3._intervalCount++;
                    try {
                        Promise.resolve(fn()).then(function (val) {
                            resolve(val);
                            _this3._next();
                        }, function (err) {
                            reject(err);
                            _this3._next();
                        });
                    } catch (err) {
                        reject(err);
                        _this3._next();
                    }
                };
                _this3.queue.enqueue(run, options);
                _this3._tryToStartAnother();
            });
        }
    }, {
        key: "addAll",
        value: function addAll(fns, options) {
            var _this4 = this;
            return Promise.all(fns.map(function (fn) {
                return _this4.add(fn, options);
            }));
        }
    }, {
        key: "start",
        value: function start() {
            if (!this._isPaused) {
                return;
            }
            this._isPaused = false;
            while (this._tryToStartAnother()) { } // eslint-disable-line no-empty
        }
    }, {
        key: "pause",
        value: function pause() {
            this._isPaused = true;
        }
    }, {
        key: "clear",
        value: function clear() {
            this.queue = new this._queueClass(); // eslint-disable-line new-cap
        }
    }, {
        key: "onEmpty",
        value: function onEmpty() {
            var _this5 = this;
            // Instantly resolve if the queue is empty
            if (this.queue.size === 0) {
                return Promise.resolve();
            }
            return new Promise(function (resolve) {
                var existingResolve = _this5._resolveEmpty;
                _this5._resolveEmpty = function () {
                    existingResolve();
                    resolve();
                };
            });
        }
    }, {
        key: "onIdle",
        value: function onIdle() {
            var _this6 = this;
            // Instantly resolve if none pending and if nothing else is queued
            if (this._pendingCount === 0 && this.queue.size === 0) {
                return Promise.resolve();
            }
            return new Promise(function (resolve) {
                var existingResolve = _this6._resolveIdle;
                _this6._resolveIdle = function () {
                    existingResolve();
                    resolve();
                };
            });
        }
    }, {
        key: "_doesIntervalAllowAnother",
        get: function get() {
            return this._isIntervalIgnored || this._intervalCount < this._intervalCap;
        }
    }, {
        key: "_doesConcurrentAllowAnother",
        get: function get() {
            return this._pendingCount < this._concurrency;
        }
    }, {
        key: "size",
        get: function get() {
            return this.queue.size;
        }
    }, {
        key: "pending",
        get: function get() {
            return this._pendingCount;
        }
    }, {
        key: "isPaused",
        get: function get() {
            return this._isPaused;
        }
    }]);
    return PQueue;
}();