/*
 * Copyright (c) 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.
 *
 * 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.
 */

public class Verify {
    public static String throwWord(boolean threw) {
        return (threw ? "threw" : "didn't throw");
    }

    public static void verifyResult(UnaryMethod method, int result1, int result2, boolean exception1, boolean exception2, int value) {
        if (exception1 != exception2) {
            throw new RuntimeException("Intrinsic version [" + method.name() + "]" + throwWord(exception1) + " exception, NonIntrinsic version" + throwWord(exception2) + " for: " + value);
        }
        if (result1 != result2) {
            throw new RuntimeException("Intrinsic version [" + method.name() + "] returned: " + result1 + " while NonIntrinsic version returned: " + result2);
        }
    }

    public static void verifyResult(UnaryLongMethod method, long result1, long result2, boolean exception1, boolean exception2, long value) {
        if (exception1 != exception2) {
            throw new RuntimeException("Intrinsic version [" + method.name() + "]" + throwWord(exception1) + " exception, NonIntrinsic version" + throwWord(exception2) + " for: " + value);
        }
        if (result1 != result2) {
            throw new RuntimeException("Intrinsic version [" + method.name() + "] returned: " + result1 + " while NonIntrinsic version returned: " + result2);
        }
    }

    private static void verifyResult(BinaryMethod method, int result1, int result2, boolean exception1, boolean exception2, int a, int b) {
        if (exception1 != exception2) {
            throw new RuntimeException("Intrinsic version [" + method.name() + "]" + throwWord(exception1) + " exception, NonIntrinsic version " + throwWord(exception2) + " for: " + a + " + " + b);
        }
        if (result1 != result2) {
            throw new RuntimeException("Intrinsic version [" + method.name() + "] returned: " + result1 + " while NonIntrinsic version returned: " + result2);
        }
    }

    private static void verifyResult(BinaryLongMethod method, long result1, long result2, boolean exception1, boolean exception2, long a, long b) {
        if (exception1 != exception2) {
            throw new RuntimeException("Intrinsic version [" + method.name() + "]" + throwWord(exception1) + " exception, NonIntrinsic version " + throwWord(exception2) + " for: " + a + " + " + b);
        }
        if (result1 != result2) {
            throw new RuntimeException("Intrinsic version [" + method.name() + "] returned: " + result1 + " while NonIntrinsic version returned: " + result2);
        }
    }


    public static void verifyUnary(int a, UnaryMethod method) {
        boolean exception1 = false, exception2 = false;
        int result1 = 0, result2 = 0;
        try {
            result1 = method.checkMethod(a);
        } catch (ArithmeticException e) {
            exception1 = true;
        }
        try {
            result2 = method.safeMethod(a);
        } catch (ArithmeticException e) {
            exception2 = true;
        }

        verifyResult(method, result1, result2, exception1, exception2, a);
    }

    public static void verifyUnary(long a, UnaryLongMethod method) {
        boolean exception1 = false, exception2 = false;
        long result1 = 0, result2 = 0;
        try {
            result1 = method.checkMethod(a);
        } catch (ArithmeticException e) {
            exception1 = true;
        }
        try {
            result2 = method.safeMethod(a);
        } catch (ArithmeticException e) {
            exception2 = true;
        }

        verifyResult(method, result1, result2, exception1, exception2, a);
    }


    public static void verifyBinary(int a, int b, BinaryMethod method) {
        boolean exception1 = false, exception2 = false;
        int result1 = 0, result2 = 0;
        try {
            result1 = method.checkMethod(a, b);
        } catch (ArithmeticException e) {
            exception1 = true;
        }
        try {
            result2 = method.safeMethod(a, b);
        } catch (ArithmeticException e) {
            exception2 = true;
        }

        verifyResult(method, result1, result2, exception1, exception2, a, b);
    }

    public static void verifyBinary(long a, long b, BinaryLongMethod method) {
        boolean exception1 = false, exception2 = false;
        long result1 = 0, result2 = 0;
        try {
            result1 = method.checkMethod(a, b);
        } catch (ArithmeticException e) {
            exception1 = true;
        }
        try {
            result2 = method.safeMethod(a, b);
        } catch (ArithmeticException e) {
            exception2 = true;
        }

        verifyResult(method, result1, result2, exception1, exception2, a, b);
    }


    public static class LoadTest {
        public static java.util.Random rnd = new java.util.Random();
        public static int[] values = new int[256];

        public static void init() {
            for (int i = 0; i < values.length; ++i) {
                values[i] = rnd.nextInt();
            }
        }

        public static void verify(BinaryMethod method) {
            for (int i = 0; i < 50000; ++i) {
                Verify.verifyBinary(values[i & 255], values[i & 255] - i, method);
                Verify.verifyBinary(values[i & 255] + i, values[i & 255] - i, method);
                Verify.verifyBinary(values[i & 255], values[i & 255], method);
                if ((i & 1) == 1 && i > 5) {
                    Verify.verifyBinary(values[i & 255] + i, values[i & 255] - i, method);
                } else {
                    Verify.verifyBinary(values[i & 255] - i, values[i & 255] + i, method);
                }
                Verify.verifyBinary(values[i & 255], values[(i + 1) & 255], method);
            }
        }
    }

    public static class NonConstantTest {
        public static java.util.Random rnd = new java.util.Random();
        public static int[] values = new int[] { Integer.MAX_VALUE, Integer.MIN_VALUE };

        public static void verify(BinaryMethod method) {
            for (int i = 0; i < 50000; ++i) {
                int rnd1 = rnd.nextInt(), rnd2 = rnd.nextInt();
                Verify.verifyBinary(rnd1, rnd2, method);
                Verify.verifyBinary(rnd1, rnd2 + 1, method);
                Verify.verifyBinary(rnd1 + 1, rnd2, method);
                Verify.verifyBinary(rnd1 - 1, rnd2, method);
                Verify.verifyBinary(rnd1, rnd2 - 1, method);
                Verify.verifyBinary(0, values[0], method);
                Verify.verifyBinary(values[0], 0, method);
                Verify.verifyBinary(0, values[1], method);
                Verify.verifyBinary(values[1], 0, method);
            }
        }
    }

    public static class NonConstantLongTest {
        public static long[] values = { Long.MIN_VALUE, Long.MAX_VALUE, 0, Long.MAX_VALUE - 1831 };
        public static java.util.Random rnd = new java.util.Random();

        public static void verify(BinaryLongMethod method) {
            for (int i = 0; i < 50000; ++i) {
                long rnd1 = rnd.nextLong(), rnd2 = rnd.nextLong();
                Verify.verifyBinary(rnd1, rnd2, method);
                Verify.verifyBinary(rnd1, rnd2 + 1, method);
                Verify.verifyBinary(rnd1 + 1, rnd2, method);
                Verify.verifyBinary(rnd1 - 1, rnd2, method);
                Verify.verifyBinary(rnd1, rnd2 - 1, method);
                Verify.verifyBinary(rnd1 + Long.MAX_VALUE - rnd2, rnd2 + 1, method);
                Verify.verifyBinary(values[0], values[2], method);
                Verify.verifyBinary(values[1], values[2], method);
                Verify.verifyBinary(values[3], 74L, method);
            }
        }
    }

    public static class LoopDependentTest {
        public static java.util.Random rnd = new java.util.Random();

        public static void verify(BinaryMethod method) {
            int rnd1 = rnd.nextInt(), rnd2 = rnd.nextInt();
            runTest(rnd1, rnd2, method);
        }

        private static void runTest(int rnd1, int rnd2, BinaryMethod method) {
            for (int i = 0; i < 50000; ++i) {
                Verify.verifyBinary(rnd1 + i, rnd2 + i, method);
                Verify.verifyBinary(rnd1 + i, rnd2 + (i & 0xff), method);
                Verify.verifyBinary(rnd1 - i, rnd2 - (i & 0xff), method);
                Verify.verifyBinary(rnd1 + i + 1, rnd2 + i + 2, method);
                Verify.verifyBinary(rnd1 + i * 2, rnd2 + i, method);
            }
        }
    }

    public static class ConstantTest {
        public static void verify(BinaryMethod method) {
            for (int i = 0; i < 50000; ++i) {
                Verify.verifyBinary(5, 7, method);
                Verify.verifyBinary(Integer.MAX_VALUE, 1, method);
                Verify.verifyBinary(Integer.MIN_VALUE, -1, method);
                Verify.verifyBinary(Integer.MAX_VALUE, -1, method);
                Verify.verifyBinary(Integer.MIN_VALUE, 1, method);
                Verify.verifyBinary(Integer.MAX_VALUE / 2, Integer.MAX_VALUE / 2, method);
                Verify.verifyBinary(Integer.MAX_VALUE / 2, (Integer.MAX_VALUE / 2) + 3, method);
                Verify.verifyBinary(Integer.MAX_VALUE, Integer.MIN_VALUE, method);
            }
        }
    }

    public static class ConstantLongTest {
        public static void verify(BinaryLongMethod method) {
            for (int i = 0; i < 50000; ++i) {
                Verify.verifyBinary(5, 7, method);
                Verify.verifyBinary(Long.MAX_VALUE, 1, method);
                Verify.verifyBinary(Long.MIN_VALUE, -1, method);
                Verify.verifyBinary(Long.MAX_VALUE, -1, method);
                Verify.verifyBinary(Long.MIN_VALUE, 1, method);
                Verify.verifyBinary(Long.MAX_VALUE / 2, Long.MAX_VALUE / 2, method);
                Verify.verifyBinary(Long.MAX_VALUE / 2, (Long.MAX_VALUE / 2) + 3, method);
                Verify.verifyBinary(Long.MAX_VALUE, Long.MIN_VALUE, method);
            }
        }
    }

    public static interface BinaryMethod {
        int safeMethod(int a, int b);
        int checkMethod(int a, int b);
        int unchecked(int a, int b);
        String name();
    }

    public static interface UnaryMethod {
        int safeMethod(int value);
        int checkMethod(int value);
        int unchecked(int value);
        String name();
    }

    public static interface BinaryLongMethod {
        long safeMethod(long a, long b);
        long checkMethod(long a, long b);
        long unchecked(long a, long b);
        String name();
    }

    public static interface UnaryLongMethod {
        long safeMethod(long value);
        long checkMethod(long value);
        long unchecked(long value);
        String name();
    }

    public static class UnaryToBinary implements BinaryMethod {
        private final UnaryMethod method;
        public UnaryToBinary(UnaryMethod method) {
            this.method = method;
        }

        @Override
        public int safeMethod(int a, int b) {
            return method.safeMethod(a);
        }

        @Override
        public int checkMethod(int a, int b) {
            return method.checkMethod(a);
        }

        @Override
        public int unchecked(int a, int b) {
            return method.unchecked(a);

        }

        @Override
        public String name() {
            return method.name();
        }
    }

    public static class UnaryToBinaryLong implements BinaryLongMethod {
        private final UnaryLongMethod method;
        public UnaryToBinaryLong(UnaryLongMethod method) {
            this.method = method;
        }

        @Override
        public long safeMethod(long a, long b) {
            return method.safeMethod(a);
        }

        @Override
        public long checkMethod(long a, long b) {
            return method.checkMethod(a);
        }

        @Override
        public long unchecked(long a, long b) {
            return method.unchecked(a);

        }

        @Override
        public String name() {
            return method.name();
        }
    }


    public static class AddExactI implements BinaryMethod {
        @Override
        public int safeMethod(int x, int y) {
            int r = x + y;
            // HD 2-12 Overflow iff both arguments have the opposite sign of the result
            if (((x ^ r) & (y ^ r)) < 0) {
                throw new ArithmeticException("integer overflow");
            }
            return r;

        }

        @Override
        public int checkMethod(int a, int b) {
            return Math.addExact(a, b);
        }

        @Override
        public String name() {
            return "addExact";
        }

        @Override
        public int unchecked(int a, int b) {
            return a + b;
        }
    }

    public static class AddExactL implements BinaryLongMethod {
        @Override
        public long safeMethod(long x, long y) {
            long r = x + y;
            // HD 2-12 Overflow iff both arguments have the opposite sign of the result
            if (((x ^ r) & (y ^ r)) < 0) {
                throw new ArithmeticException("integer overflow");
            }
            return r;

        }

        @Override
        public long checkMethod(long a, long b) {
            return Math.addExact(a, b);
        }

        @Override
        public String name() {
            return "addExactLong";
        }

        @Override
        public long unchecked(long a, long b) {
            return a + b;
        }
    }

    public static class MulExactI implements BinaryMethod {
        @Override
        public int safeMethod(int x, int y) {
            long r = (long)x * (long)y;
            if ((int)r != r) {
                throw new ArithmeticException("integer overflow");
            }
            return (int)r;

        }

        @Override
        public int checkMethod(int a, int b) {
            return Math.multiplyExact(a, b);
        }

        @Override
        public int unchecked(int a, int b) {
            return a * b;
        }

        @Override
        public String name() {
            return "multiplyExact";
        }
    }

    public static class MulExactL implements BinaryLongMethod {
        @Override
        public long safeMethod(long x, long y) {
            long r = x * y;
            long ax = Math.abs(x);
            long ay = Math.abs(y);
            if (((ax | ay) >>> 31 != 0)) {
                // Some bits greater than 2^31 that might cause overflow
                // Check the result using the divide operator
                // and check for the special case of Long.MIN_VALUE * -1
                if (((y != 0) && (r / y != x)) ||
                        (x == Long.MIN_VALUE && y == -1)) {
                    throw new ArithmeticException("long overflow");
                }
            }
            return r;
        }

        @Override
        public long checkMethod(long a, long b) {
            return Math.multiplyExact(a, b);
        }

        @Override
        public long unchecked(long a, long b) {
            return a * b;
        }

        @Override
        public String name() {
            return "multiplyExact";
        }
    }

    public static class NegExactL implements UnaryLongMethod {
        @Override
        public long safeMethod(long a) {
            if (a == Long.MIN_VALUE) {
                throw new ArithmeticException("long overflow");
            }

            return -a;

        }

        @Override
        public long checkMethod(long value) {
            return Math.negateExact(value);
        }

        @Override
        public long unchecked(long value) {
            return -value;
        }

        @Override
        public String name() {
            return "negateExactLong";
        }
    }

    public static class NegExactI implements UnaryMethod {
        @Override
        public int safeMethod(int a) {
            if (a == Integer.MIN_VALUE) {
                throw new ArithmeticException("integer overflow");
            }

            return -a;

        }

        @Override
        public int checkMethod(int value) {
            return Math.negateExact(value);
        }

        @Override
        public int unchecked(int value) {
            return -value;
        }

        @Override
        public String name() {
            return "negateExact";
        }
    }

    public static class SubExactI implements BinaryMethod {
        @Override
        public int safeMethod(int x, int y) {
            int r = x - y;
            // HD 2-12 Overflow iff the arguments have different signs and
            // the sign of the result is different than the sign of x
            if (((x ^ y) & (x ^ r)) < 0) {
                throw new ArithmeticException("integer overflow");
            }
            return r;
        }

        @Override
        public int checkMethod(int a, int b) {
            return Math.subtractExact(a, b);
        }

        @Override
        public int unchecked(int a, int b) {
            return a - b;
        }

        @Override
        public String name() {
            return "subtractExact";
        }
    }

    public static class SubExactL implements BinaryLongMethod {
        @Override
        public long safeMethod(long x, long y) {
            long r = x - y;
            // HD 2-12 Overflow iff the arguments have different signs and
            // the sign of the result is different than the sign of x
            if (((x ^ y) & (x ^ r)) < 0) {
                throw new ArithmeticException("integer overflow");
            }
            return r;
        }

        @Override
        public long checkMethod(long a, long b) {
            return Math.subtractExact(a, b);
        }

        @Override
        public long unchecked(long a, long b) {
            return a - b;
        }

        @Override
        public String name() {
            return "subtractExactLong";
        }
    }

    static class IncExactL implements UnaryLongMethod {
        @Override
        public long safeMethod(long a) {
            if (a == Long.MAX_VALUE) {
                throw new ArithmeticException("long overflow");
            }

            return a + 1L;

        }

        @Override
        public long checkMethod(long value) {
            return Math.incrementExact(value);
        }

        @Override
        public long unchecked(long value) {
            return value + 1;
        }

        @Override
        public String name() {
            return "incrementExactLong";
        }
    }

    static class IncExactI implements UnaryMethod {
        @Override
        public int safeMethod(int a) {
            if (a == Integer.MAX_VALUE) {
                throw new ArithmeticException("integer overflow");
            }

            return a + 1;
        }

        @Override
        public int checkMethod(int value) {
            return Math.incrementExact(value);
        }

        @Override
        public int unchecked(int value) {
            return value + 1;
        }

        @Override
        public String name() {
            return "incrementExact";
        }
    }

    static class DecExactL implements UnaryLongMethod {
        @Override
        public long safeMethod(long a) {
            if (a == Long.MIN_VALUE) {
                throw new ArithmeticException("long overflow");
            }

            return a - 1L;
        }

        @Override
        public long checkMethod(long value) {
            return Math.decrementExact(value);
        }

        @Override
        public long unchecked(long value) {
            return value - 1;
        }

        @Override
        public String name() {
            return "decExactLong";
        }
    }

    static class DecExactI implements UnaryMethod {
        @Override
        public int safeMethod(int a) {
            if (a == Integer.MIN_VALUE) {
                throw new ArithmeticException("integer overflow");
            }

            return a - 1;
        }

        @Override
        public int checkMethod(int value) {
            return Math.decrementExact(value);
        }

        @Override
        public int unchecked(int value) {
            return value - 1;
        }

        @Override
        public String name() {
            return "decrementExact";
        }
    }

}
