package org.multiverse.stms.gamma.transactionalobjects.txnref;

import org.junit.Before;
import org.junit.Test;
import org.multiverse.api.LockMode;
import org.multiverse.api.exceptions.LockedException;
import org.multiverse.stms.gamma.GammaStm;
import org.multiverse.stms.gamma.GammaStmConfig;
import org.multiverse.stms.gamma.transactionalobjects.GammaTxnRef;
import org.multiverse.stms.gamma.transactions.GammaTxn;

import static org.junit.Assert.assertSame;
import static org.junit.Assert.fail;
import static org.multiverse.TestUtils.assertOrecValue;
import static org.multiverse.api.TxnThreadLocal.clearThreadLocalTxn;
import static org.multiverse.stms.gamma.GammaTestUtils.*;
import static org.multiverse.stms.gamma.transactionalobjects.AbstractGammaObject.setReadonlyCount;
import static org.multiverse.stms.gamma.transactionalobjects.AbstractGammaObject.setSurplus;

public class GammaTxnRef_atomicSetTest {

    private GammaStm stm;

    @Before
    public void setUp() {
        GammaStmConfig config = new GammaStmConfig();
        config.maxRetries = 0;
        stm = new GammaStm(config);
        clearThreadLocalTxn();
    }

    // =================== write biased ==============================

    @Test
    public void writeBiased_whenReadLocked_thenLockedException() {
        writeBiased_whenLocked_thenLockedException(LockMode.Read);
    }

    @Test
    public void writeBiased_whenWriteLocked_thenLockedException() {
        writeBiased_whenLocked_thenLockedException(LockMode.Write);
    }

    @Test
    public void writeBiased_whenExclusiveLocked_thenLockedException() {
        writeBiased_whenLocked_thenLockedException(LockMode.Exclusive);
    }

    public void writeBiased_whenLocked_thenLockedException(LockMode lockMode) {
        String initialValue = "foo";
        GammaTxnRef<String> ref = new GammaTxnRef<String>(stm, initialValue);
        long initialVersion = ref.version;
        GammaTxn tx = stm.newDefaultTxn();
        ref.getLock().acquire(tx, lockMode);
        long orecValue = ref.orec;
        long globalConflictCount = stm.globalConflictCounter.count();

        try {
            ref.atomicSet("bar");
            fail();
        } catch (LockedException expected) {
        }

        assertOrecValue(ref, orecValue);
        assertGlobalConflictCount(stm, globalConflictCount);
        assertVersionAndValue(ref, initialVersion, initialValue);
    }

    @Test
    public void writeBiased_whenNoChange() {
        String initialValue = "foo";
        GammaTxnRef<String> ref = new GammaTxnRef<String>(stm, initialValue);
        long initialVersion = ref.version;
        long orecValue = ref.orec;
        long globalConflictCount = stm.globalConflictCounter.count();

        String result = ref.atomicSet(initialValue);

        assertSame(initialValue, result);
        assertOrecValue(ref, setReadonlyCount(orecValue, 1));
        assertGlobalConflictCount(stm, globalConflictCount);
        assertVersionAndValue(ref, initialVersion, initialValue);
    }

    @Test
    public void writeBiased_whenNoSurplusOfReaders() {
        String initialValue = "foo";
        GammaTxnRef<String> ref = new GammaTxnRef<String>(stm, initialValue);
        long initialVersion = ref.version;
        long orecValue = ref.orec;
        long globalConflictCount = stm.globalConflictCounter.count();

        String newValue = "bar";
        String result = ref.atomicSet(newValue);

        assertSame(newValue, result);
        assertOrecValue(ref, orecValue);
        assertGlobalConflictCount(stm, globalConflictCount);
        assertVersionAndValue(ref, initialVersion+1, newValue);
    }

    @Test
    public void writeBiased_whenSurplusOfReaders_thenGlobalConflictCounterIncreased() {
        String initialValue = "foo";
        GammaTxnRef<String> ref = new GammaTxnRef<String>(stm, initialValue);
        long initialVersion = ref.version;

        ref.arrive(1);

        long orecValue = ref.orec;
        long globalConflictCount = stm.globalConflictCounter.count();

        String newValue = "bar";
        String result = ref.atomicSet(newValue);

        assertSame(newValue, result);
        assertOrecValue(ref, orecValue);
        assertGlobalConflictCount(stm, globalConflictCount+1);
        assertVersionAndValue(ref, initialVersion+1, newValue);
    }

    // =================== read biased ==============================

    @Test
    public void readBiased_whenReadLocked_thenLockedException() {
        readBiased_whenLocked_thenLockedException(LockMode.Read);
    }

    @Test
    public void readBiased_whenWriteLocked_thenLockedException() {
        readBiased_whenLocked_thenLockedException(LockMode.Write);
    }

    @Test
    public void readBiased_whenExclusiveLocked_thenLockedException() {
        readBiased_whenLocked_thenLockedException(LockMode.Exclusive);
    }

    public void readBiased_whenLocked_thenLockedException(LockMode lockMode) {
        String initialValue = "foo";
        GammaTxnRef<String> ref = makeReadBiased(new GammaTxnRef<String>(stm, initialValue));
        long initialVersion = ref.version;
        GammaTxn tx = stm.newDefaultTxn();
        ref.getLock().acquire(tx, lockMode);
        long orecValue = ref.orec;
        long globalConflictCount = stm.globalConflictCounter.count();

        try {
            ref.atomicSet("bar");
            fail();
        } catch (LockedException expected) {
        }

        assertOrecValue(ref, orecValue);
        assertGlobalConflictCount(stm, globalConflictCount);
        assertVersionAndValue(ref, initialVersion, initialValue);
    }

    @Test
    public void readBiased_whenNoChange() {
        String initialValue = "foo";
        GammaTxnRef<String> ref = makeReadBiased(new GammaTxnRef<String>(stm, initialValue));
        long initialVersion = ref.version;
        long orecValue = ref.orec;
        long globalConflictCount = stm.globalConflictCounter.count();

        String result = ref.atomicSet(initialValue);

        System.out.println(ref.toDebugString());

        assertSame(initialValue, result);
        assertOrecValue(ref, setSurplus(orecValue, 1));
        assertGlobalConflictCount(stm, globalConflictCount);
        assertVersionAndValue(ref, initialVersion, initialValue);
    }

    @Test
    public void readBiased_whenNoSurplusOfReaders() {
        String initialValue = "foo";
        GammaTxnRef<String> ref = makeReadBiased(new GammaTxnRef<String>(stm, initialValue));

        long initialVersion = ref.version;
        long globalConflictCount = stm.globalConflictCounter.count();

        String newValue = "bar";
        String result = ref.atomicSet(newValue);

        System.out.println(ref.toDebugString());

        assertSame(newValue, result);
        assertWriteBiased(ref);
        assertSurplus(ref, 0);
        assertReadonlyCount(ref, 0);
        assertLockMode(ref, LockMode.None);
        assertGlobalConflictCount(stm, globalConflictCount);
        assertVersionAndValue(ref, initialVersion+1, newValue);
    }

    @Test
    public void readBiased_whenSurplusOfReaders_thenGlobalConflictCounterIncreased() {
        String initialValue = "foo";
        GammaTxnRef<String> ref = makeReadBiased(new GammaTxnRef<String>(stm, initialValue));
        long initialVersion = ref.version;

        ref.arrive(1);
        long globalConflictCount = stm.globalConflictCounter.count();

        String newValue = "bar";
        String result = ref.atomicSet(newValue);

        System.out.println(ref.toDebugString());

        assertSame(newValue, result);
        assertWriteBiased(ref);
        assertSurplus(ref, 0);
        assertReadonlyCount(ref, 0);
        assertLockMode(ref, LockMode.None);
        assertGlobalConflictCount(stm, globalConflictCount+1);
        assertVersionAndValue(ref, initialVersion+1, newValue);
    }
}
