#pragma once

#ifndef __INC_LIB8TION_RANDOM_H
#define __INC_LIB8TION_RANDOM_H

#include "platforms/is_platform.h"

#if defined(FL_IS_AVR)
#include "platforms/avr/is_avr.h"  // ok platform headers  // IWYU pragma: keep
#endif

#include "fl/stl/stdint.h"

#include "fl/math/lib8static.h"
#include "fl/stl/noexcept.h"

/// @file random8.h
/// Fast, efficient random number generators specifically
/// designed for high-performance LED programming.

/// @ingroup lib8tion
/// @{

/// @defgroup Random Fast Random Number Generators
/// Fast 8-bit and 16-bit unsigned random number generators.
/// Significantly faster than Arduino random(), but
/// also somewhat less random.  You can add entropy.
///
/// Pseudo-random number generation follows the form:
///   @code
///   X(n+1) = (2053 * X(n)) + 13849)
///   @endcode
/// @{

/// Multiplier value for pseudo-random number generation
#define FASTLED_RAND16_2053 ((fl::u16)(2053))
/// Increment value for pseudo-random number generation
#define FASTLED_RAND16_13849 ((fl::u16)(13849))

#ifdef FL_IS_AVR_ATTINY
/// Multiplies a value by the pseudo-random multiplier
#define APPLY_FASTLED_RAND16_2053(x) (x << 11) + (x << 2) + x
#else
/// Multiplies a value by the pseudo-random multiplier
#define APPLY_FASTLED_RAND16_2053(x) (x * FASTLED_RAND16_2053)
#endif

/// Seed for the random number generator functions
extern fl::u16 rand16seed; // = RAND16_SEED;

/// Generate an 8-bit random number
/// @returns random 8-bit number, in the range 0-255
LIB8STATIC fl::u8 random8() FL_NOEXCEPT {
    rand16seed = APPLY_FASTLED_RAND16_2053(rand16seed) + FASTLED_RAND16_13849;
    // return the sum of the high and low bytes, for better
    //  mixing and non-sequential correlation
    return (fl::u8)(((fl::u8)(rand16seed & 0xFF)) +
                     ((fl::u8)(rand16seed >> 8)));
}

/// Generate a 16-bit random number
/// @returns random 16-bit number, in the range 0-65535
LIB8STATIC fl::u16 random16() FL_NOEXCEPT {
    rand16seed = APPLY_FASTLED_RAND16_2053(rand16seed) + FASTLED_RAND16_13849;
    return rand16seed;
}

/// Generate an 8-bit random number between 0 and lim
/// @param lim the upper bound for the result, exclusive
LIB8STATIC fl::u8 random8(fl::u8 lim) FL_NOEXCEPT {
    fl::u8 r = random8();
    r = (r * lim) >> 8;
    return r;
}

/// Generate an 8-bit random number in the given range
/// @param min the lower bound for the random number, inclusive
/// @param lim the upper bound for the random number, exclusive
LIB8STATIC fl::u8 random8(fl::u8 min, fl::u8 lim) FL_NOEXCEPT {
    fl::u8 delta = lim - min;
    fl::u8 r = random8(delta) + min;
    return r;
}

/// Generate an 16-bit random number between 0 and lim
/// @param lim the upper bound for the result, exclusive
LIB8STATIC fl::u16 random16(fl::u16 lim) FL_NOEXCEPT {
    fl::u16 r = random16();
    fl::u32 p = (fl::u32)lim * (fl::u32)r;
    r = p >> 16;
    return r;
}

/// Generate an 16-bit random number in the given range
/// @param min the lower bound for the random number, inclusive
/// @param lim the upper bound for the random number, exclusive
LIB8STATIC fl::u16 random16(fl::u16 min, fl::u16 lim) FL_NOEXCEPT {
    fl::u16 delta = lim - min;
    fl::u16 r = random16(delta) + min;
    return r;
}

/// Set the 16-bit seed used for the random number generator
LIB8STATIC void random16_set_seed(fl::u16 seed) FL_NOEXCEPT { rand16seed = seed; }

/// Get the current seed value for the random number generator
LIB8STATIC fl::u16 random16_get_seed() FL_NOEXCEPT { return rand16seed; }

/// Add entropy into the random number generator
LIB8STATIC void random16_add_entropy(fl::u16 entropy) FL_NOEXCEPT {
    rand16seed += entropy;
}

/// @} Random
/// @} lib8tion

#endif
