package net.i2p.util;

import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * Like ByteCache but works directly with byte arrays, not ByteArrays.
 * These are designed to be small caches, so there's no cleaner task
 * like there is in ByteCache. And we don't zero out the arrays here.
 * Only the static methods are public here.
 *
 * @since 0.8.3
 */
public final class SimpleByteCache {

    private static final ConcurrentHashMap<Integer, SimpleByteCache> _caches = new ConcurrentHashMap<Integer, SimpleByteCache>(8);

    private static final int DEFAULT_SIZE = 64;

    /** up to this, use ABQ to minimize object churn and for performance; above this, use LBQ for two locks */
    private static final int MAX_FOR_ABQ = 64;

    /**
     * Get a cache responsible for arrays of the given size
     *
     * @param size how large should the objects cached be?
     */
    public static SimpleByteCache getInstance(int size) {
        return getInstance(DEFAULT_SIZE, size);
    }

    /**
     * Get a cache responsible for objects of the given size
     *
     * @param cacheSize how large we want the cache to grow 
     *                  (number of objects, NOT memory size)
     *                  before discarding released objects.
     * @param size how large should the objects cached be?
     */
    public static SimpleByteCache getInstance(int cacheSize, int size) {
        Integer sz = Integer.valueOf(size);
        SimpleByteCache cache = _caches.get(sz);
        if (cache == null) {
            cache = new SimpleByteCache(cacheSize, size);
            SimpleByteCache old = _caches.putIfAbsent(sz, cache);
            if (old != null)
                cache = old;
        }
        cache.resize(cacheSize);
        return cache;
    }

    /**
     *  Clear everything (memory pressure)
     */
    public static void clearAll() {
        for (SimpleByteCache bc : _caches.values())
            bc.clear();
    }

    /** list of available and available entries */
    private Queue<byte[]> _available;
    private int _maxCached;
    private final int _entrySize;
    
    private SimpleByteCache(int maxCachedEntries, int entrySize) {
        _maxCached = maxCachedEntries;
        _available = createQueue();
        _entrySize = entrySize;
    }
    
    private void resize(int maxCachedEntries) {
        if (_maxCached >= maxCachedEntries) return;
        _maxCached = maxCachedEntries;
        // make a bigger one, move the cached items over
        Queue<byte[]> newLBQ = createQueue();
        byte[] ba;
        while ((ba = _available.poll()) != null)
            newLBQ.offer(ba);
        _available = newLBQ;
    }
    
    /**
     *  @return LBQ or ABQ
     *  @since 0.9.2
     */
    private Queue<byte[]> createQueue() {
        if (_entrySize <= MAX_FOR_ABQ)
            return new ArrayBlockingQueue<byte[]>(_maxCached);
        return new LinkedBlockingQueue<byte[]>(_maxCached);
    }

    /**
     * Get the next available array, either from the cache or a brand new one
     */
    public static byte[] acquire(int size) {
        return getInstance(size).acquire();
    }

    /**
     * Get the next available array, either from the cache or a brand new one
     */
    private byte[] acquire() {
        byte[] rv = _available.poll();
        if (rv == null)
            rv = new byte[_entrySize];
        return rv;
    }
    
    /**
     * Put this array back onto the available cache for reuse
     */
    public static void release(byte[] entry) {
        SimpleByteCache cache = _caches.get(entry.length);
        if (cache != null)
            cache.releaseIt(entry);
    }

    /**
     * Put this array back onto the available cache for reuse
     */
    private void releaseIt(byte[] entry) {
        if (entry == null || entry.length != _entrySize)
            return;
        // should be safe without this
        //Arrays.fill(entry, (byte) 0);
        _available.offer(entry);
    }
    
    /**
     *  Clear everything (memory pressure)
     */
    private void clear() {
        _available.clear();
    }
}
