"use strict";
/*
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Queue = void 0;
const bluebird_1 = __importDefault(require("bluebird"));
const promiseutil = __importStar(require("../promiseutil"));
class Queue {
    /**
     * Construct a new Queue which will process items FIFO.
     * @param {Function} processFn The function to invoke when the item being processed
     * is in its critical section. Only 1 item at any one time will be calling this function.
     * The function should return a Promise which is resolved/rejected when the next item
     * can be taken from the queue.
     * @param {integer} intervalMs Optional. If provided and > 0, this queue will be serviced
     * at an interval of intervalMs. Otherwise, items will be processed as soon as they become
     * the first item in the queue to be processed.
     */
    constructor(processFn, intervalMs) {
        this.processFn = processFn;
        this.intervalMs = intervalMs;
        this.queue = [];
        this.processing = null;
        this.onceFreeDefers = [];
        if (intervalMs !== undefined && !(Number.isInteger(intervalMs) && intervalMs >= 0)) {
            throw Error('intervalMs must be a positive integer');
        }
        // XXX: Coroutines have subtly different behaviour to async/await functions
        // and I've not managed to track down precisely why. For the sake of keeping the
        // QueuePool tests happy, we will continue to use coroutine functions for now.
        this.consume = bluebird_1.default.coroutine(this.coConsume).bind(this);
        if (intervalMs) {
            // Start consuming
            this.consume();
        }
    }
    /**
     * Return the length of the queue, including the currently processed item.
     * @return {Number} The length of the queue.
     */
    size() {
        return this.queue.length + (this.processing ? 1 : 0);
    }
    /**
     * Return a promise which is resolved when this queue is free (0 items in queue).
     * @return {Promise<Queue>} Resolves to the Queue itself.
     */
    onceFree() {
        if (this.size() === 0) {
            return Promise.resolve();
        }
        const defer = promiseutil.defer();
        this.onceFreeDefers.push(defer);
        return defer.promise;
    }
    fireOnceFree() {
        this.onceFreeDefers.forEach((d) => {
            d.resolve(this);
        });
        this.onceFreeDefers = [];
    }
    /**
     * Queue up a request for the critical section function.
     * @param {string} id An ID to associate with this request. If there is already a
     * request with this ID, the promise for that request will be returned.
     * @param {*} thing The item to enqueue. It will be passed verbatim to the critical
     * section function passed in the constructor.
     * @return {Promise} A promise which will be resolved/rejected when the queued item
     * has been processed.
     */
    enqueue(id, thing) {
        for (let i = 0; i < this.queue.length; i++) {
            if (this.queue[i].id === id) {
                return this.queue[i].defer.promise;
            }
        }
        const defer = promiseutil.defer();
        this.queue.push({
            id: id,
            item: thing,
            defer: defer
        });
        if (!this.intervalMs) {
            // always process stuff asyncly, never syncly.
            process.nextTick(() => {
                this.consume();
            });
        }
        return defer.promise;
    }
    retry() {
        setTimeout(this.consume.bind(this), this.intervalMs);
    }
    *coConsume() {
        if (this.processing) {
            return;
        }
        this.processing = this.queue.shift();
        if (!this.processing) {
            if (this.intervalMs) {
                this.retry();
            }
            this.fireOnceFree();
            return;
        }
        try {
            const thing = this.processFn(this.processing.item);
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
            // @ts-ignore
            const result = yield thing;
            this.processing.defer.resolve(result);
        }
        catch (err) {
            this.processing.defer.reject(err);
        }
        finally {
            this.processing = null;
            if (this.intervalMs) {
                this.retry();
            }
        }
        if (!this.intervalMs) {
            this.consume();
        }
    }
    killAll() {
        for (let i = 0; i < this.queue.length; i++) {
            this.queue[i].defer.reject(new Error('Queue killed'));
        }
    }
}
exports.Queue = Queue;
//# sourceMappingURL=Queue.js.map