package org.ggp.base.util.symbol.grammar;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public final class SymbolPool
{

    private static final ConcurrentMap<String, SymbolAtom> atomPool = new ConcurrentHashMap<String, SymbolAtom>();
    private static final ConcurrentMap<List<Symbol>, SymbolList> listPool = new ConcurrentHashMap<List<Symbol>, SymbolList>();

    /**
     * If the pool does not have a mapping for the given key, adds a mapping from key to value
     * to the pool.
     *
     * Note that even if you've checked to make sure that the pool doesn't contain the key,
     * you still shouldn't assume that this method actually inserts the given value, since
     * this class is accessed by multiple threads simultaneously.
     *
     * @return the value mapped to by key in the pool
     */
    private static <K,V> V addToPool(K key, V value, ConcurrentMap<K, V> pool) {
        V prevValue = pool.putIfAbsent(key, value);
        if(prevValue == null)
            return value;
        else
            return prevValue;
    }

    public static SymbolAtom getAtom(String value)
    {
        SymbolAtom ret = atomPool.get(value);
        if(ret == null)
            ret = addToPool(value, new SymbolAtom(value), atomPool);

        return ret;
    }

    public static SymbolList getList(List<Symbol> contents)
    {
        SymbolList ret = listPool.get(contents);
        if(ret == null)
            ret = addToPool(contents, new SymbolList(contents), listPool);

        return ret;
    }

    public static SymbolList getList(Symbol[] contents)
    {
        return getList(Arrays.asList(contents));
    }

    /**
     * Drains the contents of the SymbolPool. Useful to control memory usage
     * once you have finished playing a large game. This should be safe to call
     * any time during gameplay, but according to my experiments the SymbolPool
     * has a 97% cache hit rate during a game, so you likely only want to call
     * this between games (since the symbols from an old game are unlikely to
     * reappear in another unrelated game).
     */
    public static void drainPool() {
        atomPool.clear();
        listPool.clear();
    }
}

