//  • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
//  ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
//  ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
//  ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
//  ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
//      Magicbane Emulator Project © 2013 - 2026
//                https://www.magicbane.com


package engine.pooling;


import org.pmw.tinylog.Logger;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

public abstract class LinkedObjectPool<T> {
    private final LinkedBlockingQueue<T> pool;
    private final AtomicInteger poolSize;

    /**
     * Constructor that allows the caller to specify initial array size and
     * percent of prefill.
     *
     * @param size - initial size for the containing array (pool)
     */
    public LinkedObjectPool(int size) {
        this.pool = new LinkedBlockingQueue<>();
        this.poolSize = new AtomicInteger();
    }

    /**
     * Default Constructor that uses default initial Array size and percent
     * prefill values
     */
    public LinkedObjectPool() {
        this(0);
    }

    /**
     * Forces pool to add <i>numberOfObjects</i> to the pool. This may cause
     * internal ArrayList to resize.
     *
     * @param numberOfObjects
     */
    public void fill(int numberOfObjects) {
        for (int i = 0; i < numberOfObjects; ++i)
            this.makeAndAdd();
    }

    /**
     * Forces subclasses to implement factory routine for object creation.
     */
    protected abstract T makeNewObject();

    /**
     * Forces subclasses to implement a way to reset object to a reusable state.
     */
    protected abstract void resetObject(T obj);

    /**
     * Generic Get routine. If the pool is empty, then one (or more) of T type
     * objects will be created. If more than one T is created, then they will be
     * put into the pool. One T will always be created and returned.
     */
    public T get() {
        T obj = pool.poll();

        if (obj == null) {
            //Oops pool is empty.. make a new obj
            obj = this.makeNewObject();
        } else {
            poolSize.decrementAndGet();
        }

        return obj;
    }

    /**
     * Generic put routine. If the current pool size is below threshold, this
     * object will be pooled, otherwise it will be NULL'ed and scheduled for
     * Garbage Collection
     *
     * @param obj
     */
    public void put(T obj) {
        //Logger.debug("Objectpool.put().  Pool size: " + pool.size());
        this.resetObject(obj);
        this.poolSize.incrementAndGet();
        this.pool.add(obj);
    }

    /**
     * Helper method. Attempts to create and add a new <i>T</i> object. If this
     * fails, an error is logged.
     */
    protected final void makeAndAdd() {
        T obj = this.makeNewObject();
        if (obj == null) {
            Logger.error("Pooling failure: Object creation failed.");
        } else {
            this.put(obj);
        }
    }

    /**
     * @return the current size of the pool, not the maximum capacity of the
     * Array holding the pool.
     */
    public final int getPoolSize() {
        return this.poolSize.get();
    }

    /**
     * Culls the pool and removes half of the stored objects. Removed objects
     * are NULL'ed and scheduled form Garbage Collection.
     *
     * @return the number of Objects removed from the pool.
     */
    public int cullHalf() {
        int full, half;
        full = this.getPoolSize();
        if (full < 1) {
            return full;
        }

        half = (full / 2);

        for (int i = 0; i < (full / 2); ++i) {
            T obj = this.pool.poll();
            obj = null; // Null out for GC
        }
        return half;
    }

    protected void handlePoolExhaustion() {
        //Not needed in this implementation
    }
}
