package org.multiverse.stms.gamma.integration.readonly;

import org.junit.Before;
import org.junit.Test;
import org.multiverse.api.Txn;
import org.multiverse.api.TxnExecutor;
import org.multiverse.api.callables.TxnCallable;
import org.multiverse.api.callables.TxnLongCallable;
import org.multiverse.api.callables.TxnVoidCallable;
import org.multiverse.api.exceptions.ReadonlyException;
import org.multiverse.stms.gamma.GammaStm;
import org.multiverse.stms.gamma.transactionalobjects.GammaTxnLong;
import org.multiverse.stms.gamma.transactions.GammaTxn;

import static org.junit.Assert.*;
import static org.multiverse.api.GlobalStmInstance.getGlobalStmInstance;
import static org.multiverse.api.TxnThreadLocal.clearThreadLocalTxn;

public class ReadonlyTest {

    private GammaStm stm;

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

    @Test
    public void whenReadonly_thenUpdateFails() {
        GammaTxnLong ref = new GammaTxnLong(stm);
        try {
            updateInReadonlyMethod(ref, 10);
            fail();
        } catch (ReadonlyException expected) {
        }

        assertEquals(0, ref.atomicGet());
    }

    public void updateInReadonlyMethod(final GammaTxnLong ref, final int newValue) {
        TxnExecutor executor = stm.newTxnFactoryBuilder()
                .setReadonly(true)
                .newTxnExecutor();

        executor.execute(new TxnVoidCallable() {
            @Override
            public void call(Txn tx) throws Exception {
                GammaTxn btx = (GammaTxn) tx;
                ref.getAndSet(btx, newValue);
            }
        });
    }

    @Test
    public void whenReadonly_thenCreationOfNewTxnObjectNotFails() {
        try {
            readonly_createNewTransactionObject(10);
            fail();
        } catch (ReadonlyException expected) {
        }
    }

    public void readonly_createNewTransactionObject(final long value) {
        TxnExecutor executor = stm.newTxnFactoryBuilder()
                .setReadonly(true)
                .newTxnExecutor();

        executor.execute(new TxnVoidCallable() {
            @Override
            public void call(Txn tx) throws Exception {
                GammaTxn btx = (GammaTxn) tx;
                GammaTxnLong ref = new GammaTxnLong(btx);
                ref.openForConstruction(btx).long_value = value;
            }
        });
    }

    @Test
    public void whenReadonly_thenCreationOfNonTxnObjectSucceeds() {
        Integer ref = readonly_createNormalObject(100);
        assertNotNull(ref);
        assertEquals(100, ref.intValue());
    }

    public Integer readonly_createNormalObject(final int value) {
        TxnExecutor executor = stm.newTxnFactoryBuilder()
                .setReadonly(true)
                .newTxnExecutor();

        return executor.execute(new TxnCallable<Integer>() {
            @Override
            public Integer call(Txn tx) throws Exception {
                return new Integer(value);
            }
        });
    }

    @Test
    public void whenReadonly_thenReadAllowed() {
        GammaTxnLong ref = new GammaTxnLong(stm, 10);
        long result = readInReadonlyMethod(ref);
        assertEquals(10, result);
        assertEquals(10, ref.atomicGet());
    }

    public long readInReadonlyMethod(final GammaTxnLong ref) {
        TxnExecutor executor = stm.newTxnFactoryBuilder()
                .setReadonly(true)
                .newTxnExecutor();

        return executor.execute(new TxnLongCallable() {
            @Override
            public long call(Txn tx) throws Exception {
                GammaTxn btx = (GammaTxn) tx;
                return ref.get(btx);
            }
        });
    }

    @Test
    public void whenUpdate_thenCreationOfNewTxnObjectsSucceeds() {
        GammaTxnLong ref = update_createNewTransactionObject(100);
        assertNotNull(ref);
        assertEquals(100, ref.atomicGet());
    }

    public GammaTxnLong update_createNewTransactionObject(final int value) {
        TxnExecutor executor = stm.newTxnFactoryBuilder()
                .setReadonly(false)
                .newTxnExecutor();

        return executor.execute(new TxnCallable<GammaTxnLong>() {
            @Override
            public GammaTxnLong call(Txn tx) throws Exception {
                GammaTxn btx = (GammaTxn) tx;
                GammaTxnLong ref = new GammaTxnLong(btx);
                ref.openForConstruction(btx).long_value = value;
                return ref;
            }
        });
    }

    @Test
    public void whenUpdate_thenCreationOfNonTxnObjectSucceeds() {
        Integer ref = update_createNormalObject(100);
        assertNotNull(ref);
        assertEquals(100, ref.intValue());
    }

    public Integer update_createNormalObject(final int value) {
        TxnExecutor executor = stm.newTxnFactoryBuilder()
                .setReadonly(false)
                .newTxnExecutor();

        return executor.execute(new TxnCallable<Integer>() {
            @Override
            public Integer call(Txn tx) throws Exception {
                return new Integer(value);
            }
        });
    }

    @Test
    public void whenUpdate_thenReadSucceeds() {
        GammaTxnLong ref = new GammaTxnLong(stm, 10);
        long result = readInUpdateMethod(ref);
        assertEquals(10, result);
        assertEquals(10, ref.atomicGet());
    }


    public long readInUpdateMethod(final GammaTxnLong ref) {
        TxnExecutor executor = stm.newTxnFactoryBuilder()
                .setReadonly(false)
                .newTxnExecutor();

        return executor.execute(new TxnLongCallable() {
            @Override
            public long call(Txn tx) throws Exception {
                GammaTxn btx = (GammaTxn) tx;
                return ref.get(btx);
            }
        });
    }

    @Test
    public void whenUpdate_thenUpdateSucceeds() {
        GammaTxnLong ref = new GammaTxnLong(stm);
        updateInUpdateMethod(ref, 10);
        assertEquals(10, ref.atomicGet());
    }

    public void updateInUpdateMethod(final GammaTxnLong ref, final int newValue) {
        TxnExecutor executor = stm.newTxnFactoryBuilder()
                .setReadonly(false)
                .newTxnExecutor();

        executor.execute(new TxnVoidCallable() {
            @Override
            public void call(Txn tx) throws Exception {
                assertFalse(tx.getConfig().isReadonly());
                GammaTxn btx = (GammaTxn) tx;
                ref.getAndSet(btx, newValue);
            }
        });
    }

    @Test
    public void whenDefault_thenUpdateSuccess() {
        GammaTxnLong ref = new GammaTxnLong(stm);
        defaultTransactionalMethod(ref);

        assertEquals(1, ref.atomicGet());
    }


    public void defaultTransactionalMethod(final GammaTxnLong ref) {
        stm.newTxnFactoryBuilder().newTxnExecutor().execute(new TxnVoidCallable() {
            @Override
            public void call(Txn tx) throws Exception {
                assertFalse(tx.getConfig().isReadonly());
                GammaTxn btx = (GammaTxn) tx;
                ref.getAndSet(btx, ref.get(btx) + 1);
            }
        });
    }
}
