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

import junit.framework.Assert;
import junit.framework.TestCase;
import org.jboss.cache.Fqn;
import org.jboss.cache.TreeCache;
import org.jboss.cache.interceptors.Interceptor;
import org.jboss.cache.config.Option;

import javax.transaction.TransactionManager;
import java.util.HashMap;
import java.util.Map;

/**
 * Tests the cache mode local override in various scenarios.  To be subclassed to test REPL_SYNC, REPL_ASYNC, INVALIDATION_SYNC, INVALIDATION_ASYNC for Opt and Pess locking.
 *
 * Option.setCacheModeLocal() only applies to put() and remove() methods.
 *
 * @author <a href="mailto:manik@jboss.org">Manik Surtani (manik@jboss.org)</a>
 */
public abstract class CacheModeLocalTestBase extends TestCase
{
    // to be subclassed.
    protected int cacheMode;
    protected String nodeLockingScheme;
    /** set this to true if the implementing class plans to use an invalidating cache mode **/
    protected boolean isInvalidation;

    private TreeCache cache1;
    private TreeCache cache2;

    private Option localOverride, defaultCacheMode;

    private Fqn fqn = Fqn.fromString("/a");
    private String key = "key";

    protected void setUp() throws Exception
    {
        // force a tear down if the test runner didn't run one before (happens in IDEA)
        if (cache1 != null || cache2 != null) tearDown();

        cache1 = new TreeCache("test", null, 1000);
        cache1.setTransactionManagerLookupClass("org.jboss.cache.DummyTransactionManagerLookup");
        cache1.setNodeLockingScheme( nodeLockingScheme );
        cache1.setCacheMode( cacheMode );
        cache1.startService();

        cache2 = new TreeCache("test", null, 1000);
        cache2.setTransactionManagerLookupClass("org.jboss.cache.DummyTransactionManagerLookup");
        cache2.setNodeLockingScheme(nodeLockingScheme);
        cache2.setCacheMode(cacheMode);
        cache2.startService();

        localOverride = new Option();
        defaultCacheMode = new Option();

        localOverride.setCacheModeLocal( true );
        defaultCacheMode.setCacheModeLocal( false );
    }

    protected void tearDown()
    {
        if (cache1 != null)
        {
            cache1.stopService();
            cache1 = null;
        }

        if (cache2 != null)
        {
            cache2.stopService();
            cache2 = null;
        }
    }

    public void testPutKeyValue() throws Exception
    {
        cache1.put(fqn, key, "value", localOverride);
        delay();
        // cache1 should still have this
        Assert.assertEquals("value", cache1.get(fqn, key));

        // cache 2 should not
        Assert.assertNull("Should be null", cache2.get(fqn, key));

        // now try again with passing the default options
        cache1.put(fqn, key, "value", defaultCacheMode);
        delay();
        // cache1 should still have this
        Assert.assertEquals("value", cache1.get(fqn, key));

        // cache 2 should as well
        if (!isInvalidation)
            Assert.assertEquals("value", cache2.get(fqn, key));
        else
            Assert.assertNull("should be invalidated", cache2.get(fqn, key));

        // now cache2
        cache2.put(fqn, key, "value2", localOverride);
        delay();
        Assert.assertEquals("value2", cache2.get(fqn, key));
        Assert.assertEquals("value", cache1.get(fqn, key));

        cache2.put(fqn, key, "value2", defaultCacheMode);
        delay();
        Assert.assertEquals("value2", cache2.get(fqn, key));
        if (!isInvalidation)
            Assert.assertEquals("value2", cache1.get(fqn, key));
        else
            Assert.assertNull("should be invalidated", cache1.get(fqn, key));
    }

    public void testPutData() throws Exception
    {
        Map map = new HashMap();
        map.put(key, "value");

        cache1.put(fqn, map, localOverride);
        delay();
        // cache1 should still have this
        Assert.assertEquals("value", cache1.get(fqn, key));
        // cache 2 should not
        Assert.assertNull("Should be null", cache2.get(fqn, key));

        // now try again with passing the default options
        cache1.put(fqn, map, defaultCacheMode);
        delay();
        // cache1 should still have this
        Assert.assertEquals("value", cache1.get(fqn, key));
        // cache 2 should as well
        if (!isInvalidation)
            Assert.assertEquals("value", cache2.get(fqn, key));
        else
            Assert.assertNull("should be invalidated", cache2.get(fqn, key));

        // now cache2
        map.put(key, "value2");
        cache2.put(fqn, map, localOverride);
        delay();
        Assert.assertEquals("value2", cache2.get(fqn, key));
        Assert.assertEquals("value", cache1.get(fqn, key));

        cache2.put(fqn, key, "value2", defaultCacheMode);
        delay();
        Assert.assertEquals("value2", cache2.get(fqn, key));
        if (!isInvalidation)
            Assert.assertEquals("value2", cache1.get(fqn, key));
        else
            Assert.assertNull("should be invalidated", cache1.get(fqn, key));
    }

    public void testRemoveNode() throws Exception
    {
        // put some stuff in the cache first
        // make sure we cleanup thread local vars.
        ((Interceptor) cache1.getInterceptors().get(0)).getInvocationContext().setOptionOverrides(null);
        cache1.put(fqn, key, "value");
        delay();
        Assert.assertEquals("value", cache1.get(fqn, key));
        if (isInvalidation)
            Assert.assertNull("Should be null", cache2.get(fqn, key));
        else
            Assert.assertEquals("value", cache2.get(fqn, key));

        cache1.remove(fqn, localOverride);
        delay();

        // should be removed in cache1
        Assert.assertNull("should be null", cache1.get(fqn, key));
        // Not in cache2
        if (isInvalidation)
            Assert.assertNull("Should be null", cache2.get(fqn, key));
        else
            Assert.assertEquals("value", cache2.get(fqn, key));

        // replace cache entries
        cache1.put(fqn, key, "value");
        delay();
        Assert.assertEquals("value", cache1.get(fqn, key));
        if (isInvalidation)
            Assert.assertNull("Should be null", cache2.get(fqn, key));
        else
            Assert.assertEquals("value", cache2.get(fqn, key));

        // now try again with passing the default options
        cache1.remove(fqn, defaultCacheMode);
        delay();

        // both should be null
        Assert.assertNull("should be null", cache1.get(fqn, key));
        Assert.assertNull("should be null", cache2.get(fqn, key));
    }

    public void testRemoveKey() throws Exception
    {
        // put some stuff in the cache first
        ((Interceptor) cache1.getInterceptors().get(0)).getInvocationContext().setOptionOverrides(null);
        cache1.put(fqn, key, "value");
        delay();
        Assert.assertEquals("value", cache1.get(fqn, key));
        if (isInvalidation)
            Assert.assertNull("Should be null", cache2.get(fqn, key));
        else
            Assert.assertEquals("value", cache2.get(fqn, key));

        cache1.remove(fqn, key, localOverride);
        delay();

        // should be removed in cache1
        Assert.assertNull("should be null", cache1.get(fqn, key));
        // Not in cache2
        if (isInvalidation)
            Assert.assertNull("Should be null", cache2.get(fqn, key));
        else
            Assert.assertEquals("value", cache2.get(fqn, key));

        // replace cache entries
        cache1.put(fqn, key, "value");
        delay();
        Assert.assertEquals("value", cache1.get(fqn, key));
        if (isInvalidation)
            Assert.assertNull("Should be null", cache2.get(fqn, key));
        else
            Assert.assertEquals("value", cache2.get(fqn, key));

        // now try again with passing the default options
        cache1.remove(fqn, key, defaultCacheMode);
        delay();

        // both should be null
        Assert.assertNull("should be null", cache1.get(fqn, key));
        Assert.assertNull("should be null", cache2.get(fqn, key));
    }

    public void testTransactionalBehaviour() throws Exception
    {
        TransactionManager mgr = cache1.getTransactionManager();
        mgr.begin();
        cache1.put(fqn, key, "value1", defaultCacheMode);
        cache1.put(fqn, key, "value2", localOverride);
        mgr.commit();
        delay();
        // cache1 should still have this
        Assert.assertEquals("value2", cache1.get(fqn, key));

        if (!isInvalidation)
            Assert.assertEquals("value1", cache2.get(fqn, key));
        else
            assertNull(cache2.get(fqn, key));

        // now try again with passing the default options
        mgr.begin();
        cache1.put(fqn, key, "value3", localOverride);
        cache1.put(fqn, key, "value", defaultCacheMode);
        mgr.commit();
        delay();
        // cache1 should still have this
        Assert.assertEquals("value", cache1.get(fqn, key));

        // cache 2 should as well
        if (!isInvalidation)
            Assert.assertEquals("value", cache2.get(fqn, key));
        else
            Assert.assertNull("should be invalidated", cache2.get(fqn, key));

        // now cache2
        mgr = cache2.getTransactionManager();
        mgr.begin();
        cache2.put(fqn, key, "value3", defaultCacheMode);
        cache2.put(fqn, key, "value2", localOverride);
        mgr.commit();
        delay();

        Assert.assertEquals("value2", cache2.get(fqn, key));

        if (!isInvalidation)
            assertEquals("value3", cache1.get(fqn, key));
        else
            assertNull(cache1.get(fqn, key));

        mgr.begin();
        cache2.put(fqn, key, "value2", localOverride);
        cache2.put(fqn, key, "value2", defaultCacheMode);
        mgr.commit();
        delay();
        Assert.assertEquals("value2", cache2.get(fqn, key));
        if (!isInvalidation)
            Assert.assertEquals("value2", cache1.get(fqn, key));
        else
            Assert.assertNull("should be invalidated", cache1.get(fqn, key));

    }

    protected abstract void delay();

}