package voldemort.socketpool;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import junit.framework.TestCase;
import voldemort.utils.pool.KeyedResourcePool;
import voldemort.utils.pool.ResourceFactory;
import voldemort.utils.pool.ResourcePoolConfig;

public abstract class AbstractSocketPoolTest<K, V> extends TestCase {

    protected static class TestStats {

        public int timeoutRequests;

        public TestStats() {
            timeoutRequests = 0;
        }
    }

    public TestStats startTest(final ResourceFactory<K, V> factory,
                               final ResourcePoolConfig config,
                               int nThreads,
                               int nRequests) throws Exception {

        final KeyedResourcePool<K, V> pool = new KeyedResourcePool<K, V>(factory, config);
        final ConcurrentHashMap<K, AtomicInteger> resourceInHand = new ConcurrentHashMap<K, AtomicInteger>();
        ExecutorService executor = Executors.newFixedThreadPool(nThreads);
        final TestStats testStats = new TestStats();
        final AtomicBoolean passed = new AtomicBoolean(true);

        for(int i = 0; i < nRequests; i++) {
            final K key = getRequestKey();
            resourceInHand.putIfAbsent(key, new AtomicInteger(0));
            executor.execute(new Runnable() {

                public void run() {
                    try {
                        // borrow resource
                        V resource = pool.checkout(key);
                        resourceInHand.get(key).incrementAndGet();

                        // assert that resourceInHand is less than equal to pool
                        // Size
                        if(resourceInHand.get(key).get() > config.getMaxPoolSize()) {
                            passed.set(false);
                        }

                        assertEquals("resources In Hand(" + resourceInHand.get(key).get()
                                             + ") should be less than equal to pool size("
                                             + config.getMaxPoolSize() + ")",
                                     true,
                                     resourceInHand.get(key).get() <= config.getMaxPoolSize());

                        // do something
                        doSomethingWithResource(key, resource);

                        // return
                        resourceInHand.get(key).decrementAndGet();
                        pool.checkin(key, resource);
                    } catch(TimeoutException e) {
                        ++testStats.timeoutRequests;
                        return;
                    } catch(Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }

        executor.shutdown();
        executor.awaitTermination(5 * 60, TimeUnit.SECONDS);

        if(!passed.get()) {
            fail();
        }
        return testStats;
    }

    protected abstract K getRequestKey() throws Exception;

    protected abstract void doSomethingWithResource(K key, V resource) throws Exception;
}
