/*
 * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
import java.util.Random;


public class BranchingBenchmarkV {
	private static final int STRING_LENGTH = 100 * 1000;
    private final static int ITER = 1000;

	// private final int perc = Integer.getInteger("perc", 0);

	private final String matching;
	private final String nonMatching;
	private char[] queries;

	private static final char[] TABLE = (""
			+ "\t\t\u0020\t\t\u1680\t\t"
			+ "\t\t\n\u000B\u3000\u000C\r\t"
			+ "\u0085\t\u2000\t\u2001\u2002\u2003\u2004"
			+ "\u2005\u2006\u205F\u2008\u2009\u200A\u2028\u2029").toCharArray();

	public BranchingBenchmarkV() {
		final StringBuilder matching = new StringBuilder();
		final StringBuilder nonMatching = new StringBuilder();
		for (char c=1; ++c!=256; ) {
			if (isBreakingWhitespace(c)) {
				matching.append(c);
			} else {
				nonMatching.append(c);
			}
		}
		this.matching = matching.toString();
		this.nonMatching = nonMatching.toString();
	}

    static int diff(char c) {
        return TABLE[(145538857 * c) >>> 27] - c;
    }

    static boolean isBreakingWhitespace(char c) {
        return diff(c) == 0;
    }

    static class Test {
      public int diff(char c) {
        return -1;
      }
      public int countBreakingWhitespace(char[] s) {
        return -1;
      }
    }
    static class Test0 extends Test {
      public int diff(char c) {
        return TABLE[(145538857 * c) >>> 27] - c;
      }
      public int countBreakingWhitespace(char[] s) {
        int result = s.length;
        for (final char c : s) {
            int x = diff(c);
            x |= -x; // Convert non-zero to negative.
            x >>= 31; // Convert negative to minus one.
            result += x;
        }
        return result;
      }
    }
    static class Test1 extends Test {
      public int diff(char c) {
        return TABLE[(145538857 * c) >>> 27] - c;
      }
      public int countBreakingWhitespace(char[] s) {
        int result = 0;
        for (final char c : s) {
            result += diff(c) == 0 ? 1 : 0;
        }
        return result;
      }
    }
    static class Test2 extends Test {
      public int diff(char c) {
        return TABLE[(145538857 * c) >>> 27] - c;
      }
      public int countBreakingWhitespace(char[] s) {
        int result = 0;
        for (final char c : s) {
            result += diff(c) == 0 ? 1 : 0;
        }
        return result;
      }
    }
    static class Test3 extends Test {
      public int diff(char c) {
        return TABLE[(145538857 * c) >>> 27] - c;
      }
      public int countBreakingWhitespace(char[] s) {
        int result = 0;
        for (final char c : s) {
            result += diff(c) == 0 ? 1 : 0;
        }
        return result;
      }
    }
    static class Test4 extends Test {
      public int diff(char c) {
        return TABLE[(145538857 * c) >>> 27] - c;
      }
      public int countBreakingWhitespace(char[] s) {
        int result = 0;
        for (final char c : s) {
            result += diff(c) == 0 ? 1 : 0;
        }
        return result;
      }
    }
    static class Test5 extends Test {
      public int diff(char c) {
        return TABLE[(145538857 * c) >>> 27] - c;
      }
      public int countBreakingWhitespace(char[] s) {
        int result = 0;
        for (final char c : s) {
            result += diff(c) == 0 ? 1 : 0;
        }
        return result;
      }
    }
    static class Test6 extends Test {
      public int diff(char c) {
        return TABLE[(145538857 * c) >>> 27] - c;
      }
      public int countBreakingWhitespace(char[] s) {
        int result = 0;
        for (final char c : s) {
            result += diff(c) == 0 ? 1 : 0;
        }
        return result;
      }
    }
    static class Test7 extends Test {
      public int diff(char c) {
        return TABLE[(145538857 * c) >>> 27] - c;
      }
      public int countBreakingWhitespace(char[] s) {
        int result = 0;
        for (final char c : s) {
            result += diff(c) == 0 ? 1 : 0;
        }
        return result;
      }
    }
    static class Test8 extends Test {
      public int diff(char c) {
        return TABLE[(145538857 * c) >>> 27] - c;
      }
      public int countBreakingWhitespace(char[] s) {
        int result = 0;
        for (final char c : s) {
            result += diff(c) == 0 ? 1 : 0;
        }
        return result;
      }
    }
    static class Test9 extends Test {
      public int diff(char c) {
        return TABLE[(145538857 * c) >>> 27] - c;
      }
      public int countBreakingWhitespace(char[] s) {
        int result = 0;
        for (final char c : s) {
            result += diff(c) == 0 ? 1 : 0;
        }
        return result;
      }
    }
    static class Test10 extends Test {
      public int diff(char c) {
        return TABLE[(145538857 * c) >>> 27] - c;
      }
      public int countBreakingWhitespace(char[] s) {
        int result = 0;
        for (final char c : s) {
            result += diff(c) == 0 ? 1 : 0;
        }
        return result;
      }
    }
    static class Test11 extends Test {
      public int diff(char c) {
        return TABLE[(145538857 * c) >>> 27] - c;
      }
      public int countBreakingWhitespace(char[] s) {
        int result = 0;
        for (final char c : s) {
            result += diff(c) == 0 ? 1 : 0;
        }
        return result;
      }
    }

    public void up(int perc) {
        final double fractionMatching = 0.01 * perc;
        final Random random = new Random();
        final StringBuilder sb = new StringBuilder();
        while (sb.length() < STRING_LENGTH) {
            final boolean shouldMatch = random.nextDouble() < fractionMatching;
            final String s = shouldMatch ? matching : nonMatching;
            sb.append(s.charAt(random.nextInt(s.length())));
        }
        queries = sb.toString().toCharArray();
    }

    private static int[] percentages = {-1, 5, 10, 15, 16, 17, 18, 19, 20, 30, 40, 50};
    private static double[] tmin  = new double[percentages.length];
    private static double[] tmax  = new double[percentages.length];
    
    private void test(Test t, int p) {
      final int perc = percentages[p];
      up(perc);
      // Warmup
      int result = 5 * ITER * t.countBreakingWhitespace(queries);
      int sum = 0;
      for (int i = 0; i < 5; i++) {
        long start = System.currentTimeMillis();
        for (int j = 0; j < ITER; j++) sum += t.countBreakingWhitespace(queries);
        long stop = System.currentTimeMillis();
        double time = ITER/(double)(stop - start);
        System.out.printf("# Warmup Iteration   %2d: %6.3f ops/ms\n", i, time);
      }
      if (result != sum) {
        System.out.println(result + " != " + sum);
        System.exit(1);
      }
      // Measure
      tmin[p] = 1000000.;
      tmax[p] = 0.;
      for (int i = 0; i < 5; i++) {
        long start = System.currentTimeMillis();
        for (int j = 0; j < ITER; j++) t.countBreakingWhitespace(queries);
        long stop = System.currentTimeMillis();
        double time = ITER/(double)(stop - start);
        System.out.printf("Iteration   %2d: %6.3f ops/ms\n", i, time);
        if (time < tmin[p]) tmin[p] = time;
        if (time > tmax[p]) tmax[p] = time;
      }
    }

	public static void main(String[] args) {
		// formatter.format("\n%10s: %9s %6s %6s %6s\n", "PERCENTAGE", "MEAN", "MIN", "MAX", "UNIT");
		BranchingBenchmarkV bb = new BranchingBenchmarkV();
        bb.up(50);
		Test t = new Test0();
		bb.test(t, 0);
		t = new Test1();
		bb.test(t, 1);
        t = new Test2();
        bb.test(t, 2);
        t = new Test3();
        bb.test(t, 3);
        t = new Test4();
        bb.test(t, 4);
        t = new Test5();
        bb.test(t, 5);
        t = new Test6();
        bb.test(t, 6);
        t = new Test7();
        bb.test(t, 7);
        t = new Test8();
        bb.test(t, 8);
        t = new Test9();
        bb.test(t, 9);
        t = new Test10();
        bb.test(t, 10);
        t = new Test11();
        bb.test(t, 11);

        System.out.printf("\n%10s: %9s %6s %6s %6s\n", "PERCENTAGE", "MEAN", "MIN", "MAX", "UNIT");
        for (int p = 0; p < percentages.length; p++) {
            final int perc = percentages[p];
            final Object name = perc < 0 ? "branchless" : perc;
            final double min = tmin[p];
            final double max = tmax[p];
            final double score = (max + min) / 2.;
            System.out.printf("%10s: %9.3f %6.3f %6.3f ops/ms\n", name, score, min, max);
        }		    
	}
}
