/*
 * Written by Doug Lea with assistance from members of JCP JSR-166
 * Expert Group and released to the public domain, as explained at
 * http://creativecommons.org/publicdomain/zero/1.0/
 */

import java.util.*;

public class DenseMapMicroBenchmark {
    static final int ITERS_PER_TEST = 4;
    static final long NANOS_PER_JOB = 4L * 1000L*1000L*1000L; // 4 sec
    static final long NANOS_PER_WARMUP = 500L*1000L*1000L; // 0.5 sec
    // map operations per iteration -- change if hasher.work changed
    static final int OPS_PER_ITER = 4; // put + get + iter + remove

    static int SIZE = 50000; // may be replaced by program arg

    abstract static class Job {
        final String name;
        long nanos;
        int runs;
        public Job(String name) { this.name = name; }
        public String name() { return name; }
        public abstract void work() throws Throwable;
    }

    /**
     * Runs each job for at least NANOS_PER_JOB seconds.
     * Returns array of average times per job per run.
     */
    static void time0(long nanos, Job ... jobs) throws Throwable {
        for (int i = 0; i < jobs.length; i++) {
            Thread.sleep(50);
            long t0 = System.nanoTime();
            long t;
            int j = 0;
            do {
                j++;
                jobs[i].work();
            } while ((t = System.nanoTime() - t0) < nanos);
            jobs[i].nanos = t / j;
            jobs[i].runs = j;
        }
    }

    static void time(Job ... jobs) throws Throwable {
        time0(NANOS_PER_JOB, jobs);

        final String nameHeader = "Method";
        int nameWidth  = nameHeader.length();
        for (Job job : jobs)
            nameWidth = Math.max(nameWidth, job.name().length());

        final int itemsPerTest = SIZE * OPS_PER_ITER * ITERS_PER_TEST;
        final String timeHeader = "Nanos/item";
        int timeWidth  = timeHeader.length();
        final String ratioHeader = "Ratio";
        int ratioWidth = ratioHeader.length();
        String format = String.format("%%-%ds %%%dd %%.3f%%n",
                                      nameWidth, timeWidth);
        String headerFormat = String.format("%%-%ds %%-%ds %%-%ds%%n",
                                            nameWidth, timeWidth, ratioWidth);
        System.out.printf(headerFormat, "Method", "Nanos/item", "Ratio");

        // Print out absolute and relative times, calibrated against first job
        for (int i = 0; i < jobs.length; i++) {
            long time = jobs[i].nanos/itemsPerTest;
            double ratio = (double) jobs[i].nanos / (double) jobs[0].nanos;
            System.out.printf(format, jobs[i].name(), time, ratio);
        }
    }


    static Long[] toLongs(Integer[] ints) {
        Long[] longs = new Long[ints.length];
        for (int i = 0; i < ints.length; i++)
            longs[i] = ints[i].longValue();
        return longs;
    }

    static String[] toStrings(Integer[] ints) {
        String[] strings = new String[ints.length];
        for (int i = 0; i < ints.length; i++)
            strings[i] = ints[i].toString();
        //            strings[i] = String.valueOf(ints[i].doubleValue());
        return strings;
    }

    static Float[] toFloats(Integer[] ints) {
        Float[] floats = new Float[ints.length];
        for (int i = 0; i < ints.length; i++)
            floats[i] = ints[i].floatValue();
        return floats;
    }

    static Double[] toDoubles(Integer[] ints) {
        Double[] doubles = new Double[ints.length];
        for (int i = 0; i < ints.length; i++)
            doubles[i] = ints[i].doubleValue();
        return doubles;
    }


    static final class Hasher extends Job {
        final Object[] elts;
        final Class mapClass;
        volatile int matches;
        Hasher(String name, Object[] elts, Class mapClass) {
            super(name);
            this.elts = elts;
            this.mapClass = mapClass;
        }
        public void work() {
            Map m = null;
            try {
                m = (Map) mapClass.newInstance();
            } catch (Exception e) {
                throw new RuntimeException("Can't instantiate " + mapClass + ": " + e);
            }
            final int len = elts.length;
            for (int j = 0; j < ITERS_PER_TEST; j++) {
                for (Object x : elts) {
                    if (m.put(x, x) != null)
                        throw new Error();
                }
                if (m.size() != len)
                    throw new Error();
                int ng = 0;
                for (Object x : elts) {
                    if (m.get(x) == x)
                        ++ng;
                }
                matches += ng;
                for (Object e : m.keySet()) {
                    if (m.get(e) == e)
                        --ng;
                }
                if (ng != 0)
                    throw new Error();
                for (Object x : elts) {
                    if (m.remove(x) != x)
                        throw new Error();
                }
                if (!m.isEmpty())
                    throw new Error();
            }
            if (matches != len * ITERS_PER_TEST)
                throw new Error();
            matches = 0;
        }
    }

    public static void main(String[] args) throws Throwable {
        Class mc = java.util.HashMap.class;
        if (args.length > 0)
            mc = Class.forName(args[0]);
        if (args.length > 1)
            SIZE = Integer.parseInt(args[1]);

        System.out.print("Class " + mc.getName());
        System.out.print(" size " + SIZE);
        System.out.println();

        final Integer[] seq = new Integer[SIZE];
        for (int i = 0; i < SIZE; i++)
            seq[i] = new Integer(i);
        final Integer[] shf = seq.clone();
        Collections.shuffle(Arrays.asList(shf));
        List<Hasher> hashers = new ArrayList<Hasher>();
        hashers.add(new Hasher("Integer sequential", seq, mc));
        hashers.add(new Hasher("Integer shuffled", shf, mc));

        hashers.add(new Hasher("Long    sequential", toLongs(seq), mc));
        hashers.add(new Hasher("Long    shuffled", toLongs(shf), mc));

        hashers.add(new Hasher("Float   sequential", toFloats(seq), mc));
        hashers.add(new Hasher("Float   shuffled", toFloats(shf), mc));

        hashers.add(new Hasher("Double  sequential", toDoubles(seq), mc));
        hashers.add(new Hasher("Double  shuffled", toDoubles(shf), mc));

        hashers.add(new Hasher("String  sequential", toStrings(seq), mc));
        hashers.add(new Hasher("String  shuffled", toStrings(shf), mc));

        Hasher[] jobs = hashers.toArray(new Hasher[0]);
        System.out.print("warmup...");
        time0(NANOS_PER_WARMUP, jobs); // Warm up run
        time0(NANOS_PER_WARMUP, jobs); // Warm up run
        for (int i = 0; i < 2; i++) {
            System.gc();
            Thread.sleep(50);
            System.runFinalization();
            Thread.sleep(50);
        }
        System.out.println("starting");
        time(jobs);
    }

}
