"use strict";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.Batch = void 0;
// In browser, during webpack or browserify bundling, this module will be replaced by 'events'
// https://github.com/Gozala/events
const events_1 = require("events");
/**
 * States for Batch.
 */
var BatchStates;
(function (BatchStates) {
    BatchStates[BatchStates["Good"] = 0] = "Good";
    BatchStates[BatchStates["Error"] = 1] = "Error";
})(BatchStates || (BatchStates = {}));
/**
 * Batch provides basic parallel execution with concurrency limits.
 * Will stop execute left operations when one of the executed operation throws an error.
 * But Batch cannot cancel ongoing operations, you need to cancel them by yourself.
 */
class Batch {
    /**
     * Concurrency. Must be lager than 0.
     */
    concurrency;
    /**
     * Number of active operations under execution.
     */
    actives = 0;
    /**
     * Number of completed operations under execution.
     */
    completed = 0;
    /**
     * Offset of next operation to be executed.
     */
    offset = 0;
    /**
     * Operation array to be executed.
     */
    operations = [];
    /**
     * States of Batch. When an error happens, state will turn into error.
     * Batch will stop execute left operations.
     */
    state = BatchStates.Good;
    /**
     * A private emitter used to pass events inside this class.
     */
    emitter;
    /**
     * Creates an instance of Batch.
     * @param concurrency -
     */
    constructor(concurrency = 5) {
        if (concurrency < 1) {
            throw new RangeError("concurrency must be larger than 0");
        }
        this.concurrency = concurrency;
        this.emitter = new events_1.EventEmitter();
    }
    /**
     * Add a operation into queue.
     *
     * @param operation -
     */
    addOperation(operation) {
        this.operations.push(async () => {
            try {
                this.actives++;
                await operation();
                this.actives--;
                this.completed++;
                this.parallelExecute();
            }
            catch (error) {
                this.emitter.emit("error", error);
            }
        });
    }
    /**
     * Start execute operations in the queue.
     *
     */
    async do() {
        if (this.operations.length === 0) {
            return Promise.resolve();
        }
        this.parallelExecute();
        return new Promise((resolve, reject) => {
            this.emitter.on("finish", resolve);
            this.emitter.on("error", (error) => {
                this.state = BatchStates.Error;
                reject(error);
            });
        });
    }
    /**
     * Get next operation to be executed. Return null when reaching ends.
     *
     */
    nextOperation() {
        if (this.offset < this.operations.length) {
            return this.operations[this.offset++];
        }
        return null;
    }
    /**
     * Start execute operations. One one the most important difference between
     * this method with do() is that do() wraps as an sync method.
     *
     */
    parallelExecute() {
        if (this.state === BatchStates.Error) {
            return;
        }
        if (this.completed >= this.operations.length) {
            this.emitter.emit("finish");
            return;
        }
        while (this.actives < this.concurrency) {
            const operation = this.nextOperation();
            if (operation) {
                operation();
            }
            else {
                return;
            }
        }
    }
}
exports.Batch = Batch;
//# sourceMappingURL=Batch.js.map