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


package engine.pooling;

import org.pmw.tinylog.Logger;

import java.nio.ByteBuffer;
import java.util.HashMap;

public class MultisizeByteBufferPool {

    /**
     * List of the powers of two from 2^0 to 2^30. The index of the array
     * corresponds to the power of two. Example: If you'd like to quickly lookup
     * 2^19, then reference powersOfTwo[19]
     */
    public static final int[] powersOfTwo = {
            1, // 2^0
            2, // 2^1
            4, // 2^2
            8, // 2^3
            16, // 2^4
            32, // 2^5
            64, // 2^6
            128, // 2^7
            256, // 2^8
            512, // 2^9
            1024, // 2^10
            2048, // 2^11
            4096, // 2^12
            8192, // 2^13
            16384, // 2^14
            32768, // 2^15
            65536, // 2^16
            131072, // 2^17
            262144, // 2^18
            524288, // 2^19
            1048576, // 2^20
            2097152, // 2^21
            4194304, // 2^22
            8388608, // 2^23
            16777216, // 2^24
            33554432, // 2^25
            67108864, // 2^26
            134217728, // 2^27
            268435456, // 2^28
            536870912, // 2^29
            1073741824, // 2^30
    };
    /**
     * Maps a power of two (0-30) to a BB Pool
     */
    private final HashMap<Integer, ByteBufferPool> powerToPoolMap = new HashMap<>();

    public MultisizeByteBufferPool() {
        super();
    }

    /**
     * Returns the next power of two that is larger than or equal too the input
     * <i>value</i>
     *
     * @param value
     * @return the power of two that is larger than or equal too the input
     * <i>value</i>. A return of -1 indicates out of range.
     */
    public static int getPowerThatWillFit(final int value) {
        return (value == 0 ? 0 : 32 - Integer.numberOfLeadingZeros(value - 1));
    }

    private static ByteBufferPool makeByteBufferPool(int bbInitialSize) {
        return new ByteBufferPool(bbInitialSize);
    }

    /**
     * Gets a ByteBuffer that is of the size 2^<i>powerOfTwo</i> from the
     * appropriate pool.
     *
     * @param powerOfTwo int range of 0-30
     * @return
     */
    public ByteBuffer getBuffer(int powerOfTwo) {
        // Validate input
        if (powerOfTwo > 30 || powerOfTwo < 0) {
            Logger.error("powerOfTwo out of range (0-30) in getBuffer().  Got: " + powerOfTwo);
            return null;
        }

        // Check to see if there is a pool for this size
        ByteBufferPool bbp = this.getByteBufferPool(powerOfTwo);
        return bbp.get();
    }

    /**
     * Internal getter to provide synchronization. Adds ByteBufferPool if not mapped.
     *
     * @param powerOfTwo
     * @return
     */
    private ByteBufferPool getByteBufferPool(Integer powerOfTwo) {
        synchronized (this.powerToPoolMap) {
            // Check to see if there is a pool for this size
            ByteBufferPool bbp = powerToPoolMap.get(powerOfTwo);

            if (bbp == null) {
                bbp = MultisizeByteBufferPool.makeByteBufferPool(powersOfTwo[powerOfTwo]);
                this.putByteBufferPool(powerOfTwo, bbp);
            }
            return bbp;
        }
    }

    /**
     * Internal setter to provide synchronization
     *
     * @param powerOfTwo
     * @param bbp
     * @return
     */
    private ByteBufferPool putByteBufferPool(Integer powerOfTwo,
                                             ByteBufferPool bbp) {
        synchronized (this.powerToPoolMap) {
            return powerToPoolMap.put(powerOfTwo, bbp);
        }
    }

    public ByteBuffer getBufferToFit(int numOfBytes) {
        int pow = MultisizeByteBufferPool.getPowerThatWillFit(numOfBytes);
        return this.getBuffer(pow);
    }

    /**
     * Puts a ByteBuffer that is of the size 2^<i>powerOfTwo</i> back into the
     * appropriate pool.
     *
     * @param bb - Bytebuffer to put into a pool
     */
    public void putBuffer(ByteBuffer bb) {

        if (bb == null)
            return;

        // determine size:
        int pow = MultisizeByteBufferPool.getPowerThatWillFit(bb.capacity());

        // if we get here and pow == -1 then we have a bytebuffer > 2^30 !!!!
        // so just file it under power of 30;
        if (pow == -1) {
            pow = 30;
        }

        // get pool
        ByteBufferPool bbp = this.getByteBufferPool(pow);

        // put buffer (back) into pool
        bbp.put(bb);
    }

    /**
     * Returns the size of the ByteBufferPool mapped to the given powerOfTwo.
     *
     * @param powerOfTwo int range of 0-30
     * @return size of pool mapped to provided <i>powerOfTwo</i>. Returns -1 on
     * error and lastError will be set.
     */
    public int getSizeOfPool(int powerOfTwo) {
        if (powerOfTwo > 30 || powerOfTwo < 0) {
            Logger.error("powerOfTwo out of range (0-30) in getSizeOfPool().  Got: "
                    + powerOfTwo);
            return -1;
        }
        ByteBufferPool bbp = this.getByteBufferPool(powerOfTwo);

        return bbp.getPoolSize();
    }

}
