/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.test.cache.test.eviction;

import java.util.Random;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

import org.apache.log4j.Logger;
import org.jboss.cache.Fqn;
import org.jboss.cache.PropertyConfigurator;
import org.jboss.cache.TreeCache;
import org.jboss.cache.eviction.RegionManager;
/**
 * Local mode test for concurrent EvictionTimerTask evicting nodes and Client removing nodes from TreeCache.
 *
 * @version $Revision: 1.1.2.4 $
 * @author <a href="mailto:uwe.lamprecht@gmx.de">Uwe Lamprecht</a> July 29 2004
 */
public final class ConcurrentEvictAndRemoveTestCase extends TestCase
{
    private static final Logger logger = Logger.getLogger(ConcurrentEvictAndRemoveTestCase.class.getName());
    private TreeCache cache;


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

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

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

    public void setUp() throws Exception
    {
        cache = new TreeCache();
        cache.setCacheMode(TreeCache.LOCAL);
        PropertyConfigurator config = new PropertyConfigurator();
        config.configure(cache, "META-INF/local-eviction-service.xml");
        cache.setTransactionManagerLookupClass("org.jboss.cache.DummyTransactionManagerLookup");
        cache.start();
    }
    public void tearDown() throws Exception
    {
        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 = 10;

            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)
                );

                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(ConcurrentEvictAndRemoveTestCase.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 static final Logger logger = Logger.getLogger(HelperThread.class.getName());
        private final TreeCache cache;
        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.cache = 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);
                    cache.put(fqn, "att", null);
                    cache.remove(fqn);
                    //Thread.yield();
                }
            } catch (Exception e) {
                System.out.println("exception: " + e);
                if ( e instanceof RuntimeException) {
                    throw (RuntimeException)e;
                } else {
                    throw new RuntimeException(e.getMessage());
                }
            }
            previousExceutionTime = stopWatch.getTime();
            log(
                    "time: "
                    + previousExceutionTime
                    + " ms"
            );
        }
        public static long getTime() {
            return previousExceutionTime;
        }
    }
}
