package org.multiverse.stms.gamma.integration.isolation.levels;

import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.multiverse.api.IsolationLevel;
import org.multiverse.api.Txn;
import org.multiverse.api.callables.TxnVoidCallable;
import org.multiverse.api.exceptions.ReadWriteConflict;
import org.multiverse.stms.gamma.GammaStm;
import org.multiverse.stms.gamma.transactionalobjects.GammaTxnLong;
import org.multiverse.stms.gamma.transactions.GammaTxn;
import org.multiverse.stms.gamma.transactions.GammaTxnFactory;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.multiverse.api.GlobalStmInstance.getGlobalStmInstance;
import static org.multiverse.api.TxnThreadLocal.clearThreadLocalTxn;
import static org.multiverse.stms.gamma.GammaTestUtils.makeReadBiased;

public class IsolationLevelSerializableTest {

    private GammaStm stm;
    private GammaTxnFactory transactionFactory;

    @Before
    public void setUp() {
        stm = (GammaStm) getGlobalStmInstance();
        clearThreadLocalTxn();
        transactionFactory = stm.newTxnFactoryBuilder()
                .setSpeculative(false)
                .setIsolationLevel(IsolationLevel.Serializable)
                .newTransactionFactory();
    }

    @Test
    public void repeatableRead_whenTracked_thenNoInconsistentRead() {
        final GammaTxnLong ref = new GammaTxnLong(stm);

        transactionFactory = stm.newTxnFactoryBuilder()
                .setSpeculative(false)
                .setReadTrackingEnabled(true)
                .setIsolationLevel(IsolationLevel.Serializable)
                .newTransactionFactory();

        GammaTxn tx = transactionFactory.newTxn();
        ref.get(tx);

        ref.atomicIncrementAndGet(1);

        long read2 = ref.get(tx);
        assertEquals(0, read2);
    }

    @Test
    @Ignore
    public void repeatableRead_whenNotTrackedAndConflictingUpdate_thenReadConflict() {
        final GammaTxnLong ref = makeReadBiased(new GammaTxnLong(stm));

        transactionFactory = stm.newTxnFactoryBuilder()
                .setSpeculative(false)
                .setReadTrackingEnabled(false)
                .setBlockingAllowed(false)
                .setIsolationLevel(IsolationLevel.Serializable)
                .newTransactionFactory();

        GammaTxn tx = transactionFactory.newTxn();
        ref.get(tx);

        ref.atomicIncrementAndGet(1);

        try {
            ref.get(tx);
            fail();
        } catch (ReadWriteConflict expected) {

        }
    }

    @Test
    public void causalConsistency_whenConflictingWrite_thenReadWriteConflict() {
        final GammaTxnLong ref1 = new GammaTxnLong(stm);
        final GammaTxnLong ref2 = new GammaTxnLong(stm);

        GammaTxn tx = transactionFactory.newTxn();

        ref1.get(tx);

        stm.getDefaultTxnExecutor().execute(new TxnVoidCallable() {
            @Override
            public void call(Txn tx) throws Exception {
                ref1.incrementAndGet(1);
                ref2.incrementAndGet(1);
            }
        });

        try {
            ref2.get(tx);
            fail();
        } catch (ReadWriteConflict expected) {

        }
    }

    @Test
    public void writeSkewNotPossible() {
        final GammaTxnLong ref1 = new GammaTxnLong(stm);
        final GammaTxnLong ref2 = new GammaTxnLong(stm);

        GammaTxn tx = transactionFactory.newTxn();
        ref1.get(tx);

        ref2.incrementAndGet(tx, 1);

        ref1.atomicIncrementAndGet(1);

        try {
            tx.commit();
            fail();
        } catch (ReadWriteConflict expected) {

        }

        assertEquals(1, ref1.atomicGet());
        assertEquals(0, ref2.atomicGet());
    }
}
