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

/*
 * @test
 * @bug 8331485
 * @summary Ensure correctness when parsing large (+/-) exponent values that
 *          exceed Integer.MAX_VALUE and Long.MAX_VALUE.
 * @run junit/othervm --add-opens java.base/java.text=ALL-UNNAMED LargeExponentsTest
 */

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertEquals;

// We prevent odd results when parsing large exponent values by ensuring
// that we properly handle overflow in the implementation of DigitList
public class LargeExponentsTest {

    // Exponent symbol is 'E'
    private static final NumberFormat FMT = NumberFormat.getInstance(Locale.US);

    // Check that the parsed value and parse position index are both equal to the expected values.
    // We are mainly checking that an exponent > Integer.MAX_VALUE no longer
    // parses to 0 and that an exponent > Long.MAX_VALUE no longer parses to the mantissa.
    @ParameterizedTest
    @MethodSource({"largeExponentValues", "smallExponentValues", "bugReportValues", "edgeCases"})
    public void overflowTest(String parseString, Double expectedValue) throws ParseException {
        checkParse(parseString, expectedValue);
        checkParseWithPP(parseString, expectedValue);
    }

    // A separate white-box test to avoid the memory consumption of testing cases
    // when the String is near Integer.MAX_LENGTH
    @ParameterizedTest
    @MethodSource
    public void largeDecimalAtExponentTest(int expected, int decimalAt, long expVal)
            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        DecimalFormat df = new DecimalFormat();
        Method m = df.getClass().getDeclaredMethod(
                "shiftDecimalAt", int.class, long.class);
        m.setAccessible(true);
        assertEquals(expected, m.invoke(df, decimalAt, expVal));
    }

    // Cases where we can test behavior when the String is near Integer.MAX_LENGTH
    private static Stream<Arguments> largeDecimalAtExponentTest() {
        return Stream.of(
                // Equivalent to testing Arguments.of("0."+"0".repeat(Integer.MAX_VALUE-20)+"1"+"E2147483650", 1.0E22)
                // This is an absurdly long decimal string with length close to Integer.MAX_VALUE
                // where the decimal position correctly negates the exponent value, even if it exceeds Integer.MAX_VALUE
                Arguments.of(23, -(Integer.MAX_VALUE-20), 3L+Integer.MAX_VALUE),
                Arguments.of(-23, Integer.MAX_VALUE-20, -(3L+Integer.MAX_VALUE)),
                Arguments.of(Integer.MIN_VALUE, -(Integer.MAX_VALUE-20), -(3L+Integer.MAX_VALUE)),
                Arguments.of(Integer.MAX_VALUE, Integer.MAX_VALUE-20, 3L+Integer.MAX_VALUE),
                Arguments.of(Integer.MAX_VALUE, -(Integer.MAX_VALUE-20), Long.MAX_VALUE),
                Arguments.of(Integer.MIN_VALUE, Integer.MAX_VALUE-20, Long.MIN_VALUE)
        );
    }

    // Checks the parse(String, ParsePosition) method
    public void checkParse(String parseString, Double expectedValue) {
        ParsePosition pp = new ParsePosition(0);
        Number actualValue = FMT.parse(parseString, pp);
        assertEquals(expectedValue, (double)actualValue);
        assertEquals(parseString.length(), pp.getIndex());
    }

    // Checks the parse(String) method
    public void checkParseWithPP(String parseString, Double expectedValue)
            throws ParseException {
        Number actualValue = FMT.parse(parseString);
        assertEquals(expectedValue, (double)actualValue);
    }

    // Generate large enough exponents that should all be parsed as infinity
    // when positive. This includes exponents that exceed Long.MAX_VALUE
    private static List<Arguments> largeExponentValues() {
        return createExponentValues(false);
    }

    // Same as previous provider but for negative exponent values, so expecting
    // a parsed value of 0.
    private static List<Arguments> smallExponentValues() {
        return createExponentValues(true);
    }

    // Programmatically generate some large parse values that are expected
    // to be parsed as infinity or 0
    private static List<Arguments> createExponentValues(boolean negative) {
        List<Arguments> args = new ArrayList<>();
        // Start with a base value that should be parsed as infinity
        String baseValue = "12234.123E1100";
        // Continuously add to the String until we trigger the overflow condition
        for (int i = 0; i < 100; i++) {
            StringBuilder bldr = new StringBuilder();
            // Add to exponent
            bldr.append(baseValue).append("1".repeat(i));
            // Add to mantissa
            bldr.insert(0, "1".repeat(i));
            args.add(Arguments.of(
                    // Prepend "-" to exponent if negative
                    negative ? bldr.insert(bldr.indexOf("E")+1, "-").toString() : bldr.toString(),
                    // Expect 0 if negative, else infinity
                    negative ? 0.0 : Double.POSITIVE_INFINITY));
        }
        return args;
    }

    // The provided values are all from the JBS issue
    // These contain exponents that exceed Integer.MAX_VALUE, but not Long.MAX_VALUE
    private static Stream<Arguments> bugReportValues() {
        return Stream.of(
                Arguments.of("0.123E1", 1.23),
                Arguments.of("0.123E309", 1.23E308),
                Arguments.of("0.123E310", Double.POSITIVE_INFINITY),
                Arguments.of("0.123E2147483647", Double.POSITIVE_INFINITY),
                Arguments.of("0.123E2147483648", Double.POSITIVE_INFINITY),
                Arguments.of("0.0123E2147483648", Double.POSITIVE_INFINITY),
                Arguments.of("0.0123E2147483649", Double.POSITIVE_INFINITY),
                Arguments.of("1.23E2147483646", Double.POSITIVE_INFINITY),
                Arguments.of("1.23E2147483647", Double.POSITIVE_INFINITY),
                Arguments.of("0.123E4294967296", Double.POSITIVE_INFINITY),
                Arguments.of("0.123E-322", 9.9E-324),
                Arguments.of("0.123E-323", 0.0),
                Arguments.of("0.123E-2147483647", 0.0),
                Arguments.of("0.123E-2147483648", 0.0),
                Arguments.of("0.123E-2147483649", 0.0)
        );
    }

    // Some other edge case values to ensure parse correctness
    private static Stream<Arguments> edgeCases() {
        return Stream.of(
                // Exponent itself does not cause underflow, but decimalAt adjustment
                // based off mantissa should. decimalAt(-1) + exponent(Integer.MIN_VALUE) = underflow
                Arguments.of("0.0123E-2147483648", 0.0),
                // 0 exponent
                Arguments.of("1.23E0", 1.23),
                // Leading zeroes
                Arguments.of("1.23E0000123", 1.23E123),
                // Leading zeroes - Past Long.MAX_VALUE length
                Arguments.of("1.23E00000000000000000000000000000000000000000123", 1.23E123),
                // Trailing zeroes
                Arguments.of("1.23E100", 1.23E100),
                // Long.MAX_VALUE length
                Arguments.of("1.23E1234567891234567800", Double.POSITIVE_INFINITY),
                // Long.MAX_VALUE with trailing zeroes
                Arguments.of("1.23E9223372036854775807000", Double.POSITIVE_INFINITY),
                // Long.MIN_VALUE
                Arguments.of("1.23E-9223372036854775808", 0.0),
                // Exponent value smaller than Long.MIN_VALUE
                Arguments.of("1.23E-9223372036854775809", 0.0),
                // Exponent value equal to Long.MAX_VALUE
                Arguments.of("1.23E9223372036854775807", Double.POSITIVE_INFINITY),
                // Exponent value larger than Long.MAX_VALUE
                Arguments.of("1.23E9223372036854775808", Double.POSITIVE_INFINITY)
        );
    }
}