/*
 * JBoss, Home of Professional Open Source
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.cache.optimistic;

import org.jboss.cache.TreeCache;
import org.jboss.cache.Fqn;

import java.util.Date;

/**
 * A base class containing config parameters with which to run tests.
 *
 * @author <a href="mailto:manik@jboss.org">Manik Surtani (manik@jboss.org)</a>
 */
public abstract class OptimisticPerfTestBase
{
    protected static int NUM_LOOPS = 2500;
    protected static int NUM_THREADS = 10;

    protected TreeCache cache;
    protected String testName;

    protected Results results;

    /**
     * Configures the cache.
     */
    protected abstract void configure() throws Exception;

    /**
     * Starts the cache
     */
    protected void startCache() throws Exception
    {
        cache.startService();
    }

    /**
     * Destroys the cache
     */
    protected void stopCache()
    {
        if (cache != null) cache.stopService();
        cache = null;
    }

    /**
     * Wait that happens between tests - may be overridden
     */
    protected void doWaitBetweenTests()
    {
        System.gc();
        try
        {
            Thread.sleep(1000);
        }
        catch (Exception e)
        {
        }
    }

    /**
     * The method that controls the running of tests.
     */
    protected void runTests() throws Exception
    {
        configure();
        System.out.println("Interceptor chain for test " + testName + ": " + cache.getInterceptorChain());
        startCache();
        runTestsInternal();
        stopCache();
    }

    protected void runTestsInternal() throws Exception
    {
        results = new Results(testName, NUM_LOOPS, NUM_THREADS);

        Thread[] threads = new Thread[NUM_THREADS];
        for (int t = 0; t<NUM_THREADS; t++)
        {
            threads[t] = new Thread()
            {
                public void run()
                {
                    doTest();
                }
            };
            threads[t].setName("WorkerThread-" + t);
            threads[t].start();
        }

        for (int t=0; t<NUM_THREADS; t++)
        {
            threads[t].join();
        }
    }

    protected void doTest()
    {
        try
        {
            String key = "key", value = "value";
            String fqnBase = "/test/" + Thread.currentThread().getName() + "/loop_";

            // do puts
            for (int i = 0; i < NUM_LOOPS; i++)
            {
                Fqn fqn = Fqn.fromString(fqnBase + i);
                long stTime = System.currentTimeMillis();
                cache.put(fqn, key, value);
                results.recordPut(System.currentTimeMillis() - stTime);
            }

            // gc and pause
            doWaitBetweenTests();

            // do gets
            for (int i = 0; i < NUM_LOOPS; i++)
            {
                Fqn fqn = Fqn.fromString(fqnBase + i);
                long stTime = System.currentTimeMillis();
                Object o = cache.get(fqn, key);
                results.recordGet(System.currentTimeMillis() - stTime);
            }

            // gc and pause
            doWaitBetweenTests();

            // do removes
            for (int i = 0; i < NUM_LOOPS; i++)
            {
                Fqn fqn = Fqn.fromString(fqnBase + i);
                long stTime = System.currentTimeMillis();
                Object o = cache.remove(fqn, key);
                results.recordRemove(System.currentTimeMillis() - stTime);
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }


    protected class Results
    {
        long totalTime;
        long numOperations;

        long totalTimePut;
        long numOperationsPut;

        long totalTimeGet;
        long numOperationsGet;

        long totalTimeRemove;
        long numOperationsRemove;

        int numLoops, numThreads;
        String testNameLocal;
        String testStartedDate;

        public Results(String testName, int numLoops, int numThreads)
        {
            this.numLoops = numLoops;
            this.numThreads = numThreads;
            this.testNameLocal = new String(testName);
            testStartedDate = new Date().toString();
        }

        double averageTime()
        {
            return simpleAverage(totalTime, numOperations);
        }

        double averageTimePut()
        {
            return simpleAverage(totalTimePut, numOperationsPut);
        }

        double averageTimeGet()
        {
            return simpleAverage(totalTimeGet, numOperationsGet);
        }

        double averageTimeRemove()
        {
            return simpleAverage(totalTimeRemove, numOperationsRemove);
        }


        double simpleAverage(long tot, long num)
        {
            return (double) tot / (double) num;
        }

        synchronized void recordPut(long l)
        {
            numOperations++;
            numOperationsPut++;
            totalTime += l;
            totalTimePut += l;
        }

        synchronized void recordGet(long l)
        {
            numOperations++;
            numOperationsGet++;
            totalTime += l;
            totalTimeGet += l;
        }

        synchronized void recordRemove(long l)
        {
            numOperations++;
            numOperationsRemove++;
            totalTime += l;
            totalTimeRemove += l;
        }

        public String toString()
        {
            StringBuffer buf = new StringBuffer();
            buf.append("-------------------------------------------------");
            buf.append("\n");
            buf.append(" Time Run: ").append(testStartedDate);
            buf.append("\n");
            buf.append(" Test Name: ").append(testNameLocal);
            buf.append("\n");
            buf.append(" Num Loops: ").append(numLoops);
            buf.append("\n");
            buf.append(" Num Threads: ").append(numThreads);
            buf.append("\n");
            buf.append("\n");
            buf.append(" ** Overall timings");
            buf.append("\n");
            buf.append(" Total time: ").append(results.totalTime);
            buf.append("\n");
            buf.append(" Num Ops: ").append(results.numOperations);
            buf.append("\n");
            buf.append(" Avg time: ").append(results.averageTime());
            buf.append("\n");
            buf.append("\n");
            buf.append(" ** Put timings");
            buf.append("\n");
            buf.append(" Total time: ").append(results.totalTimePut);
            buf.append("\n");
            buf.append(" Num Ops: ").append(results.numOperationsPut);
            buf.append("\n");
            buf.append(" Avg time: ").append(results.averageTimePut());
            buf.append("\n");
            buf.append("\n");
            buf.append(" ** Get timings");
            buf.append("\n");
            buf.append(" Total time: ").append(results.totalTimeGet);
            buf.append("\n");
            buf.append(" Num Ops: ").append(results.numOperationsGet);
            buf.append("\n");
            buf.append(" Avg time: ").append(results.averageTimeGet());
            buf.append("\n");
            buf.append("\n");
            buf.append(" ** Remove timings");
            buf.append("\n");
            buf.append(" Total time: ").append(results.totalTimeRemove);
            buf.append("\n");
            buf.append(" Num Ops: ").append(results.numOperationsRemove);
            buf.append("\n");
            buf.append(" Avg time: ").append(results.averageTimeRemove());
            buf.append("\n");
            buf.append("-------------------------------------------------");
            buf.append("\n");
            return buf.toString();
        }


        public String toCSVString()
        {
            StringBuffer buf = new StringBuffer();
            buf.append(testNameLocal);
            buf.append(",");
            buf.append(testStartedDate);
            buf.append(",");
            buf.append(numLoops);
            buf.append(",");
            buf.append(numThreads);
            buf.append(",");
            buf.append(results.totalTime);
            buf.append(",");
            buf.append(results.numOperations);
            buf.append(",");
            buf.append(results.averageTime());
            buf.append(",");
            buf.append(results.totalTimePut);
            buf.append(",");
            buf.append(results.numOperationsPut);
            buf.append(",");
            buf.append(results.averageTimePut());
            buf.append(",");
            buf.append(results.totalTimeGet);
            buf.append(",");
            buf.append(results.numOperationsGet);
            buf.append(",");
            buf.append(results.averageTimeGet());
            buf.append(",");
            buf.append(results.totalTimeRemove);
            buf.append(",");
            buf.append(results.numOperationsRemove);
            buf.append(",");
            buf.append(results.averageTimeRemove());
            return buf.toString();
        }

    }
}
