/*
 * 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.*;
import java.io.*;

public class CollectionWordLoops {

    static final String[] WORDS_FILES = {
        "kw.txt",
        "class.txt",
        //        "dir.txt",
        //        "ids.txt",
        //        "/usr/dict/words",
    };

    static final int MAX_WORDS = 500000;
    static final int pinsert   = 80;
    static final int premove   = 2;
    static final int NOPS      = 100000;
    static final int numTests  = 2;

    public static void main(String[] args) {
        Class collectionClass = null;
        try {
            collectionClass = Class.forName(args[0]);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("Class " + args[0] + " not found.");
        }

        System.out.println("Testing " + collectionClass.getName());

        for (int s = 0; s < WORDS_FILES.length; ++s)
            tests(collectionClass, numTests, s);

        for (int s = WORDS_FILES.length-1; s >= 0; --s)
            tests(collectionClass, numTests, s);

    }

    static void tests(Class collectionClass, int numTests, int sizeIndex) {
        try {
            String[] key = readWords(sizeIndex);
            int size = key.length;

            System.out.print("n = " +LoopHelpers.rightJustify(size) +" : ");
            long least = Long.MAX_VALUE;

            for (int i = 0; i < numTests; ++i) {
                Collection<String> m = newCollection(collectionClass);
                long t = doTest(collectionClass.getName(), m, key);
                if (t < least) least = t;
                m.clear();
                m = null;
            }

            long nano = Math.round(1000000.0 * (least) / NOPS);
            System.out.println(LoopHelpers.rightJustify(nano) + " ns per op");
        } catch (IOException ignore) {
            return; // skip test if can't read file
        }
    }


    static Collection<String> newCollection(Class cl) {
        try {
            Collection m = (Collection<String>) cl.newInstance();
            return m;
        } catch (Exception e) {
            throw new RuntimeException("Can't instantiate " + cl + ": " + e);
        }
    }

    static void pause() {
        try { Thread.sleep(100); }
        catch (InterruptedException ie) { return; }
    }

    static String[] readWords(int sizeIndex) throws IOException {
        String[] l = new String[MAX_WORDS];
        String[] array = null;
        try {
            FileReader fr = new FileReader(WORDS_FILES[sizeIndex]);
            BufferedReader reader = new BufferedReader(fr);
            int k = 0;
            for (;;) {
                String s = reader.readLine();
                if (s == null) break;
                l[k++] = s;
            }
            array = new String[k];
            for (int i = 0; i < k; ++i) {
                array[i] = l[i];
                l[i] = null;
            }
            l = null;
            reader.close();
        }
        catch (IOException ex) {
            System.out.println("Can't read words file:" + ex);
            throw ex;
        }
        return array;
    }

    static long doTest(String name,
                       final Collection<String> m,
                       final String[] key) {

        //    System.out.print(name + "\t");
        Runner runner = new Runner(m, key);
        long startTime = System.currentTimeMillis();
        runner.run();
        long afterRun = System.currentTimeMillis();
        long runTime =  (afterRun - startTime);
        int np = runner.total;
        if (runner.total == runner.hashCode())
            System.out.println("Useless Number" + runner.total);
        int sz = runner.maxsz;
        if (sz == runner.hashCode())
            System.out.println("Useless Number" + sz);
        //        System.out.print(" m = " + sz);
        return runTime;
    }


    static class Runner implements Runnable {
        final Collection<String> collection;
        final String[] key;
        LoopHelpers.SimpleRandom rng = new LoopHelpers.SimpleRandom();
        final int pctrem;
        final int pctins;
        int nputs = 0;
        int npgets = 0;
        int nagets = 0;
        int nremoves = 0;
        volatile int total;
        int maxsz;

        Runner(Collection<String> m, String[] k) {
            collection = m; key = k;
            pctrem = (int)(((long)premove * (long)(Integer.MAX_VALUE/2)) / 50);
            pctins = (int)(((long)pinsert * (long)(Integer.MAX_VALUE/2)) / 50);
        }


        int oneStep(int j) {
            int n = key.length;
            int r = rng.next() & 0x7FFFFFFF;
            int jinc = (r & 7);
            j += jinc - 3;
            if (j >= n) j -= n;
            if (j < 0) j += n;

            int l = n / 4 + j;
            if (l >= n) l -= n;

            String k = key[j];

            if (!collection.contains(k)) {
                ++nagets;
                if (r < pctins) {
                    collection.add(k);
                    ++nputs;
                    int csz = nputs - nremoves;
                    if (csz > maxsz) maxsz = csz;
                }
            }
            else {
                ++npgets;
                if (r < pctrem) {
                    collection.remove(k);
                    ++nremoves;
                    j += ((r >>> 8) & 7) +  n / 2;
                    if (j >= n) j -= n;
                }
            }
            return j;
        }

        public void run() {
            int j = key.length / 2;
            for (int i = 0; i < NOPS; ++i) {
                j = oneStep(j);
            }
            total = nputs + npgets + nagets + nremoves;
        }
    }

}
