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.api.exceptions.DeadTxnException;
import org.multiverse.stms.gamma.GammaStm;

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

public class CountDownCommitBarrier_joinCommitUninterruptiblyTest {
    private CountDownCommitBarrier barrier;
    private GammaStm stm;

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

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

    @Test
    public void whenOpenAndNullTransaction_thenNullPointerException() {
        barrier = new CountDownCommitBarrier(1);

        try {
            barrier.joinCommitUninterruptibly(null);
            fail();
        } catch (NullPointerException expected) {
        }

        assertTrue(barrier.isClosed());
        assertEquals(0, barrier.getNumberWaiting());
    }

    @Test
    public void whenOpenAndThreadAlreadyInterrupted_thenNotInterruptedButInterruptStatusIsSet() {
        barrier = new CountDownCommitBarrier(1);

        Thread.currentThread().interrupt();
        Txn tx = stm.newDefaultTxn();

        barrier.joinCommitUninterruptibly(tx);

        assertTrue(barrier.isCommitted());
        assertEquals(0, barrier.getNumberWaiting());
        assertTrue(Thread.currentThread().isInterrupted());
    }

    @Test
    public void whenOpenAndTransactionActive() {
        Txn tx = stm.newDefaultTxn();
        tx.prepare();

        barrier = new CountDownCommitBarrier(1);
        barrier.joinCommitUninterruptibly(tx);

        assertIsCommitted(tx);
        assertTrue(barrier.isCommitted());
    }

    @Test
    public void whenOpenAndTransactionPrepared() {
        Txn tx = stm.newDefaultTxn();
        tx.prepare();

        barrier = new CountDownCommitBarrier(1);
        barrier.joinCommitUninterruptibly(tx);

        assertIsCommitted(tx);
        assertTrue(barrier.isCommitted());
    }

    @Test
    public void whenOpenAndLastTransaction_thenAllTransactionsCommitted() {
        barrier = new CountDownCommitBarrier(3);
        AwaitThread t1 = new AwaitThread();
        AwaitThread t2 = new AwaitThread();

        startAll(t1, t2);
        sleepMs(500);

        assertAlive(t1, t2);
        assertTrue(barrier.isClosed());

        Txn tx = stm.newDefaultTxn();
        barrier.joinCommitUninterruptibly(tx);
        joinAll(t1, t2);

        assertIsCommitted(tx, t1.txn, t2.txn);
    }

    @Test
    public void whenOpenAndTransactionAborted_thenDeadTxnException() {
        Txn tx = stm.newDefaultTxn();
        tx.abort();

        barrier = new CountDownCommitBarrier(1);
        try {
            barrier.joinCommitUninterruptibly(tx);
            fail();
        } catch (DeadTxnException ex) {
        }

        assertTrue(barrier.isClosed());
        assertEquals(0, barrier.getNumberWaiting());
    }

    @Test
    public void whenOpenAndTransactionCommitted_thenDeadTxnException() {
        Txn tx = stm.newDefaultTxn();
        tx.commit();

        barrier = new CountDownCommitBarrier(1);
        try {
            barrier.joinCommitUninterruptibly(tx);
            fail();
        } catch (DeadTxnException ex) {
        }

        assertTrue(barrier.isClosed());
        assertEquals(0, barrier.getNumberWaiting());
    }

    @Test
    public void whenAborted_thenCommitBarrierOpenException() {
        barrier = new CountDownCommitBarrier(1);
        barrier.abort();

        Txn tx = stm.newDefaultTxn();

        try {
            barrier.joinCommitUninterruptibly(tx);
            fail();
        } catch (CommitBarrierOpenException expected) {
        }

        assertTrue(barrier.isAborted());
    }

    @Test
    public void whenCommitted_thenCommitBarrierOpenException() {
        barrier = new CountDownCommitBarrier(1);
        barrier.joinCommitUninterruptibly(stm.newDefaultTxn());

        Txn tx = stm.newDefaultTxn();
        try {
            barrier.joinCommitUninterruptibly(tx);
            fail();
        } catch (CommitBarrierOpenException expected) {
        }

        assertIsAborted(tx);
    }

    class AwaitThread extends TestThread {
        private Txn txn;

        @Override
        public void doRun() throws Exception {
            stm.getDefaultTxnExecutor().execute(new TxnVoidCallable() {
                @Override
                public void call(Txn txn) throws Exception {
                    AwaitThread.this.txn = txn;
                    barrier.joinCommitUninterruptibly(txn);
                }
            });
        }
    }
}
