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

import org.junit.Before;
import org.junit.Test;
import org.multiverse.TestThread;
import org.multiverse.api.Txn;
import org.multiverse.api.TxnExecutor;
import org.multiverse.api.callables.TxnVoidCallable;
import org.multiverse.api.exceptions.DeadTxnException;
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.fail;
import static org.multiverse.TestUtils.*;
import static org.multiverse.api.GlobalStmInstance.getGlobalStmInstance;
import static org.multiverse.api.TxnThreadLocal.clearThreadLocalTxn;

/**
 * A Stresstest that check if the it is possible that a dirty read is done (this is not allowed).
 *
 * @author Peter Veentjer.
 */
public class ReadCommittedStressTest {
    private GammaTxnLong ref;

    private int readThreadCount = 10;
    private int modifyThreadCount = 2;

    private volatile boolean stop;
    private GammaStm stm;

    @Before
    public void setUp() {
        clearThreadLocalTxn();
        stm = (GammaStm) getGlobalStmInstance();
        ref = new GammaTxnLong(stm);
        stop = false;
    }

    @Test
    public void test() {
        FailingModifyThread[] modifyThreads = new FailingModifyThread[modifyThreadCount];
        for (int k = 0; k < modifyThreadCount; k++) {
            modifyThreads[k] = new FailingModifyThread(k);
        }

        ReadThread[] readerThread = new ReadThread[readThreadCount];
        for (int k = 0; k < readThreadCount; k++) {
            readerThread[k] = new ReadThread(k);
        }

        startAll(modifyThreads);
        startAll(readerThread);
        sleepMs(getStressTestDurationMs(30 * 1000));
        stop = true;
        joinAll(modifyThreads);
        joinAll(readerThread);
    }

    class FailingModifyThread extends TestThread {

        public FailingModifyThread(int threadId) {
            super("FailingModifyThread-" + threadId);
        }

        @Override
        public void doRun() {
            TxnExecutor executor = stm.getDefaultTxnExecutor();
            TxnVoidCallable callable = new TxnVoidCallable() {
                @Override
                public void call(Txn tx) throws Exception {
                    GammaTxn btx = (GammaTxn) tx;
                    ref.getAndSet(btx, ref.get(btx));
                    btx.abort();
                }
            };

            while (!stop) {
                try {
                    executor.execute(callable);
                    fail();
                } catch (DeadTxnException ignore) {
                }

                sleepRandomMs(10);
            }
        }
    }

    class ReadThread extends TestThread {


        public ReadThread(int threadId) {
            super("ReadThread-" + threadId);
        }

        @Override
        public void doRun() {
            TxnVoidCallable callable = new TxnVoidCallable() {
                @Override
                public void call(Txn tx) throws Exception {
                    GammaTxn btx = (GammaTxn) tx;

                    if (ref.get(btx) % 2 != 0) {
                        fail();
                    }
                }
            };

            TxnExecutor readonlyReadtrackingBlock = stm.newTxnFactoryBuilder()
                    .setReadonly(true)
                    .setReadTrackingEnabled(true)
                    .newTxnExecutor();

            TxnExecutor updateReadtrackingBlock = stm.newTxnFactoryBuilder()
                    .setReadonly(false)
                    .setReadTrackingEnabled(true)
                    .newTxnExecutor();

            int k = 0;
            while (!stop) {
                switch (k % 2) {
                    case 0:
                        readonlyReadtrackingBlock.execute(callable);
                        break;
                    case 1:
                    case 3:
                        updateReadtrackingBlock.execute(callable);
                        break;
                    default:
                        throw new IllegalStateException();
                }

                k++;
                sleepRandomMs(5);
            }
        }
    }
}
