package org.multiverse.commitbarriers;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.multiverse.TestThread;
import org.multiverse.api.Txn;
import org.multiverse.api.callables.TxnVoidCallable;
import org.multiverse.stms.gamma.GammaStm;
import org.multiverse.stms.gamma.transactionalobjects.GammaTxnInteger;

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

public class VetoCommitBarrier_vetoCommitTest {
    private GammaStm stm;

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

    @After
    public void tearDown() {
        clearCurrentThreadInterruptedStatus();
    }

    @Test
    public void whenNoPendingTransactions() {
        VetoCommitBarrier barrier = new VetoCommitBarrier();
        barrier.atomicVetoCommit();

        assertTrue(barrier.isCommitted());
    }

    @Test
    public void whenPendingTransactions() {
        VetoCommitBarrier barrier = new VetoCommitBarrier();

        GammaTxnInteger ref1 = new GammaTxnInteger(stm);
        GammaTxnInteger ref2 = new GammaTxnInteger(stm);
        GammaTxnInteger ref3 = new GammaTxnInteger(stm);

        IncThread thread1 = new IncThread(ref1, barrier);
        IncThread thread2 = new IncThread(ref2, barrier);
        IncThread thread3 = new IncThread(ref3, barrier);

        startAll(thread1, thread2, thread3);

        sleepMs(500);
        barrier.atomicVetoCommit();
        joinAll(thread1, thread2, thread3);

        assertIsCommitted(thread1.txn);
        assertIsCommitted(thread2.txn);
        assertIsCommitted(thread3.txn);

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

    @Test
    public void whenBarrierCommitted_thenIgnored() {
        VetoCommitBarrier barrier = new VetoCommitBarrier();
        barrier.atomicVetoCommit();

        barrier.atomicVetoCommit();
        assertTrue(barrier.isCommitted());
    }

    @Test
    public void whenBarrierAborted_thenCommitBarrierOpenException() {
        VetoCommitBarrier barrier = new VetoCommitBarrier();
        barrier.abort();

        try {
            barrier.atomicVetoCommit();
            fail();
        } catch (CommitBarrierOpenException expected) {
        }
        assertTrue(barrier.isAborted());
    }

    public class IncThread extends TestThread {
        private final GammaTxnInteger ref;
        private final VetoCommitBarrier barrier;
        private Txn txn;

        public IncThread(GammaTxnInteger ref, VetoCommitBarrier barrier) {
            super("IncThread");
            this.barrier = barrier;
            this.ref = ref;
        }

        @Override
        public void doRun() throws Exception {
            stm.getDefaultTxnExecutor().execute(new TxnVoidCallable() {
                @Override
                public void call(Txn txn) throws Exception {
                    IncThread.this.txn = txn;
                    ref.getAndIncrement(txn, 1);
                    barrier.joinCommit(txn);
                }
            });
        }
    }
}
