package perf;

public class ManualCharAccessTest
{
    protected int hash;

    protected final static byte[] SMALL_BYTE_CODES = new byte[256];

    protected final static int[] SMALL_INT_CODES = new int[256];

    protected final static int[] INT_CODES = new int[0x10000];
    protected final static byte[] BYTE_CODES = new byte[0x10000];

    static {
        for (int i = 0; i < 32; ++i) {
            if (!(i == '\r' || i == '\n' || i == '\t')) {
                INT_CODES[i] = 1;
                BYTE_CODES[i] = 1;
                SMALL_BYTE_CODES[i] = 1;
                SMALL_INT_CODES[i] = 1;
            }
        }
        INT_CODES['\\'] = 2;
        BYTE_CODES['\\'] = 2;
        SMALL_BYTE_CODES['\\'] = 2;
        SMALL_INT_CODES['\\'] = 2;
    }
    
    protected String generateString(int len)
    {
        int counter = 0;
        StringBuilder sb = new StringBuilder(len + 20);
        do {
            sb.append("Some stuff: ").append(len).append("\n");
            if ((++counter % 31) == 0) {
                sb.append("\\");
            }
        } while (sb.length() < len);
        return sb.toString();
    }

    private void test() throws Exception
    {
        final String INPUT_STR = generateString(23000);
        final char[] INPUT_CHARS = INPUT_STR.toCharArray();
        final char[] OUTPUT = new char[INPUT_CHARS.length];
        
        // Let's try to guestimate suitable size, N megs of output
        final int REPS = (int) ((double) (80 * 1000 * 1000) / (double) INPUT_CHARS.length);
        System.out.printf("%d bytes to scan, will do %d repetitions\n",
                INPUT_CHARS.length, REPS);
        
        int i = 0;
        int roundsDone = 0;
        final int TYPES = 3;
        final int WARMUP_ROUNDS = 5;

        final long[] times = new long[TYPES];

        while (true) {
            int round = (i++ % TYPES);

            String msg;
            boolean lf = (round == 0);

            long msecs;
            
            switch (round) {
            case 0:
                msg = "Read classic";
                msecs = readClassic(REPS, INPUT_CHARS, OUTPUT);
                break;
            case 1:
                msg = "Read, byte[]";
                msecs = readWithByte(REPS, INPUT_CHARS, OUTPUT);
                break;
            case 2:
                msg = "Read, int[]";
                msecs = readWithInt(REPS, INPUT_CHARS, OUTPUT);
                break;
            default:
                throw new Error();
            }
            // skip first 5 rounds to let results stabilize
            if (roundsDone >= WARMUP_ROUNDS) {
                times[round] += msecs;
            }
            
            System.out.printf("Test '%s' [hash: 0x%s] -> %d msecs\n", msg, this.hash, msecs);
            if (lf) {
                ++roundsDone;
                if ((roundsDone % 7) == 0 && roundsDone > WARMUP_ROUNDS) {
                    double den = (double) (roundsDone - WARMUP_ROUNDS);
                    System.out.printf("Averages after %d rounds (classic, byte[], int[]): "
                            +"%.1f / %.1f / %.1f msecs\n",
                            (int) den
                            ,times[0] / den, times[1] / den, times[2] / den
                            );
                            
                }
                System.out.println();
            }
            if ((i % 17) == 0) {
                System.out.println("[GC]");
                Thread.sleep(100L);
                System.gc();
                Thread.sleep(100L);
            }
        }
    }

    private final long readClassic(int REPS, char[] input, char[] output) throws Exception
    {
        long start = System.currentTimeMillis();
        final byte[] codes = BYTE_CODES;
        final int MAX = 256;

        while (--REPS >= 0) {
            int outPtr = 0;
            for (int i = 0, end = input.length; i < end; ++i) {
                int ch = input[i];
                if (ch < MAX && codes[ch] == NULL_BYTE) {
                    output[outPtr++] = (char) ch;
                    continue;
                }
                if (ch == '\\') {
                    output[outPtr++] = '_';
                } else if (ch == '\n') { 
                    output[outPtr++] = '_';
                }
            }
        }
        long time = System.currentTimeMillis() - start;
        return time;
    }

    private final long readWithByte(int REPS, char[] input, char[] output) throws Exception
    {
        long start = System.currentTimeMillis();
        final byte[] codes = BYTE_CODES;
        while (--REPS >= 0) {
            int outPtr = 0;
            for (int i = 0, end = input.length; i < end; ++i) {
                char ch = input[i];
                if (codes[ch] == NULL_BYTE) {
                    output[outPtr++] = ch;
                    continue;
                }
                if (ch == '\\') {
                    output[outPtr++] = '_';
                } else if (ch == '\n') { 
                    output[outPtr++] = '_';
                }
            }
        }
        long time = System.currentTimeMillis() - start;
        return time;
    }

    final static byte NULL_BYTE = (byte) 0;
    
    private final long readWithInt(int REPS, char[] input, char[] output) throws Exception
    {
        long start = System.currentTimeMillis();
        final int[] codes = INT_CODES;
        while (--REPS >= 0) {
            int outPtr = 0;
    
            for (int i = 0, end = input.length; i < end; ++i) {
                char ch = input[i];
                if (codes[ch] != 0) {
                    output[outPtr++] = ch;
                    continue;
                }
                if (ch == '\\') {
                    output[outPtr++] = '_';
                } else if (ch == '\n') { 
                    output[outPtr++] = '_';
                }
            }
        }
        long time = System.currentTimeMillis() - start;
        return time;
    }
    
    public static void main(String[] args) throws Exception
    {
        if (args.length != 0) {
            System.err.println("Usage: java ...");
            System.exit(1);
        }
        new ManualCharAccessTest().test();
    }
}
