package org.multiverse.stms.gamma;

import org.junit.Before;
import org.junit.Test;
import org.multiverse.api.*;
import org.multiverse.api.exceptions.IllegalTxnFactoryException;
import org.multiverse.api.lifecycle.TxnListener;
import org.multiverse.stms.gamma.transactions.GammaTxn;
import org.multiverse.stms.gamma.transactions.GammaTxnConfig;
import org.multiverse.stms.gamma.transactions.GammaTxnFactory;
import org.multiverse.stms.gamma.transactions.GammaTxnFactoryBuilder;
import org.multiverse.stms.gamma.transactions.fat.FatMonoGammaTxn;

import java.util.List;

import static java.util.Arrays.asList;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;

public class GammaStm_txnFactoryBuilderTest implements GammaConstants{
    private GammaStm stm;

    @Before
    public void setUp() {
        stm = new GammaStm();
    }

    @Test
    public void whenDefaultTransactionFactory() {
        GammaTxnConfig config = new GammaTxnConfig(stm);
        config.init();

        assertEquals(IsolationLevel.Snapshot, config.isolationLevel);
        assertFalse(config.isInterruptible());
        assertFalse(config.isReadonly());
        assertEquals(LockMode.None, config.readLockMode);
        assertEquals(LockMode.None, config.writeLockMode);
        assertTrue(config.dirtyCheck);
        assertSame(stm, config.getStm());
        assertSame(stm.getGlobalConflictCounter(), config.getGlobalConflictCounter());
        assertTrue(config.trackReads);
        assertTrue(config.blockingAllowed);
        assertEquals(1000, config.maxRetries);
        assertTrue(config.isSpeculative());
        assertTrue(config.isAnonymous);
        assertSame(DefaultBackoffPolicy.MAX_100_MS, config.getBackoffPolicy());
        assertEquals(Long.MAX_VALUE, config.getTimeoutNs());
        assertSame(TraceLevel.None, config.getTraceLevel());
        assertTrue(config.writeSkewAllowed);
        assertEquals(PropagationLevel.Requires, config.getPropagationLevel());
        assertTrue(config.getPermanentListeners().isEmpty());
    }

    @Test(expected = NullPointerException.class)
    public void whenNullPermanentListener_thenNullPointerException() {
        stm.newTxnFactoryBuilder().addPermanentListener(null);
    }

    @Test
    public void whenPermanentListenerAdded() {
        GammaTxnFactoryBuilder oldBuilder = stm.newTxnFactoryBuilder();
        TxnListener listener = mock(TxnListener.class);
        GammaTxnFactoryBuilder newBuilder = oldBuilder.addPermanentListener(listener);

        assertEquals(asList(listener), newBuilder.getConfig().getPermanentListeners());
        assertTrue(oldBuilder.getConfig().getPermanentListeners().isEmpty());
    }

    @Test
    public void whenPermanentListenerAdded_thenNoCheckForDuplicates() {
        GammaTxnFactoryBuilder oldBuilder = stm.newTxnFactoryBuilder();
        TxnListener listener = mock(TxnListener.class);
        GammaTxnFactoryBuilder newBuilder = oldBuilder.addPermanentListener(listener)
                .addPermanentListener(listener);

        assertEquals(asList(listener, listener), newBuilder.getConfig().getPermanentListeners());
    }

    @Test
    public void whenNoPermanentListenersAdded_thenEmptyList() {
        GammaTxnFactoryBuilder builder = stm.newTxnFactoryBuilder();
        assertTrue(builder.getConfig().getPermanentListeners().isEmpty());
    }

    @Test
    public void whenMultipleListenersAdded_thenTheyAreAddedInOrder() {
        TxnListener listener1 = mock(TxnListener.class);
        TxnListener listener2 = mock(TxnListener.class);
        GammaTxnFactoryBuilder builder = stm.newTxnFactoryBuilder()
                .addPermanentListener(listener1)
                .addPermanentListener(listener2);

        List<TxnListener> listeners = builder.getConfig().getPermanentListeners();
        assertEquals(asList(listener1, listener2), listeners);
    }

    @Test
    public void whenGetPermanentListenersCalled_immutableListReturned() {
        GammaTxnFactoryBuilder builder = stm.newTxnFactoryBuilder()
                .addPermanentListener(mock(TxnListener.class))
                .addPermanentListener(mock(TxnListener.class));

        List<TxnListener> listeners = builder.getConfig().getPermanentListeners();

        try {
            listeners.clear();
            fail();
        } catch (UnsupportedOperationException e) {
        }
    }

    @Test
    public void whenReadLockModeOverridesWriteLockMode() {
        whenReadLockModeOverridesWriteLockMode(LockMode.None, LockMode.None);
        whenReadLockModeOverridesWriteLockMode(LockMode.None, LockMode.Read);
        whenReadLockModeOverridesWriteLockMode(LockMode.None, LockMode.Write);
        whenReadLockModeOverridesWriteLockMode(LockMode.None, LockMode.Exclusive);

        whenReadLockModeOverridesWriteLockMode(LockMode.Read, LockMode.None);
        whenReadLockModeOverridesWriteLockMode(LockMode.Read, LockMode.Read);
        whenReadLockModeOverridesWriteLockMode(LockMode.Read, LockMode.Write);
        whenReadLockModeOverridesWriteLockMode(LockMode.Read, LockMode.Exclusive);

        whenReadLockModeOverridesWriteLockMode(LockMode.Write, LockMode.None);
        whenReadLockModeOverridesWriteLockMode(LockMode.Write, LockMode.Read);
        whenReadLockModeOverridesWriteLockMode(LockMode.Write, LockMode.Write);
        whenReadLockModeOverridesWriteLockMode(LockMode.Write, LockMode.Exclusive);

        whenReadLockModeOverridesWriteLockMode(LockMode.Exclusive, LockMode.None);
        whenReadLockModeOverridesWriteLockMode(LockMode.Exclusive, LockMode.Read);
        whenReadLockModeOverridesWriteLockMode(LockMode.Exclusive, LockMode.Write);
        whenReadLockModeOverridesWriteLockMode(LockMode.Exclusive, LockMode.Exclusive);
    }

    public void whenReadLockModeOverridesWriteLockMode(LockMode readLockMode, LockMode writeLockMode) {
        GammaTxnFactory txFactory = stm.newTxnFactoryBuilder()
                .setWriteLockMode(writeLockMode)
                .setReadLockMode(readLockMode)
                .newTransactionFactory();

        assertEquals(readLockMode, txFactory.getConfig().readLockMode);
        assertEquals(readLockMode.asInt(), txFactory.getConfig().readLockModeAsInt);

        if (readLockMode.asInt() > writeLockMode.asInt()) {
            assertEquals(readLockMode, txFactory.getConfig().getWriteLockMode());
            assertEquals(readLockMode.asInt(), txFactory.getConfig().writeLockModeAsInt);
        } else {
            assertEquals(writeLockMode, txFactory.getConfig().getWriteLockMode());
            assertEquals(writeLockMode.asInt(), txFactory.getConfig().writeLockModeAsInt);
        }
    }

    @Test
    public void whenWriteLockModeOverridesReadLockMode() {
        whenWriteLockModeOverridesReadLockMode(LockMode.None, LockMode.None, true);
        whenWriteLockModeOverridesReadLockMode(LockMode.None, LockMode.Read, true);
        whenWriteLockModeOverridesReadLockMode(LockMode.None, LockMode.Write, true);
        whenWriteLockModeOverridesReadLockMode(LockMode.None, LockMode.Exclusive, true);

        whenWriteLockModeOverridesReadLockMode(LockMode.Read, LockMode.None, false);
        whenWriteLockModeOverridesReadLockMode(LockMode.Read, LockMode.Read, true);
        whenWriteLockModeOverridesReadLockMode(LockMode.Read, LockMode.Write, true);
        whenWriteLockModeOverridesReadLockMode(LockMode.Read, LockMode.Exclusive, true);

        whenWriteLockModeOverridesReadLockMode(LockMode.Write, LockMode.None, false);
        whenWriteLockModeOverridesReadLockMode(LockMode.Write, LockMode.Read, false);
        whenWriteLockModeOverridesReadLockMode(LockMode.Write, LockMode.Write, true);
        whenWriteLockModeOverridesReadLockMode(LockMode.Write, LockMode.Exclusive, true);

        whenWriteLockModeOverridesReadLockMode(LockMode.Exclusive, LockMode.None, false);
        whenWriteLockModeOverridesReadLockMode(LockMode.Exclusive, LockMode.Read, false);
        whenWriteLockModeOverridesReadLockMode(LockMode.Exclusive, LockMode.Write, false);
        whenWriteLockModeOverridesReadLockMode(LockMode.Exclusive, LockMode.Exclusive, true);
    }

    public void whenWriteLockModeOverridesReadLockMode(LockMode readLock, LockMode writeLock, boolean success) {
        if (success) {
            GammaTxnFactory txFactory = stm.newTxnFactoryBuilder()
                    .setReadLockMode(readLock)
                    .setWriteLockMode(writeLock)
                    .newTransactionFactory();

            assertEquals(readLock, txFactory.getConfig().getReadLockMode());
            assertEquals(writeLock, txFactory.getConfig().getWriteLockMode());
        } else {
            try {
                stm.newTxnFactoryBuilder()
                        .setReadLockMode(readLock)
                        .setWriteLockMode(writeLock)
                        .newTransactionFactory();
                fail();
            } catch (IllegalTxnFactoryException expected) {
            }
        }
    }

    @Test
    public void whenReadtrackingDisabled() {
        GammaTxnFactory txFactory = stm.newTxnFactoryBuilder()
                .setReadTrackingEnabled(false)
                .setBlockingAllowed(false)
                .newTransactionFactory();

        assertFalse(txFactory.getConfig().isReadTrackingEnabled());
    }

    @Test
    public void whenSpeculativeConfigEnabled() {
        GammaTxnFactory txFactory = stm.newTxnFactoryBuilder()
                .setDirtyCheckEnabled(false)
                .setSpeculative(true)
                .newTransactionFactory();

        GammaTxnConfig configuration = txFactory.getConfig();
        assertFalse(configuration.getSpeculativeConfiguration().fat);
        assertTrue(configuration.isSpeculative());
    }

    @Test
    public void whenWriteSkewNotAllowed() {
        GammaTxnFactory txFactory = stm.newTxnFactoryBuilder()
                .setSpeculative(true)
                .setIsolationLevel(IsolationLevel.Serializable)
                .newTransactionFactory();


        GammaTxnConfig configuration = txFactory.getConfig();
        assertTrue(configuration.getSpeculativeConfiguration().fat);
        assertTrue(configuration.isSpeculative());
    }

    @Test
    public void whenSerializedThenFatTransaction() {
        GammaTxnFactory txFactory = stm.newTxnFactoryBuilder()
                .setSpeculative(true)
                .setDirtyCheckEnabled(false)
                .setIsolationLevel(IsolationLevel.Serializable)
                .newTransactionFactory();

        GammaTxn tx = txFactory.newTxn();
        assertTrue(tx instanceof FatMonoGammaTxn);
    }

    @Test
    public void whenSnapshotIsolationLevelThenLeanTransaction() {
        GammaTxnFactory txFactory = stm.newTxnFactoryBuilder()
                .setSpeculative(true)
                .setDirtyCheckEnabled(false)
                .setIsolationLevel(IsolationLevel.Snapshot)
                .newTransactionFactory();

        GammaTxn tx = txFactory.newTxn();
        assertEquals(TRANSACTIONTYPE_LEAN_MONO, tx.transactionType);
    }

     @Test
    public void whenReadonlyThenFatTransaction() {
        GammaTxnFactory txFactory = stm.newTxnFactoryBuilder()
                .setSpeculative(true)
                .setReadonly(true)
                .setDirtyCheckEnabled(false)
                .newTransactionFactory();

        GammaTxn tx = txFactory.newTxn();
        assertEquals(TRANSACTIONTYPE_FAT_MONO,tx.transactionType);
    }
}
