/*
 * *****************************************************************************
 *
 * Parts of this code are adapted from the following:
 *
 * PCG, A Family of Better Random Number Generators.
 *
 * You can find the original source code at:
 *   https://github.com/imneme/pcg-c
 *
 * -----------------------------------------------------------------------------
 *
 * This code is under the following license:
 *
 * Copyright (c) 2014-2017 Melissa O'Neill and PCG Project contributors
 * Copyright (c) 2018-2021 Gavin D. Howard and contributors.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * *****************************************************************************
 *
 * Definitions for the RNG.
 *
 */

#ifndef BC_RAND_H
#define BC_RAND_H

#include <stdint.h>
#include <inttypes.h>

#include <vector.h>
#include <num.h>

#if BC_ENABLE_EXTRA_MATH

#if BC_ENABLE_LIBRARY
#define BC_RAND_USE_FREE (1)
#else // BC_ENABLE_LIBRARY
#ifndef NDEBUG
#define BC_RAND_USE_FREE (1)
#else // NDEBUG
#define BC_RAND_USE_FREE (0)
#endif // NDEBUG
#endif // BC_ENABLE_LIBRARY

/**
 * A function to return a random unsigned long.
 * @param ptr  A void ptr to some data that will help generate the random ulong.
 * @return     The random ulong.
 */
typedef ulong (*BcRandUlong)(void* ptr);

#if BC_LONG_BIT >= 64

// If longs are 64 bits, we have the option of 128-bit integers on some
// compilers. These two sections test that.
#ifdef BC_RAND_BUILTIN
#if BC_RAND_BUILTIN
#ifndef __SIZEOF_INT128__
#undef BC_RAND_BUILTIN
#define BC_RAND_BUILTIN (0)
#endif // __SIZEOF_INT128__
#endif // BC_RAND_BUILTIN
#endif // BC_RAND_BUILTIN

#ifndef BC_RAND_BUILTIN
#ifdef __SIZEOF_INT128__
#define BC_RAND_BUILTIN (1)
#else // __SIZEOF_INT128__
#define BC_RAND_BUILTIN (0)
#endif // __SIZEOF_INT128__
#endif // BC_RAND_BUILTIN

/// The type for random integers.
typedef uint64_t BcRand;

/// A constant defined by PCG.
#define BC_RAND_ROTC (63)

#if BC_RAND_BUILTIN

/// A typedef for the PCG state.
typedef __uint128_t BcRandState;

/**
 * Multiply two integers, worrying about overflow.
 * @param a  The first integer.
 * @param b  The second integer.
 * @return   The product of the PCG states.
 */
#define bc_rand_mul(a, b) (((BcRandState) (a)) * ((BcRandState) (b)))

/**
 * Add two integers, worrying about overflow.
 * @param a  The first integer.
 * @param b  The second integer.
 * @return   The sum of the PCG states.
 */
#define bc_rand_add(a, b) (((BcRandState) (a)) + ((BcRandState) (b)))

/**
 * Multiply two PCG states.
 * @param a  The first PCG state.
 * @param b  The second PCG state.
 * @return   The product of the PCG states.
 */
#define bc_rand_mul2(a, b) (((BcRandState) (a)) * ((BcRandState) (b)))

/**
 * Add two PCG states.
 * @param a  The first PCG state.
 * @param b  The second PCG state.
 * @return   The sum of the PCG states.
 */
#define bc_rand_add2(a, b) (((BcRandState) (a)) + ((BcRandState) (b)))

/**
 * Figure out if the PRNG has been modified. Since the increment of the PRNG has
 * to be odd, we use the extra bit to store whether it has been modified or not.
 * @param r  The PRNG.
 * @return   True if the PRNG has *not* been modified, false otherwise.
 */
#define BC_RAND_NOTMODIFIED(r) (((r)->inc & 1UL) == 0)

/**
 * Return true if the PRNG has not been seeded yet.
 * @param r  The PRNG.
 * @return   True if the PRNG has not been seeded yet, false otherwise.
 */
#define BC_RAND_ZERO(r) (!(r)->state)

/**
 * Returns a constant built from @a h and @a l.
 * @param h  The high 64 bits.
 * @param l  The low 64 bits.
 * @return   The constant built from @a h and @a l.
 */
#define BC_RAND_CONSTANT(h, l) ((((BcRandState) (h)) << 64) + (BcRandState) (l))

/**
 * Truncates a PCG state to the number of bits in a random integer.
 * @param s  The state to truncate.
 * @return   The truncated state.
 */
#define BC_RAND_TRUNC(s) ((uint64_t) (s))

/**
 * Chops a PCG state in half and returns the top bits.
 * @param s  The state to chop.
 * @return   The chopped state's top bits.
 */
#define BC_RAND_CHOP(s) ((uint64_t) ((s) >> 64UL))

/**
 * Rotates a PCG state.
 * @param s  The state to rotate.
 * @return   The rotated state.
 */
#define BC_RAND_ROTAMT(s) ((unsigned int) ((s) >> 122UL))

#else // BC_RAND_BUILTIN

/// A typedef for the PCG state.
typedef struct BcRandState
{
	/// The low bits.
	uint_fast64_t lo;

	/// The high bits.
	uint_fast64_t hi;

} BcRandState;

/**
 * Multiply two integers, worrying about overflow.
 * @param a  The first integer.
 * @param b  The second integer.
 * @return   The product of the PCG states.
 */
#define bc_rand_mul(a, b) (bc_rand_multiply((a), (b)))

/**
 * Add two integers, worrying about overflow.
 * @param a  The first integer.
 * @param b  The second integer.
 * @return   The sum of the PCG states.
 */
#define bc_rand_add(a, b) (bc_rand_addition((a), (b)))

/**
 * Multiply two PCG states.
 * @param a  The first PCG state.
 * @param b  The second PCG state.
 * @return   The product of the PCG states.
 */
#define bc_rand_mul2(a, b) (bc_rand_multiply2((a), (b)))

/**
 * Add two PCG states.
 * @param a  The first PCG state.
 * @param b  The second PCG state.
 * @return   The sum of the PCG states.
 */
#define bc_rand_add2(a, b) (bc_rand_addition2((a), (b)))

/**
 * Figure out if the PRNG has been modified. Since the increment of the PRNG has
 * to be odd, we use the extra bit to store whether it has been modified or not.
 * @param r  The PRNG.
 * @return   True if the PRNG has *not* been modified, false otherwise.
 */
#define BC_RAND_NOTMODIFIED(r) (((r)->inc.lo & 1) == 0)

/**
 * Return true if the PRNG has not been seeded yet.
 * @param r  The PRNG.
 * @return   True if the PRNG has not been seeded yet, false otherwise.
 */
#define BC_RAND_ZERO(r) (!(r)->state.lo && !(r)->state.hi)

/**
 * Returns a constant built from @a h and @a l.
 * @param h  The high 64 bits.
 * @param l  The low 64 bits.
 * @return   The constant built from @a h and @a l.
 */
#define BC_RAND_CONSTANT(h, l) \
	{                          \
		.lo = (l), .hi = (h)   \
	}

/**
 * Truncates a PCG state to the number of bits in a random integer.
 * @param s  The state to truncate.
 * @return   The truncated state.
 */
#define BC_RAND_TRUNC(s) ((s).lo)

/**
 * Chops a PCG state in half and returns the top bits.
 * @param s  The state to chop.
 * @return   The chopped state's top bits.
 */
#define BC_RAND_CHOP(s) ((s).hi)

/**
 * Returns the rotate amount for a PCG state.
 * @param s  The state to rotate.
 * @return   The semi-rotated state.
 */
#define BC_RAND_ROTAMT(s) ((unsigned int) ((s).hi >> 58UL))

/// A 64-bit integer with the bottom 32 bits set.
#define BC_RAND_BOTTOM32 (((uint_fast64_t) 0xffffffffULL))

/**
 * Returns the 32-bit truncated value of @a n.
 * @param n  The integer to truncate.
 * @return   The bottom 32 bits of @a n.
 */
#define BC_RAND_TRUNC32(n) ((n) & (BC_RAND_BOTTOM32))

/**
 * Returns the second 32 bits of @a n.
 * @param n  The integer to truncate.
 * @return   The second 32 bits of @a n.
 */
#define BC_RAND_CHOP32(n) ((n) >> 32)

#endif // BC_RAND_BUILTIN

/// A constant defined by PCG.
#define BC_RAND_MULTIPLIER \
	BC_RAND_CONSTANT(2549297995355413924ULL, 4865540595714422341ULL)

/**
 * Returns the result of a PCG fold.
 * @param s  The state to fold.
 * @return   The folded state.
 */
#define BC_RAND_FOLD(s) ((BcRand) (BC_RAND_CHOP(s) ^ BC_RAND_TRUNC(s)))

#else // BC_LONG_BIT >= 64

// If we are using 32-bit longs, we need to set these so.
#undef BC_RAND_BUILTIN
#define BC_RAND_BUILTIN (1)

/// The type for random integers.
typedef uint32_t BcRand;

/// A constant defined by PCG.
#define BC_RAND_ROTC (31)

/// A typedef for the PCG state.
typedef uint_fast64_t BcRandState;

/**
 * Multiply two integers, worrying about overflow.
 * @param a  The first integer.
 * @param b  The second integer.
 * @return   The product of the PCG states.
 */
#define bc_rand_mul(a, b) (((BcRandState) (a)) * ((BcRandState) (b)))

/**
 * Add two integers, worrying about overflow.
 * @param a  The first integer.
 * @param b  The second integer.
 * @return   The sum of the PCG states.
 */
#define bc_rand_add(a, b) (((BcRandState) (a)) + ((BcRandState) (b)))

/**
 * Multiply two PCG states.
 * @param a  The first PCG state.
 * @param b  The second PCG state.
 * @return   The product of the PCG states.
 */
#define bc_rand_mul2(a, b) (((BcRandState) (a)) * ((BcRandState) (b)))

/**
 * Add two PCG states.
 * @param a  The first PCG state.
 * @param b  The second PCG state.
 * @return   The sum of the PCG states.
 */
#define bc_rand_add2(a, b) (((BcRandState) (a)) + ((BcRandState) (b)))

/**
 * Figure out if the PRNG has been modified. Since the increment of the PRNG has
 * to be odd, we use the extra bit to store whether it has been modified or not.
 * @param r  The PRNG.
 * @return   True if the PRNG has *not* been modified, false otherwise.
 */
#define BC_RAND_NOTMODIFIED(r) (((r)->inc & 1UL) == 0)

/**
 * Return true if the PRNG has not been seeded yet.
 * @param r  The PRNG.
 * @return   True if the PRNG has not been seeded yet, false otherwise.
 */
#define BC_RAND_ZERO(r) (!(r)->state)

/**
 * Returns a constant built from a number.
 * @param n  The number.
 * @return   The constant built from @a n.
 */
#define BC_RAND_CONSTANT(n) UINT64_C(n)

/// A constant defined by PCG.
#define BC_RAND_MULTIPLIER BC_RAND_CONSTANT(6364136223846793005)

/**
 * Truncates a PCG state to the number of bits in a random integer.
 * @param s  The state to truncate.
 * @return   The truncated state.
 */
#define BC_RAND_TRUNC(s) ((uint32_t) (s))

/**
 * Chops a PCG state in half and returns the top bits.
 * @param s  The state to chop.
 * @return   The chopped state's top bits.
 */
#define BC_RAND_CHOP(s) ((uint32_t) ((s) >> 32UL))

/**
 * Returns the rotate amount for a PCG state.
 * @param s  The state to rotate.
 * @return   The semi-rotated state.
 */
#define BC_RAND_ROTAMT(s) ((unsigned int) ((s) >> 59UL))

/**
 * Returns the result of a PCG fold.
 * @param s  The state to fold.
 * @return   The folded state.
 */
#define BC_RAND_FOLD(s) ((BcRand) ((((s) >> 18U) ^ (s)) >> 27U))

#endif // BC_LONG_BIT >= 64

/**
 * Rotates @a v by @a r bits.
 * @param v  The value to rotate.
 * @param r  The amount to rotate by.
 * @return   The rotated value.
 */
#define BC_RAND_ROT(v, r) \
	((BcRand) (((v) >> (r)) | ((v) << ((0 - (r)) & BC_RAND_ROTC))))

/// The number of bits in a random integer.
#define BC_RAND_BITS (sizeof(BcRand) * CHAR_BIT)

/// The number of bits in a PCG state.
#define BC_RAND_STATE_BITS (sizeof(BcRandState) * CHAR_BIT)

/// The size of a BcNum with the max random integer. This isn't exact; it's
/// actually rather crude. But it's always enough.
#define BC_RAND_NUM_SIZE (BC_NUM_BIGDIG_LOG10 * 2 + 2)

/// The mask for how many bits bc_rand_srand() can set per iteration.
#define BC_RAND_SRAND_BITS ((1 << CHAR_BIT) - 1)

/// The actual RNG data. These are the actual PRNG's.
typedef struct BcRNGData
{
	/// The state.
	BcRandState state;

	/// The increment and the modified bit.
	BcRandState inc;

} BcRNGData;

/// The public PRNG. This is just a stack of PRNG's to maintain the globals
/// stack illusion.
typedef struct BcRNG
{
	/// The stack of PRNG's.
	BcVec v;

} BcRNG;

/**
 * Initializes a BcRNG.
 * @param r  The BcRNG to initialize.
 */
void
bc_rand_init(BcRNG* r);

#if BC_RAND_USE_FREE

/**
 * Frees a BcRNG. This is only in debug builds because it would only be freed on
 * exit.
 * @param r  The BcRNG to free.
 */
void
bc_rand_free(BcRNG* r);

#endif // BC_RAND_USE_FREE

/**
 * Returns a random integer from the PRNG.
 * @param r  The PRNG.
 * @return   A random integer.
 */
BcRand
bc_rand_int(BcRNG* r);

/**
 * Returns a random integer from the PRNG bounded by @a bound. Bias is
 * eliminated.
 * @param r      The PRNG.
 * @param bound  The bound for the random integer.
 * @return       A bounded random integer.
 */
BcRand
bc_rand_bounded(BcRNG* r, BcRand bound);

/**
 * Seed the PRNG with the state in two parts and the increment in two parts.
 * @param r       The PRNG.
 * @param state1  The first part of the state.
 * @param state2  The second part of the state.
 * @param inc1    The first part of the increment.
 * @param inc2    The second part of the increment.
 */
void
bc_rand_seed(BcRNG* r, ulong state1, ulong state2, ulong inc1, ulong inc2);

/**
 * Pushes a new PRNG onto the PRNG stack.
 * @param r  The PRNG.
 */
void
bc_rand_push(BcRNG* r);

/**
 * Pops one or all but one items off of the PRNG stack.
 * @param r      The PRNG.
 * @param reset  True if all but one PRNG should be popped off the stack, false
 *               if only one should be popped.
 */
void
bc_rand_pop(BcRNG* r, bool reset);

/**
 * Returns, via pointers, the state of the PRNG in pieces.
 * @param r   The PRNG.
 * @param s1  The return value for the first part of the state.
 * @param s2  The return value for the second part of the state.
 * @param i1  The return value for the first part of the increment.
 * @param i2  The return value for the second part of the increment.
 */
void
bc_rand_getRands(BcRNG* r, BcRand* s1, BcRand* s2, BcRand* i1, BcRand* i2);

/**
 * Seed the PRNG with random data.
 * @param rng  The PRNG.
 */
void
bc_rand_srand(BcRNGData* rng);

/// A reference to a constant multiplier.
extern const BcRandState bc_rand_multiplier;

#endif // BC_ENABLE_EXTRA_MATH

#endif // BC_RAND_H
