import { LinkedList } from "../../../util/array.js";
import { DropType, QueueType } from "./types.js";
// Having a drop ratio of 1 will empty the queue which is too severe
// Worse case drop 95% of the queue
const MAX_DROP_RATIO = 0.95;
/**
 * Default gossip queue for all topics except for beacon_attestation
 * Support LIFO and FIFO type.
 */
export class LinearGossipQueue {
    opts;
    list = new LinkedList();
    // Increase _dropRatio gradually, retest its initial value if node is in good status
    _dropRatio = 0;
    // this is to avoid the case we drop 90% of the queue, then queue is empty and we consider
    // node is in good status
    recentDrop = false;
    // set recentDrop to false after we process up to maxLength items
    processedCountSinceDrop = 0;
    constructor(opts) {
        this.opts = opts;
        if (opts.dropOpts.type === DropType.ratio) {
            const { start, step } = opts.dropOpts;
            if (start <= 0 || start > 1) {
                throw Error(`Invalid drop ratio start ${start} step ${step}`);
            }
            this._dropRatio = opts.dropOpts.start;
        }
    }
    get length() {
        return this.list.length;
    }
    get keySize() {
        // this implementation doesn't support indexing
        return 1;
    }
    // not implemented for this gossip queue
    getDataAgeMs() {
        return [];
    }
    get dropRatio() {
        return this._dropRatio;
    }
    clear() {
        this.list.clear();
    }
    /**
     * Add item to gossip queue.
     * Return number of items dropped
     */
    add(item) {
        // this signals the node is not overloaded anymore
        if (this.opts.dropOpts.type === DropType.ratio && !this.recentDrop && this.length === 0) {
            // reset drop ratio to see if node is comfortable with it
            this._dropRatio = this.opts.dropOpts.start;
        }
        this.list.push(item);
        if (this.list.length <= this.opts.maxLength) {
            return 0;
        }
        // overload, need to drop more items
        if (this.opts.dropOpts.type === DropType.count) {
            return this.dropByCount(this.opts.dropOpts.count);
        }
        this.recentDrop = true;
        const droppedCount = this.dropByRatio(this._dropRatio);
        // increase drop ratio the next time queue is full
        this._dropRatio = Math.min(MAX_DROP_RATIO, this._dropRatio + this.opts.dropOpts.step);
        return droppedCount;
    }
    next() {
        let item = null;
        // LIFO -> pop() remove last item, FIFO -> shift() remove first item
        switch (this.opts.type) {
            case QueueType.LIFO:
                item = this.list.pop();
                break;
            case QueueType.FIFO:
                item = this.list.shift();
                break;
        }
        // it's ok to mark recent drop as false if we dropped <50% of the queue the last time
        if (this.opts.dropOpts.type === DropType.ratio && this.recentDrop && item !== null) {
            this.processedCountSinceDrop++;
            if (this.processedCountSinceDrop >= this.opts.maxLength) {
                this.recentDrop = false;
                this.processedCountSinceDrop = 0;
            }
        }
        return item;
    }
    getAll() {
        return this.list.toArray();
    }
    /**
     * Drop up to some ratio of items from the queue
     * ratio is from 0 to 1 inclusive
     * Return number of items dropped
     */
    dropByRatio(ratio) {
        if (ratio < 0 || ratio > 1) {
            throw Error(`Invalid ratio ${ratio}`);
        }
        if (ratio === 0) {
            return 0;
        }
        if (ratio === 1) {
            const numDeleted = this.length;
            this.clear();
            return numDeleted;
        }
        const count = Math.floor(this.list.length * ratio);
        return this.dropByCount(count);
    }
    /**
     * Drop up to some number of items from the queue
     * Return number of items dropped
     */
    dropByCount(count) {
        if (count <= 0) {
            return 0;
        }
        if (count >= this.length) {
            const numDeleted = this.length;
            this.clear();
            return numDeleted;
        }
        let i = 0;
        while (i < count && this.length > 0) {
            if (this.opts.type === QueueType.LIFO) {
                this.list.shift();
            }
            else {
                this.list.pop();
            }
            i++;
        }
        return i;
    }
}
//# sourceMappingURL=linear.js.map