Rng.java

package space.sunqian.common.base.random;

import space.sunqian.annotations.Nonnull;
import space.sunqian.common.Check;

import java.nio.ByteBuffer;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.DoubleSupplier;
import java.util.function.IntSupplier;
import java.util.function.LongSupplier;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;

/**
 * The Random Number Generator, base interface to produce random numbers. It extends the {@link IntSupplier},
 * {@link LongSupplier} and {@link DoubleSupplier}, to supply random {@code int}, {@code long} and {@code double}
 * values.
 *
 * @author sunqian
 */
public interface Rng extends IntSupplier, LongSupplier, DoubleSupplier {

    /**
     * Returns a {@link Rng} instance.
     *
     * @return a {@link Rng} instance
     */
    static @Nonnull Rng newRng() {
        Random random = new Random();
        // random.setSeed(System.nanoTime());
        return newRng(random);
    }

    /**
     * Returns a {@link Rng} instance with the specified seed.
     *
     * @param seed the specified seed
     * @return a {@link Rng} instance with the specified seed
     */
    static @Nonnull Rng newRng(long seed) {
        Random random = new Random(seed);
        // random.setSeed(seed);
        return newRng(random);
    }

    /**
     * Returns a {@link Rng} instance based on the specified {@link Random}.
     *
     * @param random the specified {@link Random}
     * @return a {@link Rng} instance based on the specified {@link Random}
     */
    static @Nonnull Rng newRng(@Nonnull Random random) {
        return RngService.INST.random(random);
    }

    /**
     * Returns a {@link Rng} instance based on the {@link ThreadLocalRandom}, and it is thread-safe and its
     * {@link #reset(long)} has no effect.
     *
     * @return a {@link Rng} instance based on the {@link ThreadLocalRandom}
     */
    static @Nonnull Rng threadLocal() {
        return RngService.INST.threadLocalRandom();
    }

    /**
     * Returns a {@link Rng} instance based on the {@link SecureRandom} with the specified random algorithm. This method
     * is equivalent to:
     * <pre>{@code
     * try {
     *     SecureRandom secureRandom = SecureRandom.getInstance(algorithm);
     *     return Rng.newRng(secureRandom);
     * } catch (NoSuchAlgorithmException e) {
     *     throw new UnsupportedOperationException(e);
     * }
     * }</pre>
     *
     * @param algorithm the specified random algorithm
     * @return a {@link Rng} instance based on the {@link SecureRandom} with the specified random algorithm
     * @throws UnsupportedOperationException if the specified algorithm is unsupported
     * @see SecureRandom#getInstance(String)
     */
    static @Nonnull Rng secure(@Nonnull String algorithm) throws UnsupportedOperationException {
        try {
            SecureRandom secureRandom = SecureRandom.getInstance(algorithm);
            return newRng(secureRandom);
        } catch (NoSuchAlgorithmException e) {
            throw new UnsupportedOperationException(e);
        }
    }

    /**
     * Resets a random seed for this {@link Rng}.
     */
    default void reset() {
        reset(nextLong());
    }

    /**
     * Resets this {@link Rng} via the specified seed.
     *
     * @param seed the specified seed
     */
    void reset(long seed);

    /**
     * Resets this {@link Rng} via the specified seed.
     *
     * @param seed the specified seed
     */
    void reset(byte @Nonnull [] seed);

    /**
     * Returns the next random boolean value.
     *
     * @return the next random boolean value
     */
    default boolean nextBoolean() {
        return nextInt() < 0;
    }

    /**
     * Returns the next random int value.
     *
     * @return the next random int value
     */
    int nextInt();

    /**
     * Returns the next random int value in the range {@code [startInclusive, endExclusive)}. If
     * {@code startInclusive == endExclusive}, then {@code startInclusive} is returned.
     * <p>
     * Note that if this method needs to be invoked multiple times, it is recommended to use {@code ints} or
     * {@code intSupplier} to reduce overhead.
     *
     * @param startInclusive the start value inclusive
     * @param endExclusive   the end value exclusive
     * @return the next random int value in the range {@code [startInclusive, endExclusive)}
     * @throws IllegalArgumentException if {@code startInclusive > endExclusive}
     */
    int nextInt(int startInclusive, int endExclusive) throws IllegalArgumentException;

    /**
     * Returns the next random long value.
     *
     * @return the next random long value
     */
    long nextLong();

    /**
     * Returns the next random long value in the range {@code [startInclusive, endExclusive)}. If
     * {@code startInclusive == endExclusive}, then {@code startInclusive} is returned.
     * <p>
     * Note that if this method needs to be invoked multiple times, it is recommended to use {@code longs} or
     * {@code longSupplier} to reduce overhead.
     *
     * @param startInclusive the start value inclusive
     * @param endExclusive   the end value exclusive
     * @return the next random long value in the range {@code [startInclusive, endExclusive)}
     * @throws IllegalArgumentException if {@code startInclusive > endExclusive}
     */
    long nextLong(long startInclusive, long endExclusive) throws IllegalArgumentException;

    /**
     * Returns the next random float value in the range {@code [0.0, 1.0)}.
     *
     * @return the next random float value in the range {@code [0.0, 1.0)}
     */
    float nextFloat();

    /**
     * Returns the next random float value in the range {@code [startInclusive, endExclusive)}. If
     * {@code startInclusive == endExclusive}, then {@code startInclusive} is returned.
     * <p>
     * Note that if this method needs to be invoked multiple times, it is recommended to use {@code doubles} or
     * {@code doubleSupplier} to reduce overhead.
     *
     * @param startInclusive the start value inclusive
     * @param endExclusive   the end value exclusive
     * @return the next random float value in the range {@code [startInclusive, endExclusive)}
     * @throws IllegalArgumentException if {@code startInclusive > endExclusive}
     */
    float nextFloat(float startInclusive, float endExclusive) throws IllegalArgumentException;

    /**
     * Returns the next random double value in the range {@code [0.0, 1.0)}.
     *
     * @return the next random double value in the range {@code [0.0, 1.0)}
     */
    double nextDouble();

    /**
     * Returns the next random double value in the range {@code [startInclusive, endExclusive)}. If
     * {@code startInclusive == endExclusive}, then {@code startInclusive} is returned.
     * <p>
     * Note that if this method needs to be invoked multiple times, it is recommended to use {@code doubles} or
     * {@code doubleSupplier} to reduce overhead.
     *
     * @param startInclusive the start value inclusive
     * @param endExclusive   the end value exclusive
     * @return the next random double value in the range {@code [startInclusive, endExclusive)}
     * @throws IllegalArgumentException if {@code startInclusive > endExclusive}
     */
    double nextDouble(double startInclusive, double endExclusive) throws IllegalArgumentException;

    /**
     * Returns a new random byte array of the specified length.
     *
     * @param length the specified length of the array
     * @return a new random byte array of the specified length
     * @throws NegativeArraySizeException if {@code length < 0}
     */
    default byte @Nonnull [] nextBytes(int length) throws NegativeArraySizeException {
        byte[] bytes = new byte[length];
        nextBytes(bytes, 0, length);
        return bytes;
    }

    /**
     * Fills the specified byte array with random bytes.
     *
     * @param bytes the specified byte array
     */
    default void nextBytes(byte @Nonnull [] bytes) {
        nextBytes(bytes, 0, bytes.length);
    }

    /**
     * Fills the specified byte array with {@code len} random bytes, starting at the specified offset.
     *
     * @param bytes the specified byte array
     * @param off   the specified offset
     * @param len   the number of bytes to fill
     * @throws IndexOutOfBoundsException if {@code off < 0} or {@code len < 0} or {@code off + len > bytes.length}
     */
    default void nextBytes(byte @Nonnull [] bytes, int off, int len) throws IndexOutOfBoundsException {
        Check.checkOffLen(off, len, bytes.length);
        int i = off;
        int end = off + len;
        for (int words = len >> 3; words-- > 0; ) {
            long rnd = nextLong();
            for (int n = 8; n-- > 0; rnd >>>= Byte.SIZE) {
                bytes[i++] = (byte) rnd;
            }
        }
        if (i < end) {
            for (long rnd = nextLong(); i < end; rnd >>>= Byte.SIZE) {
                bytes[i++] = (byte) rnd;
            }
        }
    }

    /**
     * Fills the specified byte buffer with random bytes. The buffer's position increments by the actual filled number.
     *
     * @param bytes the specified byte buffer
     */
    default void nextBytes(@Nonnull ByteBuffer bytes) {
        int remaining = bytes.remaining();
        if (bytes.hasArray()) {
            nextBytes(bytes.array(), bytes.arrayOffset() + bytes.position(), remaining);
            bytes.position(bytes.position() + remaining);
        } else {
            byte[] rd = new byte[remaining];
            nextBytes(rd);
            bytes.put(rd);
        }
    }

    /**
     * Returns a new unlimited {@link IntStream} that produces random int values.
     *
     * @return a new unlimited {@link IntStream} that produces random int values
     */
    default @Nonnull IntStream ints() {
        return IntStream.generate(this::nextInt);
    }

    /**
     * Returns a new unlimited {@link IntStream} that produces random int values in the range
     * {@code [startInclusive, endExclusive)}. If {@code startInclusive == endExclusive}, then {@code startInclusive} is
     * always produced.
     *
     * @param startInclusive the start value inclusive
     * @param endExclusive   the end value exclusive
     * @return a new unlimited {@link IntStream} that produces random int values in the range
     * {@code [startInclusive, endExclusive)}
     * @throws IllegalArgumentException if {@code startInclusive > endExclusive}
     */
    default @Nonnull IntStream ints(int startInclusive, int endExclusive) throws IllegalArgumentException {
        IntSupplier supplier = intSupplier(startInclusive, endExclusive);
        return IntStream.generate(supplier);
    }

    /**
     * Returns a new {@link IntStream} that produces random int values, and the stream size is limited by the given
     * size.
     *
     * @param size the given stream size
     * @return a new {@link IntStream} that produces random int values, and the stream size is limited by the given size
     * @throws IllegalArgumentException if {@code size < 0}
     */
    default @Nonnull IntStream ints(long size) throws IllegalArgumentException {
        return ints().limit(size);
    }

    /**
     * Returns a new {@link IntStream} that produces random int values in the range
     * {@code [startInclusive, endExclusive)}, and the stream size is limited by the given size. If
     * {@code startInclusive == endExclusive}, then {@code startInclusive} is always produced.
     *
     * @param size           the given stream size
     * @param startInclusive the start value inclusive
     * @param endExclusive   the end value exclusive
     * @return a new {@link IntStream} that produces random int values in the range
     * {@code [startInclusive, endExclusive)}, and the stream size is limited by the given size
     * @throws IllegalArgumentException if {@code size < 0} or {@code startInclusive > endExclusive}
     */
    default @Nonnull IntStream ints(long size, int startInclusive, int endExclusive) throws IllegalArgumentException {
        return ints(startInclusive, endExclusive).limit(size);
    }

    /**
     * Returns a new {@link IntSupplier} that produces random int values.
     *
     * @return a new {@link IntSupplier} that produces random int values
     */
    default @Nonnull IntSupplier intSupplier() {
        return this::nextInt;
    }

    /**
     * Returns a new {@link IntSupplier} that produces random {@code int} value in the range:
     * {@code startInclusive <= value < endExclusive}. If {@code startInclusive == endExclusive}, then
     * {@code startInclusive} is always produced.
     *
     * @param startInclusive the start value inclusive
     * @param endExclusive   the end value exclusive
     * @return a new {@link IntSupplier} that produces random {@code int} value in the range:
     * {@code startInclusive <= value < endExclusive}
     * @throws IllegalArgumentException if {@code startInclusive > endExclusive}
     */
    @Nonnull
    IntSupplier intSupplier(int startInclusive, int endExclusive) throws IllegalArgumentException;

    /**
     * Returns a new unlimited {@link LongStream} that produces random long values.
     *
     * @return a new unlimited {@link LongStream} that produces random long values
     */
    default @Nonnull LongStream longs() {
        return LongStream.generate(this::nextLong);
    }

    /**
     * Returns a new unlimited {@link LongStream} that produces random long values in the range
     * {@code [startInclusive, endExclusive)}. If {@code startInclusive == endExclusive}, then {@code startInclusive} is
     * always produced.
     *
     * @param startInclusive the start value inclusive
     * @param endExclusive   the end value exclusive
     * @return a new unlimited {@link LongStream} that produces random long values in the range
     * {@code [startInclusive, endExclusive)}
     * @throws IllegalArgumentException if {@code startInclusive > endExclusive}
     */
    default @Nonnull LongStream longs(long startInclusive, long endExclusive) throws IllegalArgumentException {
        LongSupplier supplier = longSupplier(startInclusive, endExclusive);
        return LongStream.generate(supplier);
    }

    /**
     * Returns a new {@link LongStream} that produces random long values, and the stream size is limited by the given
     * size.
     *
     * @param size the given stream size
     * @return a new {@link LongStream} that produces random long values, and the stream size is limited by the given
     * size
     * @throws IllegalArgumentException if {@code size < 0}
     */
    default @Nonnull LongStream longs(long size) throws IllegalArgumentException {
        return longs().limit(size);
    }

    /**
     * Returns a new {@link LongStream} that produces random long values in the range
     * {@code [startInclusive, endExclusive)}, and the stream size is limited by the given size. If
     * {@code startInclusive == endExclusive}, then {@code startInclusive} is always produced.
     *
     * @param size           the given stream size
     * @param startInclusive the start value inclusive
     * @param endExclusive   the end value exclusive
     * @return a new {@link LongStream} that produces random long values in the range
     * {@code [startInclusive, endExclusive)}, and the stream size is limited by the given size
     * @throws IllegalArgumentException if {@code size < 0} or {@code startInclusive > endExclusive}
     */
    default @Nonnull LongStream longs(long size, long startInclusive, long endExclusive) throws IllegalArgumentException {
        return longs(startInclusive, endExclusive).limit(size);
    }

    /**
     * Returns a new {@link LongSupplier} that produces random long values.
     *
     * @return a new {@link LongSupplier} that produces random long values
     */
    default @Nonnull LongSupplier longSupplier() {
        return this::nextLong;
    }

    /**
     * Returns a new {@link LongSupplier} that produces random {@code long} value in the range:
     * {@code startInclusive <= value < endExclusive}. If {@code startInclusive == endExclusive}, then
     * {@code startInclusive} is always produced.
     *
     * @param startInclusive the start value inclusive
     * @param endExclusive   the end value exclusive
     * @return a new {@link LongSupplier} that produces random {@code long} value in the range:
     * {@code startInclusive <= value < endExclusive}
     * @throws IllegalArgumentException if {@code startInclusive > endExclusive}
     */
    LongSupplier longSupplier(long startInclusive, long endExclusive) throws IllegalArgumentException;

    /**
     * Returns a new unlimited {@link DoubleStream} that produces random double values in the range {@code [0.0, 1.0)}.
     *
     * @return a new unlimited {@link DoubleStream} that produces random double values in the range {@code [0.0, 1.0)}
     */
    default @Nonnull DoubleStream doubles() {
        return DoubleStream.generate(this::nextDouble);
    }

    /**
     * Returns a new unlimited {@link DoubleStream} that produces random double values in the range
     * {@code [startInclusive, endExclusive)}. If {@code startInclusive == endExclusive}, then {@code startInclusive} is
     * always produced.
     *
     * @param startInclusive the start value inclusive
     * @param endExclusive   the end value exclusive
     * @return a new unlimited {@link DoubleStream} that produces random double values in the range
     * {@code [startInclusive, endExclusive)}
     * @throws IllegalArgumentException if {@code startInclusive > endExclusive}
     */
    default @Nonnull DoubleStream doubles(double startInclusive, double endExclusive) throws IllegalArgumentException {
        DoubleSupplier supplier = doubleSupplier(startInclusive, endExclusive);
        return DoubleStream.generate(supplier);
    }

    /**
     * Returns a new {@link DoubleStream} that produces random double values in the range {@code [0.0, 1.0)}, and the
     * stream size is limited by the given size.
     *
     * @param size the given stream size
     * @return a new {@link DoubleStream} that produces random double values in the range {@code [0.0, 1.0)}, and the
     * stream size is limited by the given size
     * @throws IllegalArgumentException if {@code size < 0}
     */
    default @Nonnull DoubleStream doubles(long size) throws IllegalArgumentException {
        return doubles().limit(size);
    }

    /**
     * Returns a new {@link DoubleStream} that produces random double values in the range
     * {@code [startInclusive, endExclusive)}, and the stream size is limited by the given size. If
     * {@code startInclusive == endExclusive}, then {@code startInclusive} is always produced.
     *
     * @param size           the given stream size
     * @param startInclusive the start value inclusive
     * @param endExclusive   the end value exclusive
     * @return a new {@link DoubleStream} that produces random double values in the range
     * {@code [startInclusive, endExclusive)}, and the stream size is limited by the given size
     * @throws IllegalArgumentException if {@code size < 0} or {@code startInclusive > endExclusive}
     */
    default @Nonnull DoubleStream doubles(long size, double startInclusive, double endExclusive) throws IllegalArgumentException {
        return doubles(startInclusive, endExclusive).limit(size);
    }

    /**
     * Returns a new {@link DoubleSupplier} that produces random double values in the range {@code [0.0, 1.0)}.
     *
     * @return a new {@link DoubleSupplier} that produces random double values in the range {@code [0.0, 1.0)}
     */
    default @Nonnull DoubleSupplier doubleSupplier() {
        return this::nextDouble;
    }

    /**
     * Returns a new {@link DoubleSupplier} that produces random {@code double} value in the range:
     * {@code startInclusive <= value < endExclusive}. If {@code startInclusive == endExclusive}, then
     * {@code startInclusive} is always produced.
     *
     * @param startInclusive the start value inclusive
     * @param endExclusive   the end value exclusive
     * @return a new {@link DoubleSupplier} that produces random {@code double} value in the range:
     * {@code startInclusive <= value < endExclusive}
     * @throws IllegalArgumentException if {@code startInclusive > endExclusive}
     */
    @Nonnull
    DoubleSupplier doubleSupplier(double startInclusive, double endExclusive) throws IllegalArgumentException;

    /**
     * Returns the next random int value.
     *
     * @return the next random int value
     */
    @Override
    default int getAsInt() {
        return nextInt();
    }

    /**
     * Returns the next random long value.
     *
     * @return the next random long value
     */
    @Override
    default long getAsLong() {
        return nextLong();
    }

    /**
     * Returns the next random double value in the range {@code [0.0, 1.0)}.
     *
     * @return the next random double value in the range {@code [0.0, 1.0)}
     */
    @Override
    default double getAsDouble() {
        return nextDouble();
    }
}