import { assert } from "./assert.js";
import { BitSet } from "./binary/BitSet.js";

/**
 * Pool of unsigned integer IDs, useful when you don't want to just keep incrementing ID counter and want to re-use IDs after they are no longer needed
 * Main use case is when your IDs are array indices, having compact IDs lets you use fixed size array even as objects are added and removed as you're able to reuse slots in an efficient manner
 */
class IdPool {
    /**
     * @private
     * @type {BitSet}
     */
    #set = new BitSet();

    /**
     * Looks up next available ID without reserving it
     * If you want to also reserve the ID - use {@link get} instead
     * @returns {number}
     */
    peek() {
        return this.#set.nextClearBit(0);
    }

    /**
     *
     * @returns {number}
     */
    get() {
        const id = this.peek();

        this.#set.set(id, true);

        return id;
    }

    /**
     * Attempt to request a specific ID from the pool.
     * @param {number} id
     * @return {boolean} true if request succeeds, false otherwise
     */
    getSpecific(id) {
        if (this.isUsed(id)) {
            return false;
        }

        this.#set.set(id, true);
        return true;
    }

    /**
     *
     * @param {number} id
     * @returns {boolean}
     */
    isUsed(id) {
        return this.#set.get(id);
    }

    /**
     * Traverse all IDs currently in use
     * @param {function(id:number)} visitor
     */
    traverseUsed(visitor) {
        assert.isFunction(visitor, "visitor");

        const set = this.#set;

        for (let i = set.nextSetBit(0); i !== -1; i = set.nextSetBit(i + 1)) {
            visitor(i);
        }

    }

    /**
     *
     * @param {number} id
     */
    release(id) {
        this.#set.clear(id);
    }

    /**
     * Release all used IDs
     */
    reset() {
        this.#set.reset();
    }
}

export default IdPool;
