package org.multiverse.stms.gamma.benchmarks;

import org.multiverse.TestThread;
import org.multiverse.stms.gamma.GammaConstants;
import org.multiverse.stms.gamma.GammaStm;
import org.multiverse.stms.gamma.transactionalobjects.GammaTxnLong;
import org.multiverse.stms.gamma.transactions.GammaTxnConfig;
import org.multiverse.stms.gamma.transactions.fat.FatFixedLengthGammaTxn;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

import static org.benchy.BenchyUtils.format;
import static org.multiverse.TestUtils.joinAll;
import static org.multiverse.TestUtils.startAll;
import static org.multiverse.stms.gamma.GammaTestUtils.makeReadBiased;

/**
 * @author Peter Veentjer
 */
public class UncontendedMultipleReadBenchmark implements GammaConstants {
    private GammaStm stm;
    private final long transactionCount = 100 * 1000 * 1000;


    public static void main(String[] args) {
        //should be a power of two.
        int refCount = Integer.parseInt(args[0]);
        UncontendedMultipleReadBenchmark test = new UncontendedMultipleReadBenchmark();

        test.start(refCount);
    }

    public void start(int refCount) {
        int[] processors = BenchmarkUtils.generateProcessorRange();

        System.out.printf("Multiverse> Uncontended multiple read transaction benchmark\n");
        System.out.printf("Multiverse> Running with the following processor range %s\n", Arrays.toString(processors));
        System.out.printf("Multiverse> Running with %s transactionalobjects per transaction\n", refCount);
        System.out.printf("Multiverse> %s Transactions per thread\n", format(transactionCount));
        Result[] result = new Result[processors.length];

        System.out.printf("Multiverse> Starting warmup run\n");
        test(1, 1);
        System.out.printf("Multiverse> Finished warmup run\n");

        long startNs = System.nanoTime();

        for (int k = 0; k < processors.length; k++) {
            int processorCount = processors[k];
            double performance = test(processorCount, refCount);
            result[k] = new Result(processorCount, performance);
        }

        long durationNs = System.nanoTime() - startNs;
        System.out.printf("Benchmark took %s seconds\n", TimeUnit.NANOSECONDS.toSeconds(durationNs));

        BenchmarkUtils.toGnuplot(result);
    }


    private double test(int threadCount, int refCount) {
        System.out.printf("Multiverse> Running with %s processors\n", threadCount);

        stm = new GammaStm();

        ReadThread[] threads = new ReadThread[threadCount];

        for (int k = 0; k < threads.length; k++) {
            threads[k] = new ReadThread(k, refCount);
        }

        startAll(threads);
        joinAll(threads);

        long totalDurationMs = 0;
        for (ReadThread t : threads) {
            totalDurationMs += t.durationMs;
        }

        double readsPerSecond = BenchmarkUtils.transactionsPerSecondPerThread(
                transactionCount * refCount, totalDurationMs, threadCount);

        System.out.printf("Multiverse> Performance %s reads/second with %s threads\n",
                format(readsPerSecond), threadCount);
        return readsPerSecond;
    }

    class ReadThread extends TestThread {
        private final int refCount;
        private long durationMs;

        public ReadThread(int id, int refCount) {
            super("ReadThread-" + id);
            setPriority(Thread.MAX_PRIORITY);
            this.refCount = refCount;
        }

        public void doRun() {
            switch (refCount) {
                case 1:
                    run1();
                    break;
                case 2:
                    run2();
                    break;
                case 4:
                    run4();
                    break;
                case 8:
                    run8();
                    break;
                case 16:
                    run16();
                    break;
                case 32:
                    run32();
                    break;
                default:
                    throw new IllegalStateException();
            }
        }

        public void run1() {
            GammaTxnLong ref1 = newReadBiasedLongRef(stm);

            GammaTxnConfig config = new GammaTxnConfig(stm, 1)
                    .setReadonly(true);
            FatFixedLengthGammaTxn tx = new FatFixedLengthGammaTxn(config);

            long startMs = System.currentTimeMillis();

            for (long iteration = 0; iteration < transactionCount; iteration++) {
                ref1.openForRead(tx, LOCKMODE_NONE);
                tx.commit();
                tx.hardReset();
            }

            durationMs = System.currentTimeMillis() - startMs;
            System.out.printf("Multiverse> %s is finished in %s ms\n", getName(), durationMs);
        }

        public void run2() {
            GammaTxnLong ref1 = newReadBiasedLongRef(stm);
            GammaTxnLong ref2 = newReadBiasedLongRef(stm);

            GammaTxnConfig config = new GammaTxnConfig(stm, 2)
                    .setReadonly(true);
            FatFixedLengthGammaTxn tx = new FatFixedLengthGammaTxn(config);

            long startMs = System.currentTimeMillis();

            for (long iteration = 0; iteration < transactionCount; iteration++) {
                ref1.openForRead(tx, LOCKMODE_NONE);
                ref2.openForRead(tx, LOCKMODE_NONE);
                tx.commit();
                tx.hardReset();

            }

            durationMs = System.currentTimeMillis() - startMs;
            System.out.printf("Multiverse> %s is finished in %s ms\n", getName(), durationMs);
        }

        public void run4() {
            GammaTxnLong ref1 = newReadBiasedLongRef(stm);
            GammaTxnLong ref2 = newReadBiasedLongRef(stm);
            GammaTxnLong ref3 = newReadBiasedLongRef(stm);
            GammaTxnLong ref4 = newReadBiasedLongRef(stm);

            GammaTxnConfig config = new GammaTxnConfig(stm, 4)
                    .setReadonly(true);
            FatFixedLengthGammaTxn tx = new FatFixedLengthGammaTxn(config);

            long startMs = System.currentTimeMillis();

            for (long iteration = 0; iteration < transactionCount; iteration++) {
                ref1.openForRead(tx, LOCKMODE_NONE);
                ref2.openForRead(tx, LOCKMODE_NONE);
                ref3.openForRead(tx, LOCKMODE_NONE);
                ref4.openForRead(tx, LOCKMODE_NONE);
                tx.commit();
                tx.hardReset();
            }

            durationMs = System.currentTimeMillis() - startMs;
            System.out.printf("Multiverse> %s is finished in %s ms\n", getName(), durationMs);
        }

        public void run8() {
            GammaTxnLong ref1 = newReadBiasedLongRef(stm);
            GammaTxnLong ref2 = newReadBiasedLongRef(stm);
            GammaTxnLong ref3 = newReadBiasedLongRef(stm);
            GammaTxnLong ref4 = newReadBiasedLongRef(stm);
            GammaTxnLong ref5 = newReadBiasedLongRef(stm);
            GammaTxnLong ref6 = newReadBiasedLongRef(stm);
            GammaTxnLong ref7 = newReadBiasedLongRef(stm);
            GammaTxnLong ref8 = newReadBiasedLongRef(stm);

            GammaTxnConfig config = new GammaTxnConfig(stm, 8)
                    .setReadonly(true);
            FatFixedLengthGammaTxn tx = new FatFixedLengthGammaTxn(config);

            long startMs = System.currentTimeMillis();

            for (long iteration = 0; iteration < transactionCount; iteration++) {
                ref1.openForRead(tx, LOCKMODE_NONE);
                ref2.openForRead(tx, LOCKMODE_NONE);
                ref3.openForRead(tx, LOCKMODE_NONE);
                ref4.openForRead(tx, LOCKMODE_NONE);
                ref5.openForRead(tx, LOCKMODE_NONE);
                ref6.openForRead(tx, LOCKMODE_NONE);
                ref7.openForRead(tx, LOCKMODE_NONE);
                ref8.openForRead(tx, LOCKMODE_NONE);

                tx.commit();
                tx.hardReset();

            }

            durationMs = System.currentTimeMillis() - startMs;
            System.out.printf("Multiverse> %s is finished in %s ms\n", getName(), durationMs);
        }

        public void run16() {
            GammaTxnLong ref1 = newReadBiasedLongRef(stm);
            GammaTxnLong ref2 = newReadBiasedLongRef(stm);
            GammaTxnLong ref3 = newReadBiasedLongRef(stm);
            GammaTxnLong ref4 = newReadBiasedLongRef(stm);
            GammaTxnLong ref5 = newReadBiasedLongRef(stm);
            GammaTxnLong ref6 = newReadBiasedLongRef(stm);
            GammaTxnLong ref7 = newReadBiasedLongRef(stm);
            GammaTxnLong ref8 = newReadBiasedLongRef(stm);
            GammaTxnLong ref9 = newReadBiasedLongRef(stm);
            GammaTxnLong ref10 = newReadBiasedLongRef(stm);
            GammaTxnLong ref11 = newReadBiasedLongRef(stm);
            GammaTxnLong ref12 = newReadBiasedLongRef(stm);
            GammaTxnLong ref13 = newReadBiasedLongRef(stm);
            GammaTxnLong ref14 = newReadBiasedLongRef(stm);
            GammaTxnLong ref15 = newReadBiasedLongRef(stm);
            GammaTxnLong ref16 = newReadBiasedLongRef(stm);

            GammaTxnConfig config = new GammaTxnConfig(stm, 16)
                    .setReadonly(true);
            FatFixedLengthGammaTxn tx = new FatFixedLengthGammaTxn(config);

            long startMs = System.currentTimeMillis();

            for (long iteration = 0; iteration < transactionCount; iteration++) {
                ref1.openForRead(tx, LOCKMODE_NONE);
                ref2.openForRead(tx, LOCKMODE_NONE);
                ref3.openForRead(tx, LOCKMODE_NONE);
                ref4.openForRead(tx, LOCKMODE_NONE);
                ref5.openForRead(tx, LOCKMODE_NONE);
                ref6.openForRead(tx, LOCKMODE_NONE);
                ref7.openForRead(tx, LOCKMODE_NONE);
                ref8.openForRead(tx, LOCKMODE_NONE);
                ref9.openForRead(tx, LOCKMODE_NONE);
                ref10.openForRead(tx, LOCKMODE_NONE);
                ref11.openForRead(tx, LOCKMODE_NONE);
                ref12.openForRead(tx, LOCKMODE_NONE);
                ref13.openForRead(tx, LOCKMODE_NONE);
                ref14.openForRead(tx, LOCKMODE_NONE);
                ref15.openForRead(tx, LOCKMODE_NONE);
                ref15.openForRead(tx, LOCKMODE_NONE);
                ref16.openForRead(tx, LOCKMODE_NONE);

                tx.commit();
                tx.hardReset();

            }

            durationMs = System.currentTimeMillis() - startMs;
            System.out.printf("Multiverse> %s is finished in %s ms\n", getName(), durationMs);
        }

        public void run32() {
            GammaTxnLong ref1 = newReadBiasedLongRef(stm);
            GammaTxnLong ref2 = newReadBiasedLongRef(stm);
            GammaTxnLong ref3 = newReadBiasedLongRef(stm);
            GammaTxnLong ref4 = newReadBiasedLongRef(stm);
            GammaTxnLong ref5 = newReadBiasedLongRef(stm);
            GammaTxnLong ref6 = newReadBiasedLongRef(stm);
            GammaTxnLong ref7 = newReadBiasedLongRef(stm);
            GammaTxnLong ref8 = newReadBiasedLongRef(stm);
            GammaTxnLong ref9 = newReadBiasedLongRef(stm);
            GammaTxnLong ref10 = newReadBiasedLongRef(stm);
            GammaTxnLong ref11 = newReadBiasedLongRef(stm);
            GammaTxnLong ref12 = newReadBiasedLongRef(stm);
            GammaTxnLong ref13 = newReadBiasedLongRef(stm);
            GammaTxnLong ref14 = newReadBiasedLongRef(stm);
            GammaTxnLong ref15 = newReadBiasedLongRef(stm);
            GammaTxnLong ref16 = newReadBiasedLongRef(stm);
            GammaTxnLong ref17 = newReadBiasedLongRef(stm);
            GammaTxnLong ref18 = newReadBiasedLongRef(stm);
            GammaTxnLong ref19 = newReadBiasedLongRef(stm);
            GammaTxnLong ref20 = newReadBiasedLongRef(stm);
            GammaTxnLong ref21 = newReadBiasedLongRef(stm);
            GammaTxnLong ref22 = newReadBiasedLongRef(stm);
            GammaTxnLong ref23 = newReadBiasedLongRef(stm);
            GammaTxnLong ref24 = newReadBiasedLongRef(stm);
            GammaTxnLong ref25 = newReadBiasedLongRef(stm);
            GammaTxnLong ref26 = newReadBiasedLongRef(stm);
            GammaTxnLong ref27 = newReadBiasedLongRef(stm);
            GammaTxnLong ref28 = newReadBiasedLongRef(stm);
            GammaTxnLong ref29 = newReadBiasedLongRef(stm);
            GammaTxnLong ref30 = newReadBiasedLongRef(stm);
            GammaTxnLong ref31 = newReadBiasedLongRef(stm);
            GammaTxnLong ref32 = newReadBiasedLongRef(stm);


            GammaTxnConfig config = new GammaTxnConfig(stm, 32)
                    .setReadonly(true);
            FatFixedLengthGammaTxn tx = new FatFixedLengthGammaTxn(config);

            long startMs = System.currentTimeMillis();

            for (long iteration = 0; iteration < transactionCount; iteration++) {
                ref1.openForRead(tx, LOCKMODE_NONE);
                ref2.openForRead(tx, LOCKMODE_NONE);
                ref3.openForRead(tx, LOCKMODE_NONE);
                ref4.openForRead(tx, LOCKMODE_NONE);
                ref5.openForRead(tx, LOCKMODE_NONE);
                ref6.openForRead(tx, LOCKMODE_NONE);
                ref7.openForRead(tx, LOCKMODE_NONE);
                ref8.openForRead(tx, LOCKMODE_NONE);
                ref9.openForRead(tx, LOCKMODE_NONE);
                ref10.openForRead(tx, LOCKMODE_NONE);
                ref11.openForRead(tx, LOCKMODE_NONE);
                ref12.openForRead(tx, LOCKMODE_NONE);
                ref13.openForRead(tx, LOCKMODE_NONE);
                ref14.openForRead(tx, LOCKMODE_NONE);
                ref15.openForRead(tx, LOCKMODE_NONE);
                ref15.openForRead(tx, LOCKMODE_NONE);
                ref16.openForRead(tx, LOCKMODE_NONE);
                ref17.openForRead(tx, LOCKMODE_NONE);
                ref18.openForRead(tx, LOCKMODE_NONE);
                ref19.openForRead(tx, LOCKMODE_NONE);
                ref20.openForRead(tx, LOCKMODE_NONE);
                ref21.openForRead(tx, LOCKMODE_NONE);
                ref22.openForRead(tx, LOCKMODE_NONE);
                ref23.openForRead(tx, LOCKMODE_NONE);
                ref24.openForRead(tx, LOCKMODE_NONE);
                ref25.openForRead(tx, LOCKMODE_NONE);
                ref26.openForRead(tx, LOCKMODE_NONE);
                ref27.openForRead(tx, LOCKMODE_NONE);
                ref28.openForRead(tx, LOCKMODE_NONE);
                ref29.openForRead(tx, LOCKMODE_NONE);
                ref30.openForRead(tx, LOCKMODE_NONE);
                ref31.openForRead(tx, LOCKMODE_NONE);
                ref32.openForRead(tx, LOCKMODE_NONE);
                tx.commit();
                tx.hardReset();
            }

            durationMs = System.currentTimeMillis() - startMs;
            System.out.printf("Multiverse> %s is finished in %s ms\n", getName(), durationMs);
        }
    }

    private GammaTxnLong newReadBiasedLongRef(GammaStm stm) {
        return makeReadBiased(new GammaTxnLong(stm));
    }
}
