/*
 * Written by Bill Scherer and Doug Lea with assistance from members
 * of JCP JSR-166 Expert Group and released to the public domain. Use,
 * modify, and redistribute this code in any way without
 * acknowledgement.
 */

import edu.emory.mathcs.backport.java.util.concurrent.*;

import edu.emory.mathcs.backport.java.util.concurrent.atomic.*;
import edu.emory.mathcs.backport.java.util.concurrent.locks.*;
import edu.emory.mathcs.backport.java.util.concurrent.helpers.Utils;

public class ExchangeLoops {
    static final int NCPUS = Runtime.getRuntime().availableProcessors();

    static final int  DEFAULT_THREADS = NCPUS + 2;
    static final long DEFAULT_TRIAL_MILLIS   = 10000;

    public static void main(String[] args) throws Exception {
        int maxThreads = DEFAULT_THREADS;
        long trialMillis = DEFAULT_TRIAL_MILLIS;
        int nReps = 3;

        // Parse and check args
        int argc = 0;
        while (argc < args.length) {
            String option = args[argc++];
            if (option.equals("-t"))
                trialMillis = Integer.parseInt(args[argc]);
            else if (option.equals("-r"))
                nReps = Integer.parseInt(args[argc]);
            else
                maxThreads = Integer.parseInt(option);
            argc++;
        }

	// Display runtime parameters
	System.out.print("ExchangeTest");
	System.out.print(" -t " + trialMillis);
        System.out.print(" -r " + nReps);
	System.out.print(" max threads " + maxThreads);
	System.out.println();
        long warmupTime = 2000;
        long sleepTime = 100;
        int nw = maxThreads >= 3? 3 : 2;

        System.out.println("Warmups..");
        oneRun(3, warmupTime);
        Thread.sleep(sleepTime);

        for (int i = maxThreads; i >= 2; i -= 1) {
            oneRun(i, warmupTime++);
            //            System.gc();
            Thread.sleep(sleepTime);
        }

        /*
        for (int i = maxThreads; i >= 2; i -= 1) {
            oneRun(i, warmupTime++);
        }
        */

        for (int j = 0; j < nReps; ++j) {
            System.out.println("Trial: " + j);
            for (int i = 2; i <= maxThreads; i += 2) {
                oneRun(i, trialMillis);
                //                System.gc();
                Thread.sleep(sleepTime);
            }
            for (int i = maxThreads; i >= 2; i -= 2) {
                oneRun(i, trialMillis);
                //                System.gc();
                Thread.sleep(sleepTime);
            }
            Thread.sleep(sleepTime);
        }


    }

    static void oneRun(int nThreads, long trialMillis) throws Exception {
        System.out.println(nThreads + " threads");
        Exchanger x = new Exchanger();
        Runner[] runners = new Runner[nThreads];
        Thread[] threads = new Thread[nThreads];
        for (int i = 0; i < nThreads; ++i) {
            runners[i] = new Runner(x);
            threads[i] = new Thread(runners[i]);
            //            int h = System.identityHashCode(threads[i]);
            //            h ^= h << 1;
            //            h ^= h >>> 3;
            //            h ^= h << 10;
            //            System.out.printf("%10x\n", h);
        }

        long startTime = Utils.nanoTime();
        for (int i = 0; i < nThreads; ++i) {
            threads[i].start();
        }
        Thread.sleep(trialMillis);
        for (int i = 0; i < nThreads; ++i)
            threads[i].interrupt();
        long elapsed = Utils.nanoTime() - startTime;
        for (int i = 0; i < nThreads; ++i)
            threads[i].join();
        int iters = 1;
        //        System.out.println();
        for (int i = 0; i < nThreads; ++i) {
            int ipr = runners[i].iters;
            //            System.out.println(ipr);
            iters += ipr;
        }
        long rate = iters * 1000L * 1000L * 1000L / elapsed;
        long npt = elapsed / iters;
        System.out.println(elapsed / (1000L * 1000L) + "ms");
        System.out.println(rate + " it/s ");
        System.out.println(npt + " ns/it");
        System.out.println();
        //        x.printStats();
    }

    static final class Runner implements Runnable {
        final Exchanger exchanger;
        final Object mine = new Integer(2688);
        volatile int iters;
        Runner(Exchanger x) { this.exchanger = x; }

        public void run() {
            Exchanger x = exchanger;
            Object m = mine;
            int i = 0;
            try {
                for (;;) {
                    Object e = x.exchange(m);
                    if (e == null || e == m)
                        throw new Error();
                    m = e;
                    ++i;
                }
            } catch (InterruptedException ie) {
                iters = i;
            }
        }
    }
}

