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

import { noop } from "../../function/noop.js";

/**
 * Object Pool pattern implementation.
 * Reuse objects instead of relying on garbage collector. Useful in cases where creating or destroying an object is costly, and objects are relatively short-lived
 * @template T
 */
export class ObjectPoolFactory {
    /**
     * @private
     * @type {Array<T>}
     */
    pool = [];

    /**
     * @type {number}
     */
    maxSize = 256;

    /**
     * @param {function():T} creator
     * @param {function(T)} destroyer
     * @param {function(T)} resetter
     */
    constructor(
        creator,
        destroyer = noop,
        resetter = noop
    ) {
        assert.isFunction(creator, 'creator');
        assert.isFunction(destroyer, 'destroyer');
        assert.isFunction(resetter, 'resetter');

        /**
         * @private
         * @type {function(): T}
         */
        this.creator = creator;

        /**
         * @private
         * @type {function(T)}
         */
        this.destroyer = destroyer;

        /**
         * @private
         * @type {function(T)}
         */
        this.resetter = resetter;

    }

    /**
     *
     * @returns {T}
     */
    get() {
        const pool = this.pool;

        if (pool.length > 0) {

            const oldInstance = pool.pop();

            //reset the object
            this.resetter(oldInstance);

            assert.notNull(oldInstance, 'oldInstance');
            assert.defined(oldInstance, 'oldInstance');

            return oldInstance;

        } else {

            const newInstance = this.creator();

            assert.notNull(newInstance, 'newInstance');
            assert.defined(newInstance, 'newInstance');

            return newInstance;

        }
    }

    /**
     *
     * @param {T} object
     * @returns {boolean} true if object was added back to the pool, false if pool was full and object was destroyed instead
     */
    release(object) {
        assert.notNull(object, 'object');
        assert.defined(object, 'object');
        assert.isInstanceOf(this, ObjectPoolFactory, 'this');

        const pool = this.pool;

        assert.arrayHasNo(pool, object, `Pool already contains object that is being attempted to be release`);

        if (pool.length >= this.maxSize) {
            //pool is too large, destroy and discard the object
            if (this.destroyer !== noop) {
                this.destroyer(object);
            }

            return false;
        }

        //add it to the pool
        pool.push(object);

        return true;
    }
}


/**
 * @deprecated use `get` instead
 * @type {function(): T}
 */
ObjectPoolFactory.prototype.create = ObjectPoolFactory.prototype.get