"use strict";

/**
* Handle ratelimiting something
* @prop {Number} tokens How many tokens the bucket has consumed in this interval
* @prop {Number} lastReset Timestamp of last token clearing
* @prop {Number} lastSend Timestamp of last token consumption
* @prop {Number} tokenLimit The max number tokens the bucket can consume per interval
* @prop {Number} interval How long (in ms) to wait between clearing used tokens
*/
class Bucket {
    /**
    * Construct a Bucket
    * @arg {Number} tokenLimit The max number of tokens the bucket can consume per interval
    * @arg {Number} interval How long (in ms) to wait between clearing used tokens
    * @arg {Object} [options] Optional parameters
    * @arg {Number} options.reservedTokens How many tokens to reserve for priority operations
    * @arg {Object} options.latencyRef A latency reference object
    * @arg {Number} options.latencyRef.latency Interval between consuming tokens
    */
    constructor(tokenLimit, interval, options = {}) {
        this.tokenLimit = tokenLimit;
        this.interval = interval;
        this.latencyRef = options.latencyRef || {latency: 0};
        this.lastReset = this.tokens = this.lastSend = 0;
        this.reservedTokens = options.reservedTokens || 0;
        this._queue = [];
    }

    /**
    * Queue something in the Bucket
    * @arg {Function} func A callback to call when a token can be consumed
    * @arg {Boolean} priority Whether or not the callback should use reserved tokens
    */
    queue(func, priority=false) {
        if(priority) {
            this._queue.unshift({func, priority});
        } else {
            this._queue.push({func, priority});
        }
        this.check();
    }

    check() {
        if(this.timeout || this._queue.length === 0) {
            return;
        }
        if(this.lastReset + this.interval + this.tokenLimit * this.latencyRef.latency < Date.now()) {
            this.lastReset = Date.now();
            this.tokens = Math.max(0, this.tokens - this.tokenLimit);
        }

        let val;
        const tokensAvailable = this.tokens < this.tokenLimit;
        const unreservedTokensAvailable = this.tokens < (this.tokenLimit - this.reservedTokens);
        while(this._queue.length > 0 && (unreservedTokensAvailable || (tokensAvailable && this._queue[0].priority))) {
            this.tokens++;
            const item = this._queue.shift();
            val = this.latencyRef.latency - Date.now() + this.lastSend;
            if(this.latencyRef.latency === 0 || val <= 0) {
                item.func();
                this.lastSend = Date.now();
            } else {
                setTimeout(() => {
                    item.func();
                }, val);
                this.lastSend = Date.now() + val;
            }
        }

        if(this._queue.length > 0 && !this.timeout) {
            this.timeout = setTimeout(() => {
                this.timeout = null;
                this.check();
            }, this.tokens < this.tokenLimit ? this.latencyRef.latency : Math.max(0, this.lastReset + this.interval + this.tokenLimit * this.latencyRef.latency - Date.now()));
        }
    }
}

module.exports = Bucket;

@@ -2046,20 +2046,18 @@
         
-cons
+le
 t tokens
@@ -2107,20 +2107,18 @@
         
-cons
+le
 t unrese
@@ -2340,16 +2340,172 @@
 kens++;%0A
+            tokensAvailable = this.tokens %3C this.tokenLimit;%0A            unreservedTokensAvailable = this.tokens %3C (this.tokenLimit - this.reservedTokens);%0A
         

Fix Bucket ratelimiting (#965)