"use strict";
/* --------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License. See License.txt in the project root for license information.
 * ------------------------------------------------------------------------------------------ */
Object.defineProperty(exports, "__esModule", { value: true });
exports.forEach = exports.mapAsync = exports.map = exports.clearTestMode = exports.setTestMode = exports.Semaphore = exports.Delayer = void 0;
const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol");
class Delayer {
    constructor(defaultDelay) {
        this.defaultDelay = defaultDelay;
        this.timeout = undefined;
        this.completionPromise = undefined;
        this.onSuccess = undefined;
        this.task = undefined;
    }
    trigger(task, delay = this.defaultDelay) {
        this.task = task;
        if (delay >= 0) {
            this.cancelTimeout();
        }
        if (!this.completionPromise) {
            this.completionPromise = new Promise((resolve) => {
                this.onSuccess = resolve;
            }).then(() => {
                this.completionPromise = undefined;
                this.onSuccess = undefined;
                var result = this.task();
                this.task = undefined;
                return result;
            });
        }
        if (delay >= 0 || this.timeout === void 0) {
            this.timeout = (0, vscode_languageserver_protocol_1.RAL)().timer.setTimeout(() => {
                this.timeout = undefined;
                this.onSuccess(undefined);
            }, delay >= 0 ? delay : this.defaultDelay);
        }
        return this.completionPromise;
    }
    forceDelivery() {
        if (!this.completionPromise) {
            return undefined;
        }
        this.cancelTimeout();
        let result = this.task();
        this.completionPromise = undefined;
        this.onSuccess = undefined;
        this.task = undefined;
        return result;
    }
    isTriggered() {
        return this.timeout !== undefined;
    }
    cancel() {
        this.cancelTimeout();
        this.completionPromise = undefined;
    }
    cancelTimeout() {
        if (this.timeout !== undefined) {
            this.timeout.dispose();
            this.timeout = undefined;
        }
    }
}
exports.Delayer = Delayer;
class Semaphore {
    constructor(capacity = 1) {
        if (capacity <= 0) {
            throw new Error('Capacity must be greater than 0');
        }
        this._capacity = capacity;
        this._active = 0;
        this._waiting = [];
    }
    lock(thunk) {
        return new Promise((resolve, reject) => {
            this._waiting.push({ thunk, resolve, reject });
            this.runNext();
        });
    }
    get active() {
        return this._active;
    }
    runNext() {
        if (this._waiting.length === 0 || this._active === this._capacity) {
            return;
        }
        (0, vscode_languageserver_protocol_1.RAL)().timer.setImmediate(() => this.doRunNext());
    }
    doRunNext() {
        if (this._waiting.length === 0 || this._active === this._capacity) {
            return;
        }
        const next = this._waiting.shift();
        this._active++;
        if (this._active > this._capacity) {
            throw new Error(`To many thunks active`);
        }
        try {
            const result = next.thunk();
            if (result instanceof Promise) {
                result.then((value) => {
                    this._active--;
                    next.resolve(value);
                    this.runNext();
                }, (err) => {
                    this._active--;
                    next.reject(err);
                    this.runNext();
                });
            }
            else {
                this._active--;
                next.resolve(result);
                this.runNext();
            }
        }
        catch (err) {
            this._active--;
            next.reject(err);
            this.runNext();
        }
    }
}
exports.Semaphore = Semaphore;
let $test = false;
function setTestMode() {
    $test = true;
}
exports.setTestMode = setTestMode;
function clearTestMode() {
    $test = false;
}
exports.clearTestMode = clearTestMode;
const defaultYieldTimeout = 15 /*ms*/;
class Timer {
    constructor(yieldAfter = defaultYieldTimeout) {
        this.yieldAfter = $test === true ? Math.max(yieldAfter, 2) : Math.max(yieldAfter, defaultYieldTimeout);
        this.startTime = Date.now();
        this.counter = 0;
        this.total = 0;
        // start with a counter interval of 1.
        this.counterInterval = 1;
    }
    start() {
        this.counter = 0;
        this.total = 0;
        this.counterInterval = 1;
        this.startTime = Date.now();
    }
    shouldYield() {
        if (++this.counter >= this.counterInterval) {
            const timeTaken = Date.now() - this.startTime;
            const timeLeft = Math.max(0, this.yieldAfter - timeTaken);
            this.total += this.counter;
            this.counter = 0;
            if (timeTaken >= this.yieldAfter || timeLeft <= 1) {
                // Yield also if time left <= 1 since we compute the counter
                // for max < 2 ms.
                // Start with interval 1 again. We could do some calculation
                // with using 80% of the last counter however other things (GC)
                // affect the timing heavily since we have small timings (1 - 15ms).
                this.counterInterval = 1;
                this.total = 0;
                return true;
            }
            else {
                // Only increase the counter until we have spent <= 2 ms. Increasing
                // the counter further is very fragile since timing is influenced
                // by other things and can increase the counter too much. This will result
                // that we yield in average after [14 - 16]ms.
                switch (timeTaken) {
                    case 0:
                    case 1:
                        this.counterInterval = this.total * 2;
                        break;
                }
            }
        }
        return false;
    }
}
async function map(items, func, token, options) {
    if (items.length === 0) {
        return [];
    }
    const result = new Array(items.length);
    const timer = new Timer(options?.yieldAfter);
    function convertBatch(start) {
        timer.start();
        for (let i = start; i < items.length; i++) {
            result[i] = func(items[i]);
            if (timer.shouldYield()) {
                options?.yieldCallback && options.yieldCallback();
                return i + 1;
            }
        }
        return -1;
    }
    // Convert the first batch sync on the same frame.
    let index = convertBatch(0);
    while (index !== -1) {
        if (token !== undefined && token.isCancellationRequested) {
            break;
        }
        index = await new Promise((resolve) => {
            (0, vscode_languageserver_protocol_1.RAL)().timer.setImmediate(() => {
                resolve(convertBatch(index));
            });
        });
    }
    return result;
}
exports.map = map;
async function mapAsync(items, func, token, options) {
    if (items.length === 0) {
        return [];
    }
    const result = new Array(items.length);
    const timer = new Timer(options?.yieldAfter);
    async function convertBatch(start) {
        timer.start();
        for (let i = start; i < items.length; i++) {
            result[i] = await func(items[i], token);
            if (timer.shouldYield()) {
                options?.yieldCallback && options.yieldCallback();
                return i + 1;
            }
        }
        return -1;
    }
    let index = await convertBatch(0);
    while (index !== -1) {
        if (token !== undefined && token.isCancellationRequested) {
            break;
        }
        index = await new Promise((resolve) => {
            (0, vscode_languageserver_protocol_1.RAL)().timer.setImmediate(() => {
                resolve(convertBatch(index));
            });
        });
    }
    return result;
}
exports.mapAsync = mapAsync;
async function forEach(items, func, token, options) {
    if (items.length === 0) {
        return;
    }
    const timer = new Timer(options?.yieldAfter);
    function runBatch(start) {
        timer.start();
        for (let i = start; i < items.length; i++) {
            func(items[i]);
            if (timer.shouldYield()) {
                options?.yieldCallback && options.yieldCallback();
                return i + 1;
            }
        }
        return -1;
    }
    // Convert the first batch sync on the same frame.
    let index = runBatch(0);
    while (index !== -1) {
        if (token !== undefined && token.isCancellationRequested) {
            break;
        }
        index = await new Promise((resolve) => {
            (0, vscode_languageserver_protocol_1.RAL)().timer.setImmediate(() => {
                resolve(runBatch(index));
            });
        });
    }
}
exports.forEach = forEach;
