/*
 *
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */

package org.jboss.cache;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.eviction.RegionManager;

import java.util.Random;

/**
 * Local mode test for concurrent EvictionTimerTask evicting nodes and Client removing nodes from TreeCache.
 *
 * @version $Revision: 3147 $
 * @author <a href="mailto:uwe.lamprecht@gmx.de">Uwe Lamprecht</a> July 29 2004
 */
public final class ConcurrentEvictAndRemoveTest extends TestCase
{
    private static final Log log = LogFactory.getLog(ConcurrentEvictAndRemoveTest.class.getName());
    private TreeCache cache;


   static void log(String msg) {
      System.out.println(msg);
      log.info(msg);
   }

   void err(String msg) {
      // System.err.println(msg);
      log.error(msg);
   }

    public ConcurrentEvictAndRemoveTest(String name)
    {
        super(name);
    }

    public void setUp() throws Exception
    {
        super.setUp();
        cache = new TreeCache();
        cache.setCacheMode(TreeCache.LOCAL);
        PropertyConfigurator config = new PropertyConfigurator();
        config.configure(cache, "META-INF/local-mru-eviction-service.xml");
        cache.setTransactionManagerLookupClass("org.jboss.cache.DummyTransactionManagerLookup");
        cache.start();
    }
    public void tearDown() throws Exception
    {
        super.tearDown();
        cache.stop();
    }

    public void test() throws Exception
    {
        final long MAX_LOOP_TIME = 4000; // value to experiment;)
        int numberOfInitialCreatedNodes = 0;

        log("*** Start Test: ConcurrentEvictAndRemoveTest ***");

        // creation of nodes
        {
            final StopWatch stopWatch = new StopWatch();
            int i = 0;
            while(true) {
                i++;
                final Fqn fqn = Fqn.fromString("/test/item_" + i);
                cache.put(fqn, "att", null);
                if (stopWatch.getTime()>MAX_LOOP_TIME) {
                    break;
                }
            }
            numberOfInitialCreatedNodes = i;
            log(
                    "nodes created: "
                    + numberOfInitialCreatedNodes
                    + " time: "
                    + stopWatch.getTime()
                    + " ms"

            );
        }

        // random remove nodes
        {
            int numberCacheHitSuccess = 0;
            int numberCacheHitFailure = 0;
            final Random random = new Random();
            final StopWatch stopWatch = new StopWatch();

            while(true) {
                final Fqn fqn = Fqn.fromString("/test/item_" + random.nextInt(numberOfInitialCreatedNodes));
                if (cache.exists(fqn)) {
                    cache.remove(fqn);
                    numberCacheHitSuccess++;
                } else {
                    numberCacheHitFailure++;
                }
                if (stopWatch.getTime()>MAX_LOOP_TIME) {
                    break;
                }
            }
            log(
                    "nodes removed: "
                    + numberCacheHitSuccess
                    + ", total cache hits: "
                    + (numberCacheHitFailure + numberCacheHitSuccess)
                    + " time: "
                    + stopWatch.getTime()
                    + " ms"
            );
        }

        // create/remove nodes (-->queue overflow and blocking)
        {
            log("create put/remove events");
            final int SEQUENCE_SIZE = 10000; // number of nodes processed by a single thread
            final long TIMEOUT_DEFAULT = 100000;
            final int MAX_TIMEOUT_FACTOR = 100;

            for(int i = 0; i < RegionManager.CAPACITY; i = i + SEQUENCE_SIZE) {
                final int sequenceStart = i;
                final int sequenceEnd = i + SEQUENCE_SIZE -1;

                Thread thread = new Thread(
                        new HelperThread(cache, sequenceStart, sequenceEnd),
                        "HelperThread processing sequence " + sequenceStart
                );

                long timeout = (HelperThread.getTime()==HelperThread.EXECUTION_TIME_NOT_INITIALIZED)?
                        TIMEOUT_DEFAULT:
                            HelperThread.getTime()*MAX_TIMEOUT_FACTOR;

                thread.start();
                try {
                    thread.join(timeout);
                    if (thread.isAlive()) {
                        // Timeout occurred; thread has not finished
                        err("Timeout of " + timeout + " ms exceeded for sequence of put/remove operations.");
                        fail("Timeout of " + timeout + " ms exceeded for sequence of put/remove operations.");
                    } else {
                        // Finished
                    }
                }
                catch(InterruptedException ignore) {
                    err("Thread join interrupted.");
                }
            }
        }
        log("*** Successfully finished! ***");
    }
    public static Test suite() throws Exception
    {
        return new TestSuite(ConcurrentEvictAndRemoveTest.class);
    }

    // private util classes
    private static final class StopWatch {
        public StopWatch() {
            startTime = System.currentTimeMillis() ;
        }
        private final long startTime;   // Start time of stopwatch.
        //   (Time is measured in milliseconds.)

        public String toString() {
            return String.valueOf(getTime());
        }
        public long getTime() {
            return System.currentTimeMillis() - startTime;
        }
    }
    private static final class HelperThread implements Runnable {
        private final TreeCache c;
        private final int start;
        private final int end;
        // static member
        public static final long EXECUTION_TIME_NOT_INITIALIZED = -1;
        private static long previousExceutionTime = EXECUTION_TIME_NOT_INITIALIZED;
        private static final String FQN_PREFIX =  "/test/overflow-item_";

        public HelperThread ( TreeCache cache, int start, int end) {
            this.c = cache;
            this.start = start;
            this.end = end;
        }
        public void run() {
            StopWatch stopWatch = new StopWatch();
            log(
                    "process node range: "
                    +  this.start
                    + "/"
                    + this.end
            );
            try {
                for (int i = start; i<=end; i++) {
                    final Fqn fqn = Fqn.fromString(FQN_PREFIX + i);
                    c.put(fqn, "att", null);
                    c.remove(fqn);
                    //Thread.yield();
                }
            } catch (Exception e) {
                System.out.println("exception: " + e);
                if ( e instanceof RuntimeException) {
                    throw (RuntimeException)e;
                } else {
                    throw new RuntimeException(e);
                }
            }
            previousExceutionTime = stopWatch.getTime();
            log(
                    "time: "
                    + previousExceutionTime
                    + " ms"
            );
        }
        public static long getTime() {
            return previousExceutionTime;
        }
    }
}
