/**
 * References:
 * <dl>
 *     <dt>This class has been derived from "FastDoubleParser".</dt>
 *     <dd>Copyright (c) Werner Randelshofer. Apache 2.0 License.
 *         <a href="https://github.com/wrandelshofer/FastDoubleParser">github.com</a>.</dd>
 * </dl>
 */

package com.fasterxml.jackson.core.io.doubleparser;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;

import java.util.Random;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import static org.junit.jupiter.api.DynamicTest.dynamicTest;

/**
 * The purpose of the tests in this class is to discover new cases, where
 * {@link FastDoubleParser#parseDouble(CharSequence)} does not
 * produce the same result like {@link Double#parseDouble(String)}.
 * <p>
 * Unfortunately, the space of input values is huge, it includes
 * all character sequences from lengths in the range
 * [0,{@value Integer#MAX_VALUE}].
 * <p>
 * Each run of this method produces new test cases with
 * input generated by applying the syntax rules for a double
 * value. The generation uses a random number generator.
 * <p>
 * The tests in this class are disabled by default. Enable them for
 * discovering new test cases.
 */
abstract class AbstractLexicallyGeneratedTest {
    /**
     * Seed for random number generator.
     * Specify a literal number to obtain repeatable tests.
     * Specify System.nanoTime to explore the input space.
     * (Make sure to take a note of the seed value if
     * tests failed.)
     */
    public static final long SEED = System.nanoTime();

    @TestFactory
    @Disabled
    Stream<DynamicTest> dynamicTestsRandomStringFrom1SyntaxRuleWithoutWhitespace() {
        Random rng = new Random(SEED);
        LexicalGenerator gen = new LexicalGenerator(false, true);
        return IntStream.range(0, 100).mapToObj(i -> {
                    String str = gen.produceRandomInputStringFromLexicalRuleWithoutWhitespace(1, rng);
                    return dynamicTest(i + ": " + str,
                            () -> testAgainstJdk(str));
                }
        );
    }

    @TestFactory
    @Disabled
    Stream<DynamicTest> dynamicTestsRandomStringFrom2SyntaxRuleWithoutWhitespace() {
        Random rng = new Random(SEED);
        LexicalGenerator gen = new LexicalGenerator(false, true);
        return IntStream.range(0, 10_000).mapToObj(i -> {
                    String str = gen.produceRandomInputStringFromLexicalRuleWithoutWhitespace(2, rng);
                    return dynamicTest(i + ": " + str,
                            () -> testAgainstJdk(str));
                }
        );
    }

    @TestFactory
    @Disabled
    Stream<DynamicTest> dynamicTestsRandomStringFrom3SyntaxRuleWithoutWhitespace() {
        Random rng = new Random(SEED);
        LexicalGenerator gen = new LexicalGenerator(false, true);
        return IntStream.range(0, 10_000).mapToObj(i -> {
                    String str = gen.produceRandomInputStringFromLexicalRuleWithoutWhitespace(3, rng);
                    return dynamicTest(i + ": " + str,
                            () -> testAgainstJdk(str));
                }
        );
    }

    @TestFactory
    @Disabled
    Stream<DynamicTest> dynamicTestsRandomStringFrom4SyntaxRuleWithoutWhitespace() {
        Random rng = new Random(SEED);
        LexicalGenerator gen = new LexicalGenerator(false, true);
        return IntStream.range(0, 10_000).mapToObj(i -> {
                    String str = gen.produceRandomInputStringFromLexicalRuleWithoutWhitespace(4, rng);
                    return dynamicTest(i + ": " + str,
                            () -> testAgainstJdk(str));
                }
        );
    }

    @TestFactory
    @Disabled
    Stream<DynamicTest> dynamicTestsRandomStringFrom10SyntaxRuleWithoutWhitespace() {
        Random rng = new Random(SEED);
        LexicalGenerator gen = new LexicalGenerator(false, true);
        return IntStream.range(0, 10_000).mapToObj(i -> {
                    String str = gen.produceRandomInputStringFromLexicalRuleWithoutWhitespace(10, rng);
                    return dynamicTest(i + ": " + str,
                            () -> testAgainstJdk(str));
                }
        );
    }

    @TestFactory
    @Disabled
    Stream<DynamicTest> dynamicTestsRandomStringsOfIncreasingLengthWithWhitespace() {
        Random rng = new Random(SEED);
        LexicalGenerator gen = new LexicalGenerator(false, true);
        return IntStream.range(1, 100).mapToObj(i -> {
                    String str = gen.produceRandomInputStringFromLexicalRuleWithWhitespace(i, rng);
                    return dynamicTest(i + ": " + str,
                            () -> testAgainstJdk(str));
                }
        );
    }

    @TestFactory
    @Disabled
    Stream<DynamicNode> dynamicTestsAsciiCharacterInputsUpTo4Characters() {
        int maxLength = 4;
        Random rng = new Random();
        return IntStream.range(0, 10_000).mapToObj(i -> {
            char[] ch = new char[4];
            int n = rng.nextInt(maxLength) + 1;
            for (int j = 0; j < n; j++) {
                ch[j] = nextAsciiChar(rng);
            }
            StringBuilder str = new StringBuilder();
            StringBuilder title = new StringBuilder(Integer.toString(n));
            title.append(':');
            for (int j = 0; j < 4; j++) {
                char c = ch[j];
                if (c >= ' ') {
                    if (Character.isISOControl(c) || Character.isWhitespace(c)) {
                        title.append("&#x").append(Integer.toHexString(c)).append(';');
                        str.append(c);
                    } else {
                        title.append((char) c);
                        str.append(c);
                    }
                }
            }
            return dynamicTest(title.toString(), () -> testAgainstJdk(str.toString()));
        });
    }

    private static char nextAsciiChar(Random rng) {
        //U+0020 SPACE
        //U+007F DELETE
        return (char) (rng.nextInt(0x7f - 0x20) + 0x20);
    }

    /**
     * Given an input String {@code str},
     * tests if {@link FastDoubleParser#parseDouble(CharSequence)}
     * produces the same result like {@link Double#parseDouble(String)}.
     *
     * @param str the given input string
     */
    protected abstract void testAgainstJdk(String str);


}