#pragma once

#ifndef __INC_LIB8TION_H
#define __INC_LIB8TION_H

#include "fl/math/types.h"
#include "fl/stl/compiler_control.h"

#include "led_sysdefs.h"
#include "platforms/is_platform.h"


/// @file lib8tion.h
/// Fast, efficient 8-bit math functions specifically
/// designed for high-performance LED programming.

#include "fl/stl/stdint.h"
#include "fl/math/lib8static.h"
#include "fl/math/math8.h"
#include "fl/math/qfx.h"
#include "fl/math/memmove.h"
#include "platforms/math8_config.h"
#include "fl/math/ease.h"
#include "fl/math/squarewave.h"
#include "fl/math/beat.h"
#include "fl/math/time_functions.h"
#include "fl/stl/chrono.h"


#if !defined(FL_IS_AVR)
// memmove, memcpy, and memset are defined in lib8tion/memmove.h and fl/stl/cstring.h
#endif // end of !defined(FL_IS_AVR)

/// @defgroup lib8tion Fast Math Functions
/// Fast, efficient 8-bit math functions specifically
/// designed for high-performance LED programming. 
///
/// Because of the AVR (Arduino) and ARM assembly language
/// implementations provided, using these functions often
/// results in smaller and faster code than the equivalent
/// program using plain "C" arithmetic and logic.
///
/// Included are:
///
///  - Saturating unsigned 8-bit add and subtract.
///    Instead of wrapping around if an overflow occurs,
///    these routines just 'clamp' the output at a maxumum
///    of 255, or a minimum of 0.  Useful for adding pixel
///    values.  E.g., fl::qadd8( 200, 100) = 255.
///      @code
///      fl::qadd8( i, j) == fl::min( (i + j), 0xFF )
///      fl::qsub8( i, j) == fl::max( (i - j), 0 )
///      @endcode
///
///  - Saturating signed 8-bit ("7-bit") add.
///      @code
///      qadd7( i, j) == fl::min( (i + j), 0x7F)
///      @endcode
///
///  - Scaling (down) of unsigned 8- and 16- bit values.
///    Scaledown value is specified in 1/256ths.
///      @code
///      scale8( i, sc) == (i * sc) / 256
///      scale16by8( i, sc) == (i * sc) / 256
///      @endcode
///
///    Example: scaling a 0-255 value down into a
///    range from 0-99:
///      @code
///      downscaled = scale8( originalnumber, 100);
///      @endcode
///
///    A special version of scale8 is provided for scaling
///    LED brightness values, to make sure that they don't
///    accidentally scale down to total black at low
///    dimming levels, since that would look wrong:
///      @code
///      scale8_video( i, sc) = ((i * sc) / 256) +? 1
///      @endcode
///
///    Example: reducing an LED brightness by a
///    dimming factor:
///      @code
///      new_bright = scale8_video( orig_bright, dimming);
///      @endcode
///
///  - Fast 8- and 16- bit unsigned random numbers.
///    Significantly faster than Arduino random(), but
///    also somewhat less random.  You can add entropy.
///      @code
///      random8()       == random from 0..255
///      random8( n)     == random from 0..(N-1)
///      random8( n, m)  == random from N..(M-1)
///
///      random16()      == random from 0..65535
///      random16( n)    == random from 0..(N-1)
///      random16( n, m) == random from N..(M-1)
///
///      random16_set_seed( k)    ==  seed = k
///      random16_add_entropy( k) ==  seed += k
///      @endcode
///
///  - Absolute value of a signed 8-bit value.
///      @code
///      abs8( i)     == abs( i)
///      @endcode
///
///  - 8-bit math operations which return 8-bit values.
///    These are provided mostly for completeness,
///    not particularly for performance.
///      @code
///      fl::mul8( i, j)  == (i * j) & 0xFF
///      add8( i, j)  == (i + j) & 0xFF
///      sub8( i, j)  == (i - j) & 0xFF
///      @endcode
///
///  - Fast 16-bit approximations of sin and cos.
///    Input angle is a uint16_t from 0-65535.
///    Output is a signed int16_t from -32767 to 32767.
///      @code
///      sin16( x)  == sin( (x/32768.0) * pi) * 32767
///      cos16( x)  == cos( (x/32768.0) * pi) * 32767
///      @endcode
///
///    Accurate to more than 99% in all cases.
///
///  - Fast 8-bit approximations of sin and cos.
///    Input angle is a uint8_t from 0-255.
///    Output is an UNsigned uint8_t from 0 to 255.
///      @code
///      sin8( x)  == (sin( (x/128.0) * pi) * 128) + 128
///      cos8( x)  == (cos( (x/128.0) * pi) * 128) + 128
///      @endcode
///
///    Accurate to within about 2%.
///
///  - Fast 8-bit "easing in/out" function.
///      @code
///      ease8InOutCubic(x) == 3(x^2) - 2(x^3)
///      ease8InOutApprox(x) ==
///        faster, rougher, approximation of cubic easing
///      ease8InOutQuad(x) == quadratic (vs cubic) easing
///      @endcode
///
///  - Cubic, Quadratic, and Triangle wave functions.
///    Input is a uint8_t representing phase withing the wave,
///      similar to how sin8 takes an angle 'theta'.
///    Output is a uint8_t representing the amplitude of
///    the wave at that point.
///      @code
///      cubicwave8( x)
///      quadwave8( x)
///      triwave8( x)
///      @endcode
///
///  - Square root for 16-bit integers.  About three times
///    faster and five times smaller than Arduino's built-in
///    generic 32-bit sqrt routine.
///      @code
///      sqrt16( uint16_t x ) == sqrt( x)
///      @endcode
///
///  - Dimming and brightening functions for 8-bit
///    light values.
///      @code
///      dim8_video( x)  == scale8_video( x, x)
///      dim8_raw( x)    == scale8( x, x)
///      dim8_lin( x)    == (x<128) ? ((x+1)/2) : scale8(x,x)
///      brighten8_video( x) == 255 - dim8_video( 255 - x)
///      brighten8_raw( x) == 255 - dim8_raw( 255 - x)
///      brighten8_lin( x) == 255 - dim8_lin( 255 - x)
///      @endcode
///
///    The dimming functions in particular are suitable
///    for making LED light output appear more 'linear'.
///
///  - Linear interpolation between two values, with the
///    fraction between them expressed as an 8- or 16-bit
///    fixed point fraction (fract8 or fract16).
///      @code
///      lerp8by8(   fromU8, toU8, fract8 )
///      fl::lerp16by8(  fromU16, toU16, fract8 )
///      fl::lerp15by8(  fromS16, toS16, fract8 )
///        == from + (( to - from ) * fract8) / 256)
///      fl::lerp16by16( fromU16, toU16, fract16 )
///        == from + (( to - from ) * fract16) / 65536)
///      map8( in, rangeStart, rangeEnd)
///        == map( in, 0, 255, rangeStart, rangeEnd);
///      @endcode
///
///  - Optimized memmove, memcpy, and memset, that are
///    faster than standard avr-libc 1.8.
///      @code
///      memmove8( dest, src,  bytecount)
///      memcpy8(  dest, src,  bytecount)
///      memset8(  buf, value, bytecount)
///      @endcode
///
///  - Beat generators which return sine or sawtooth
///    waves in a specified number of Beats Per Minute.
///    Sine wave beat generators can specify a low and
///    high range for the output.  Sawtooth wave beat
///    generators always range 0-255 or 0-65535.
///      @code
///      beatsin8( BPM, low8, high8)
///          = (sine(beatphase) * (high8-low8)) + low8
///      beatsin16( BPM, low16, high16)
///          = (sine(beatphase) * (high16-low16)) + low16
///      beatsin88( BPM88, low16, high16)
///          = (sine(beatphase) * (high16-low16)) + low16
///      beat8( BPM)  = 8-bit repeating sawtooth wave
///      beat16( BPM) = 16-bit repeating sawtooth wave
///      beat88( BPM88) = 16-bit repeating sawtooth wave
///      @endcode
///
///    BPM is beats per minute in either simple form
///    e.g. 120, or Q8.8 fixed-point form.
///    BPM88 is beats per minute in ONLY Q8.8 fixed-point
///    form.
///
/// Lib8tion is pronounced like 'libation': lie-BAY-shun
///
/// @{




#include "fl/math/math8.h"
#include "fl/math/scale8.h"
#include "fl/math/random8.h"
#include "fl/math/trig8.h"

// Bring types and common math functions into global scope for backward compatibility.
// Only import names that are actually defined in namespace fl (via platforms/ headers).
// Functions defined with LIB8STATIC outside namespace fl (e.g., random8.h) are already global.
//
// On AVR, Arduino's USBAPI.h defines u8/u16/u32 at global scope with potentially
// different underlying types (e.g. unsigned short vs unsigned int for u16).
// Skip those to avoid redeclaration conflicts.
#if !defined(FL_IS_AVR)
using fl::u8;
// On AVR+Arduino, USBAPI.h (included via Arduino.h) already provides a global
// 'typedef unsigned short u16;'. fl::u16 on AVR is 'unsigned int' — same width
// but a different type — so 'using fl::u16;' would conflict. Skip it on AVR and
// let USBAPI.h's typedef provide the global u16 name instead.
#if !defined(FL_IS_AVR) || !defined(ARDUINO)
using fl::u16;
#endif
using fl::u32;
#endif
using fl::u64;
using fl::i8;
using fl::i16;
using fl::i32;
using fl::i64;
// scale8.h (namespace fl)
using fl::scale8;
using fl::scale8_video;
using fl::scale16;
using fl::scale16by8;
using fl::scale8_LEAVING_R1_DIRTY;
using fl::nscale8x3;
using fl::nscale8x3_video;
using fl::scale8_video_LEAVING_R1_DIRTY;
using fl::cleanup_R1;
// trig8.h (namespace fl)
using fl::sin8;
using fl::sin16;
using fl::cos8;
using fl::cos16;
// math8.h (namespace fl)
using fl::qadd8;
using fl::qsub8;
using fl::qadd7;
using fl::qmul8;
using fl::add8;
using fl::add8to16;
using fl::sub8;
using fl::avg8;
using fl::avg16;
using fl::avg8r;
using fl::avg16r;
using fl::avg7;
using fl::avg15;
using fl::mul8;
using fl::abs8;
using fl::blend8;
using fl::mod8;
using fl::addmod8;
using fl::submod8;
using fl::sqrt16;
using fl::sqrt8;
using fl::dim8_raw;
// qfx.h (namespace fl)
using fl::q44;
using fl::q62;
using fl::q88;
using fl::q124;
// squarewave.h (namespace fl)
using fl::squarewave8;
// beat.h (namespace fl)
using fl::beat88;
using fl::beat16;
using fl::beat8;
using fl::beatsin88;
using fl::beatsin16;
using fl::beatsin8;
// time_functions.h (namespace fl)
using fl::seconds16;
using fl::minutes16;
using fl::hours8;
using fl::div1024_32_16;
using fl::bseconds16;

///////////////////////////////////////////////////////////////////////



///////////////////////////////////////////////////////////////////////
///
/// @defgroup FloatConversions Float-to-Fixed and Fixed-to-Float Conversions
/// Functions to convert between floating point and fixed point types. 
/// @note Anything involving a "float" on AVR will be slower.
/// @{

/// Conversion from 16-bit fixed point (::sfract15) to IEEE754 32-bit float.
LIB8STATIC float sfract15ToFloat( sfract15 y)
{
    return y / 32768.0f;
}

/// Conversion from IEEE754 float in the range (-1,1) to 16-bit fixed point (::sfract15).
/// @note The extremes of one and negative one are NOT representable! The
/// representable range is 0.99996948242 to -0.99996948242, in steps of 0.00003051757.
LIB8STATIC sfract15 floatToSfract15( float f)
{
    return static_cast<sfract15>(f * 32768.0f);
}

/// @} FloatConversions





///////////////////////////////////////////////////////////////////////
///
/// @defgroup LinearInterpolation Linear Interpolation
/// Fast linear interpolation functions, such as could be used for Perlin noise, etc.
///
/// A note on the structure of the lerp functions:
/// The cases for b>a and b<=a are handled separately for
/// speed. Without knowing the relative order of a and b,
/// the value (a-b) might be overflow the width of a or b,
/// and have to be promoted to a wider, slower type.
/// To avoid that, we separate the two cases, and are able
/// to do all the math in the same width as the arguments,
/// which is much faster and smaller on AVR.
/// @{

/// Linear interpolation between two unsigned 8-bit values,
/// with 8-bit fraction
LIB8STATIC fl::u8 lerp8by8( fl::u8 a, fl::u8 b, fract8 frac)
{
    fl::u8 result;
    if( b > a) {
        fl::u8 delta = b - a;
        fl::u8 scaled = scale8( delta, frac);
        result = a + scaled;
    } else {
        fl::u8 delta = a - b;
        fl::u8 scaled = scale8( delta, frac);
        result = a - scaled;
    }
    return result;
}

/// Linear interpolation between two unsigned 16-bit values,
/// with 16-bit fraction
LIB8STATIC fl::u16 lerp16by16( fl::u16 a, fl::u16 b, fract16 frac)
{
    fl::u16 result;
    if( b > a ) {
        fl::u16 delta = b - a;
        fl::u16 scaled = scale16(delta, frac);
        result = a + scaled;
    } else {
        fl::u16 delta = a - b;
        fl::u16 scaled = scale16( delta, frac);
        result = a - scaled;
    }
    return result;
}

/// Linear interpolation between two unsigned 16-bit values,
/// with 8-bit fraction
LIB8STATIC fl::u16 lerp16by8( fl::u16 a, fl::u16 b, fract8 frac)
{
    fl::u16 result;
    if( b > a) {
        fl::u16 delta = b - a;
        fl::u16 scaled = scale16by8( delta, frac);
        result = a + scaled;
    } else {
        fl::u16 delta = a - b;
        fl::u16 scaled = scale16by8( delta, frac);
        result = a - scaled;
    }
    return result;
}

/// Linear interpolation between two signed 15-bit values,
/// with 8-bit fraction
LIB8STATIC fl::i16 lerp15by8( fl::i16 a, fl::i16 b, fract8 frac)
{
    fl::i16 result;
    if( b > a) {
        fl::u16 delta = b - a;
        fl::u16 scaled = scale16by8( delta, frac);
        result = a + scaled;
    } else {
        fl::u16 delta = a - b;
        fl::u16 scaled = scale16by8( delta, frac);
        result = a - scaled;
    }
    return result;
}

/// Linear interpolation between two signed 15-bit values,
/// with 8-bit fraction
LIB8STATIC fl::i16 lerp15by16( fl::i16 a, fl::i16 b, fract16 frac)
{
    fl::i16 result;
    if( b > a) {
        fl::u16 delta = b - a;
        fl::u16 scaled = scale16( delta, frac);
        result = a + scaled;
    } else {
        fl::u16 delta = a - b;
        fl::u16 scaled = scale16( delta, frac);
        result = a - scaled;
    }
    return result;
}

/// Map from one full-range 8-bit value into a narrower
/// range of 8-bit values, possibly a range of hues.
///
/// E.g. map `myValue` into a hue in the range blue..purple..pink..red
///   @code
///   hue = map8( myValue, HUE_BLUE, HUE_RED);
///   @endcode
///
/// Combines nicely with the waveform functions (like sin8(), etc)
/// to produce continuous hue gradients back and forth:
///   @code
///   hue = map8( sin8( myValue), HUE_BLUE, HUE_RED);
///   @endcode
///
/// Mathematically simiar to lerp8by8(), but arguments are more
/// like Arduino's "map"; this function is similar to
///   @code
///   map( in, 0, 255, rangeStart, rangeEnd)
///   @endcode
///
/// but faster and specifically designed for 8-bit values.
LIB8STATIC fl::u8 map8( fl::u8 in, fl::u8 rangeStart, fl::u8 rangeEnd)
{
    fl::u8 rangeWidth = rangeEnd - rangeStart;
    fl::u8 out = scale8( in, rangeWidth);
    out += rangeStart;
    return out;
}

/// @} LinearInterpolation


///////////////////////////////////////////////////////////////////////
///
/// @defgroup Easing Easing Functions 
/// Specify the rate of change of a parameter over time.
/// @see http://easings.net
/// @{

/// 8-bit quadratic ease-in / ease-out function. 
/// Takes around 13 cycles on AVR.
#if (EASE8_C == 1) || defined(FASTLED_DOXYGEN)
LIB8STATIC fl::u8 ease8InOutQuad(fl::u8 i)
{
    fl::u8 j = i;
    if( j & 0x80 ) {
        j = 255 - j;
    }
    fl::u8 jj  = scale8(  j, j);
    fl::u8 jj2 = jj << 1;
    if( i & 0x80 ) {
        jj2 = 255 - jj2;
    }
    return jj2;
}

#elif EASE8_AVRASM == 1
// This AVR asm version of ease8InOutQuad preserves one more
// low-bit of precision than the C version, and is also slightly
// smaller and faster.
LIB8STATIC fl::u8 ease8InOutQuad(fl::u8 val) {
    fl::u8 j=val;
    asm volatile (
      "sbrc %[val], 7 \n"
      "com %[j]       \n"
      "mul %[j], %[j] \n"
      "add r0, %[j]   \n"
      "ldi %[j], 0    \n"
      "adc %[j], r1   \n"
      "lsl r0         \n" // carry = high bit of low byte of mul product
      "rol %[j]       \n" // j = (j * 2) + carry // preserve add'l bit of precision
      "sbrc %[val], 7 \n"
      "com %[j]       \n"
      "clr __zero_reg__   \n"
      : [j] "+&a" (j)
      : [val] "a" (val)
      : "r0", "r1"
      );
    return j;
}

#else
#error "No implementation for ease8InOutQuad available."
#endif

LIB8STATIC fl::u16 ease16InOutQuad(fl::u16 i)
{
    // This is the legacy version, there is a slightly more accurate version in fl/ease.cpp
    // with fl::easeInOutQuad16. However the difference is minimal.
    //
    // 16-bit quadratic ease-in / ease-out function
    fl::u16 j = i;
    if (j & 0x8000) {
        j = 65535 - j;
    }
    fl::u16 jj = scale16(j, j);
    fl::u16 jj2 = jj << 1;
    if (i & 0x8000) {
        jj2 = 65535 - jj2;
    }
    return jj2;
}

LIB8STATIC fl::u16 ease16InOutCubic(fl::u16 i)  {
    // This function produces wrong results, use fl::easeInOutCubic16 instead
    //
    // 16-bit cubic ease-in / ease-out function
    // Equivalent to ease8InOutCubic() but for 16-bit values
    // Formula: 3(x^2) - 2(x^3) applied with proper ease-in-out curve

    // Apply the cubic formula directly, similar to the 8-bit version
    // scale16(a, b) computes (a * b) / 65536
    fl::u32 ii = scale16(i, i);   // i^2 scaled to 16-bit
    fl::u32 iii = scale16(ii, i); // i^3 scaled to 16-bit

    // Apply cubic formula: 3x^2 - 2x^3
    fl::u32 r1 = (3 * ii) - (2 * iii);

    // Clamp result to 16-bit range
    if (r1 > 65535) {
        return 65535;
    }
    return (fl::u16)r1;
}


/// 8-bit cubic ease-in / ease-out function. 
/// Takes around 18 cycles on AVR.
LIB8STATIC fract8 ease8InOutCubic( fract8 i)
{
    fl::u8 ii  = scale8_LEAVING_R1_DIRTY(  i, i);
    fl::u8 iii = scale8_LEAVING_R1_DIRTY( ii, i);

    fl::u16 r1 = (3 * (fl::u16)(ii)) - ( 2 * (fl::u16)(iii));

    /* the code generated for the above *'s automatically
       cleans up R1, so there's no need to explicitily call
       cleanup_R1(); */

    fl::u8 result = r1;

    // if we got "256", return 255:
    if( r1 & 0x100 ) {
        result = 255;
    }
    return result;
}


/// Fast, rough 8-bit ease-in/ease-out function. 
/// Shaped approximately like ease8InOutCubic(),
/// it's never off by more than a couple of percent
/// from the actual cubic S-curve, and it executes
/// more than twice as fast.  Use when the cycles
/// are more important than visual smoothness.
/// Asm version takes around 7 cycles on AVR.
#if (EASE8_C == 1) || defined(FASTLED_DOXYGEN)
LIB8STATIC fract8 ease8InOutApprox( fract8 i)
{
    if( i < 64) {
        // start with slope 0.5
        i /= 2;
    } else if( i > (255 - 64)) {
        // end with slope 0.5
        i = 255 - i;
        i /= 2;
        i = 255 - i;
    } else {
        // in the middle, use slope 192/128 = 1.5
        i -= 64;
        i += (i / 2);
        i += 32;
    }

    return i;
}

#elif EASE8_AVRASM == 1
LIB8STATIC fl::u8 ease8InOutApprox( fract8 i)
{
    // takes around 7 cycles on AVR
    asm volatile (
        "  subi %[i], 64         \n\t"
        "  cpi  %[i], 128        \n\t"
        "  brcc Lshift_%=        \n\t"

        // middle case
        "  mov __tmp_reg__, %[i] \n\t"
        "  lsr __tmp_reg__       \n\t"
        "  add %[i], __tmp_reg__ \n\t"
        "  subi %[i], 224        \n\t"
        "  rjmp Ldone_%=         \n\t"

        // start or end case
        "Lshift_%=:              \n\t"
        "  lsr %[i]              \n\t"
        "  subi %[i], 96         \n\t"

        "Ldone_%=:               \n\t"

        : [i] "+a" (i)
        :
        : "r0"
        );
    return i;
}
#else
#error "No implementation for ease8 available."
#endif

/// @} Easing


///////////////////////////////////////////////////////////////////////
///
/// @defgroup WaveformGenerators Waveform Generators
/// General purpose wave generator functions.
///
/// @{


/// Triangle wave generator. 
/// Useful for turning a one-byte ever-increasing value into a
/// one-byte value that oscillates up and down.
///   @code
///           input         output
///           0..127        0..254 (positive slope)
///           128..255      254..0 (negative slope)
///   @endcode
///
/// On AVR this function takes just three cycles.
///
LIB8STATIC fl::u8 triwave8(fl::u8 in)
{
    if( in & 0x80) {
        in = 255 - in;
    }
    fl::u8 out = in << 1;
    return out;
}

/// Quadratic waveform generator. Spends just a little
/// more time at the limits than "sine" does. 
///
/// S-shaped wave generator (like "sine"). Useful 
/// for turning a one-byte "counter" value into a
/// one-byte oscillating value that moves smoothly up and down,
/// with an "acceleration" and "deceleration" curve.
///
/// This is even faster than "sin8()", and has
/// a slightly different curve shape.
LIB8STATIC fl::u8 quadwave8(fl::u8 in)
{
    return ease8InOutQuad( triwave8( in));
}

/// Cubic waveform generator. Spends visibly more time
/// at the limits than "sine" does. 
/// @copydetails quadwave8()
LIB8STATIC fl::u8 cubicwave8(fl::u8 in)
{
    return ease8InOutCubic( triwave8( in));
}


// squarewave8 is now in fl/math/squarewave.h (namespace fl),
// brought into global scope via 'using fl::squarewave8' above.

/// @} WaveformGenerators

/// @} lib8tion (excluding the timekeeping functions from the nested group)


///////////////////////////////////////////////////////////////////////
///
/// @defgroup Timekeeping Timekeeping Functions
/// Tools for tracking and working with time
///
/// @{

#if ((defined(ARDUINO) || defined(FL_IS_STM32_F2) || defined(FASTLED_HAS_MILLIS)) && !defined(USE_GET_MILLISECOND_TIMER)) || defined(FASTLED_DOXYGEN)
// Forward declaration of Arduino function 'millis'.
//uint32_t millis();

/// The a number of functions need access to a millisecond counter
/// in order to keep time. On Arduino, this is "millis()".
/// On other platforms, you'll need to provide a function with this
/// signature which provides similar functionality:
///   @code{.cpp}
///   uint32_t get_millisecond_timer();
///   @endcode
///
/// You can also force use of the get_millisecond_timer() function
/// by \#defining `USE_GET_MILLISECOND_TIMER`.
#define GET_MILLIS fl::millis
#else
fl::u32 get_millisecond_timer();
#define GET_MILLIS get_millisecond_timer
#endif

/// @} Timekeeping


/// @addtogroup lib8tion
/// @{


// Beat generators (beat88, beat16, beat8, beatsin88, beatsin16, beatsin8) are now
// in fl/math/beat.h (namespace fl), brought into global scope via 'using' above.

// Time functions (seconds16, minutes16, hours8, div1024_32_16, bseconds16) are now
// in fl/math/time_functions.h (namespace fl), brought into global scope via 'using' above.

/// Preprocessor-based class "template" for ::CEveryNTime, used with `EVERY_N_TIME` timekeepers. 
/// Classes to implement ::EVERY_N_MILLIS, ::EVERY_N_SECONDS,
/// ::EVERY_N_MINUTES, ::EVERY_N_HOURS, and ::EVERY_N_BSECONDS.
#if 1
#define INSTANTIATE_EVERY_N_TIME_PERIODS(NAME,TIMETYPE,TIMEGETTER) \
class NAME {    \
public: \
    TIMETYPE mPrevTrigger;  \
    TIMETYPE mPeriod;   \
    \
    NAME() { reset(); mPeriod = 1; }; \
    NAME(TIMETYPE period) { reset(); setPeriod(period); };    \
    void setPeriod( TIMETYPE period) { mPeriod = period; }; \
    TIMETYPE getTime() { return (TIMETYPE)(TIMEGETTER()); };    \
    TIMETYPE getPeriod() { return mPeriod; };   \
    TIMETYPE getElapsed() { return getTime() - mPrevTrigger; }  \
    TIMETYPE getRemaining() { return mPeriod - getElapsed(); }  \
    TIMETYPE getLastTriggerTime() { return mPrevTrigger; }  \
    bool ready() { \
        bool isReady = (getElapsed() >= mPeriod);   \
        if( isReady ) { reset(); }  \
        return isReady; \
    }   \
    void reset() { mPrevTrigger = getTime(); }; \
    void trigger() { mPrevTrigger = getTime() - mPeriod; }; \
        \
    operator bool() { return ready(); } \
};

/// @name CEveryNTime Base Classes
/// These macros define the time interval checking classes
/// used in the `EVERY_N_TIME` time macros.
/// @{

#if defined(FASTLED_DOXYGEN)
/// Time interval checking class. 
/// Keeps track of a time interval in order to limit how often code
/// is executed.
/// @note TIMETYPE is specific to the initialized class, and is in the
/// units used by the time function. E.g. for ::EVERY_N_MILLIS it's uint32_t
/// and milliseconds, for ::EVERY_N_HOURS it's uint8_t and hours, etc.
/// @warning This specific class isn't actually part of the library! It's created
/// using a preprocessor macro (::INSTANTIATE_EVERY_N_TIME_PERIODS) as
/// a new class for every different time unit. It has been recreated
/// specifically for the documentation, so that the methods can be documented
/// as usual.
/// @see INSTANTIATE_EVERY_N_TIME_PERIODS
class CEveryNTime {
public:
    TIMETYPE mPrevTrigger;  ///< Timestamp of the last time the class was "ready"
    TIMETYPE mPeriod;       ///< Timing interval to check

    /// Default constructor
    CEveryNTime() { reset(); mPeriod = 1; };
    /// Constructor
    /// @param period the time interval between triggers
    CEveryNTime(TIMETYPE period) { reset(); setPeriod(period); };

    /// Set the time interval between triggers
    void setPeriod( TIMETYPE period) { mPeriod = period; };

    /// Get the current time according to the class' timekeeper
    TIMETYPE getTime() { return (TIMETYPE)(TIMEGETTER()); };

    /// Get the time interval between triggers
    TIMETYPE getPeriod() { return mPeriod; };

    /// Get the time elapsed since the last trigger event
    TIMETYPE getElapsed() { return getTime() - mPrevTrigger; }

    /// Get the time until the next trigger event
    TIMETYPE getRemaining() { return mPeriod - getElapsed(); }

    /// Get the timestamp of the most recent trigger event
    TIMETYPE getLastTriggerTime() { return mPrevTrigger; }

    /// Check if the time interval has elapsed
    bool ready() {
        bool isReady = (getElapsed() >= mPeriod);
        if( isReady ) { reset(); }
        return isReady;
    }

    /// Reset the timestamp to the current time
    void reset() { mPrevTrigger = getTime(); };

    /// Reset the timestamp so it is ready() on next call
    void trigger() { mPrevTrigger = getTime() - mPeriod; };

    /// @copydoc ready()
    operator bool() { return ready(); }
};
#endif  // FASTLED_DOXYGEN

/// Create the CEveryNMillis class for millisecond intervals
INSTANTIATE_EVERY_N_TIME_PERIODS(CEveryNMillis,fl::u32,GET_MILLIS);

/// Create the CEveryNSeconds class for second intervals
INSTANTIATE_EVERY_N_TIME_PERIODS(CEveryNSeconds,fl::u16,seconds16);

/// Create the CEveryNBSeconds class for bsecond intervals
INSTANTIATE_EVERY_N_TIME_PERIODS(CEveryNBSeconds,fl::u16,bseconds16);

/// Create the CEveryNMinutes class for minutes intervals
INSTANTIATE_EVERY_N_TIME_PERIODS(CEveryNMinutes,fl::u16,minutes16);

/// Create the CEveryNHours class for hours intervals
INSTANTIATE_EVERY_N_TIME_PERIODS(CEveryNHours,fl::u8,hours8);

/// Alias for CEveryNMillis
#define CEveryNMilliseconds CEveryNMillis

/// Create the CEveryNMillisDynamic class for dynamic millisecond intervals
class CEveryNMillisDynamic {
public:
    fl::u32 mPrevTrigger;
    fl::u32 mPeriod;

    CEveryNMillisDynamic(fl::u32 period) : mPeriod(period) { reset(); };
    fl::u32 getTime() { return GET_MILLIS(); };
    fl::u32 getPeriod() const { return mPeriod; };
    fl::u32 getElapsed() { return getTime() - mPrevTrigger; }
    fl::u32 getRemaining() { return getPeriod() - getElapsed(); }
    fl::u32 getLastTriggerTime() { return mPrevTrigger; }
    bool ready() {
        bool isReady = (getElapsed() >= getPeriod());
        if( isReady ) { reset(); }
        return isReady;
    }
    void reset() { mPrevTrigger = getTime(); };
    void trigger() { mPrevTrigger = getTime() - getPeriod(); };
    void setPeriod(fl::u32 period) { mPeriod = period; }

    operator bool() { return ready(); }
};
/// @} CEveryNTime Base Classes



// ————————————————————————————————————————————————
// Random‐interval version of EVERY_N_MILLISECONDS:
// on each trigger, pick the next period randomly in [MIN..MAX].
// ————————————————————————————————————————————————
class CEveryNMillisRandom {
public:
    fl::u32 mPrevTrigger;
    fl::u32 mPeriod;
    fl::u32 mMinPeriod;
    fl::u32 mMaxPeriod;

    CEveryNMillisRandom(fl::u32 minPeriod, fl::u32 maxPeriod)
      : mMinPeriod(minPeriod), mMaxPeriod(maxPeriod)
    {
        computeNext();
        reset();
    }

    void computeNext() {
        // random16(x) returns [0..x-1], so this yields MIN..MAX
        fl::u32 range = mMaxPeriod - mMinPeriod + 1;
        mPeriod = mMinPeriod + random16(range);
    }

    fl::u32 getTime() const { return GET_MILLIS(); }

    bool ready() {
        fl::u32 now = getTime();
        if (now - mPrevTrigger >= mPeriod) {
            mPrevTrigger = now;
            computeNext();
            return true;
        }
        return false;
    }

    void reset() { mPrevTrigger = getTime(); }
};

#else

// Under C++11 rules, we would be allowed to use not-external
// -linkage-type symbols as template arguments,
// e.g., LIB8STATIC seconds16, and we'd be able to use these
// templates as shown below.
// However, under C++03 rules, we cannot do that, and thus we
// have to resort to the preprocessor to 'instantiate' 'templates',
// as handled above.
template<typename timeType,timeType (*timeGetter)()>
class CEveryNTimePeriods {
public:
    timeType mPrevTrigger;
    timeType mPeriod;

    CEveryNTimePeriods() { reset(); mPeriod = 1; };
    CEveryNTimePeriods(timeType period) { reset(); setPeriod(period); };
    void setPeriod( timeType period) { mPeriod = period; };
    timeType getTime() { return (timeType)(timeGetter()); };
    timeType getPeriod() { return mPeriod; };
    timeType getElapsed() { return getTime() - mPrevTrigger; }
    timeType getRemaining() { return mPeriod - getElapsed(); }
    timeType getLastTriggerTime() { return mPrevTrigger; }
    bool ready() {
        bool isReady = (getElapsed() >= mPeriod);
        if( isReady ) { reset(); }
        return isReady;
    }
    void reset() { mPrevTrigger = getTime(); };
    void trigger() { mPrevTrigger = getTime() - mPeriod; };

    operator bool() { return ready(); }
};
typedef CEveryNTimePeriods<fl::u16,seconds16> CEveryNSeconds;
typedef CEveryNTimePeriods<fl::u16,bseconds16> CEveryNBSeconds;
typedef CEveryNTimePeriods<fl::u32,millis> CEveryNMillis;
typedef CEveryNTimePeriods<fl::u16,minutes16> CEveryNMinutes;
typedef CEveryNTimePeriods<fl::u8,hours8> CEveryNHours;
#endif


/// @name "EVERY_N_TIME" Macros
/// Check whether to excecute a block of code every N amount of time.
/// These are useful for limiting how often code runs. For example,
/// you can use ::fill_rainbow() to fill a strip of LEDs with color,
/// combined with an ::EVERY_N_MILLIS block to limit how fast the colors
/// change:
///   @code{.cpp}
///   static uint8_t hue = 0;
///   fill_rainbow(leds, NUM_LEDS, hue);
///   EVERY_N_MILLIS(20) { hue++; }  // advances hue every 20 milliseconds
///   @endcode
/// Note that in order for these to be accurate, the EVERY_N block must
/// be evaluated at a regular basis.  
/// @{

/// @cond
#define CONCAT_HELPER( x, y ) x##y
#define CONCAT_MACRO( x, y ) CONCAT_HELPER( x, y )
/// @endcond


/// Checks whether to execute a block of code every N milliseconds
/// @see GET_MILLIS
#define EVERY_N_MILLIS(N) EVERY_N_MILLIS_I(CONCAT_MACRO(PER, __COUNTER__ ),N)

/// Checks whether to execute a block of code every N milliseconds, using a custom instance name
/// @copydetails EVERY_N_MILLIS
#define EVERY_N_MILLIS_I(NAME,N) static CEveryNMillis NAME(N); if( NAME )


/// Checks whether to execute a block of code every N seconds
/// @see seconds16()
#define EVERY_N_SECONDS(N) EVERY_N_SECONDS_I(CONCAT_MACRO(PER, __COUNTER__ ),N)

/// Checks whether to execute a block of code every N seconds, using a custom instance name
/// @copydetails EVERY_N_SECONDS
#define EVERY_N_SECONDS_I(NAME,N) static CEveryNSeconds NAME(N); if( NAME )


/// Checks whether to execute a block of code every N bseconds
/// @see bseconds16()
#define EVERY_N_BSECONDS(N) EVERY_N_BSECONDS_I(CONCAT_MACRO(PER, __COUNTER__ ),N)

/// Checks whether to execute a block of code every N bseconds, using a custom instance name
/// @copydetails EVERY_N_BSECONDS
#define EVERY_N_BSECONDS_I(NAME,N) static CEveryNBSeconds NAME(N); if( NAME )


/// Checks whether to execute a block of code every N minutes
/// @see minutes16()
#define EVERY_N_MINUTES(N) EVERY_N_MINUTES_I(CONCAT_MACRO(PER, __COUNTER__ ),N)

/// Checks whether to execute a block of code every N minutes, using a custom instance name
/// @copydetails EVERY_N_MINUTES
#define EVERY_N_MINUTES_I(NAME,N) static CEveryNMinutes NAME(N); if( NAME )


/// Checks whether to execute a block of code every N hours
/// @see hours8()
#define EVERY_N_HOURS(N) EVERY_N_HOURS_I(CONCAT_MACRO(PER, __COUNTER__ ),N)

/// Checks whether to execute a block of code every N hours, using a custom instance name
/// @copydetails EVERY_N_HOURS
#define EVERY_N_HOURS_I(NAME,N) static CEveryNHours NAME(N); if( NAME )


/// Alias for ::EVERY_N_MILLIS
#define EVERY_N_MILLISECONDS(N) EVERY_N_MILLIS(N)
/// Alias for ::EVERY_N_MILLIS_I
#define EVERY_N_MILLISECONDS_I(NAME,N) EVERY_N_MILLIS_I(NAME,N)

/// Checks whether to execute a block of code every N milliseconds, where N is determined dynamically
#define EVERY_N_MILLISECONDS_DYNAMIC(PERIOD_FUNC) EVERY_N_MILLISECONDS_DYNAMIC_I(CONCAT_MACRO(__dynamic_millis_timer, __COUNTER__ ), (PERIOD_FUNC))

/// Checks whether to execute a block of code every N milliseconds, where N is determined dynamically, using a custom instance name
#define EVERY_N_MILLISECONDS_DYNAMIC_I(NAME, PERIOD_FUNC) \
    static CEveryNMillisDynamic NAME(1); \
    NAME.setPeriod(PERIOD_FUNC); \
    if( NAME )


#define EVERY_N_MILLISECONDS_RANDOM(MIN, MAX)                                 \
    EVERY_N_MILLISECONDS_RANDOM_I(                                           \
        CONCAT_MACRO(_permRand, __COUNTER__), MIN, MAX)

#define EVERY_N_MILLISECONDS_RANDOM_I(NAME, MIN, MAX)                        \
    static CEveryNMillisRandom NAME(MIN, MAX);                               \
    if (NAME.ready())

/// @} Every_N
/// @} Timekeeping


// These defines are used to declare hidden or commented symbols for the
// purposes of Doxygen documentation generation. They do not affect your program.
#ifdef FASTLED_DOXYGEN
/// Set this flag to use the get_millisecond_timer() function in place
/// of the default millis() function.
/// @ingroup Timekeeping
#define USE_GET_MILLISECOND_TIMER
#endif

#endif

// Using declarations moved to top of file
