#pragma once

/// @file fl/stl/chrono.h
/// @brief FastLED chrono implementation - duration types for time measurements
///
/// This header provides a minimal chrono implementation compatible with std::chrono,
/// allowing FastLED to use duration types without directly depending on <chrono>.
/// The implementation is intentionally compatible with std::chrono for interoperability.

#include "fl/stl/int.h"
#include "fl/stl/ratio.h"

#ifdef FASTLED_TESTING
#include "fl/stl/function.h"
#include "fl/stl/noexcept.h"
#endif

namespace fl {
namespace chrono {

/// @brief Represents a time duration
/// @tparam Rep The arithmetic type representing the number of ticks
/// @tparam Period A fl::ratio representing the tick period (seconds per tick)
///
/// This is a simplified version of std::chrono::duration that provides:
/// - Storage for a duration value
/// - Implicit conversion from compatible duration types
/// - count() method to extract the tick count
///
/// Example:
/// @code
/// fl::chrono::milliseconds ms(1000);  // 1000 milliseconds
/// fl::u32 count = ms.count();          // returns 1000
/// @endcode
template<typename Rep, typename Period = fl::ratio<1>>
class duration {
public:
    using rep = Rep;
    using period = Period;

    /// Default constructor - zero duration
    constexpr duration() FL_NOEXCEPT : mCount(0) {}

    /// Explicit constructor from tick count
    /// @param count Number of ticks
    constexpr explicit duration(const Rep& count) FL_NOEXCEPT : mCount(count) {}

    /// Implicit conversion constructor from compatible duration types
    /// @tparam Rep2 Source tick type
    /// @tparam Period2 Source tick period
    template<typename Rep2, typename Period2>
    constexpr duration(const duration<Rep2, Period2>& d) FL_NOEXCEPT
        : mCount(duration_cast_impl<Rep, Period, Rep2, Period2>(d.count())) {}

    /// Get the tick count
    /// @return Number of ticks stored in this duration
    constexpr Rep count() const FL_NOEXCEPT { return mCount; }

private:
    Rep mCount;

    /// Internal duration_cast implementation
    template<typename ToRep, typename ToPeriod, typename FromRep, typename FromPeriod>
    static constexpr ToRep duration_cast_impl(FromRep count) FL_NOEXCEPT {
        // Convert from source period to destination period
        // target_count = source_count * (FromPeriod / ToPeriod)
        // Using: target = source * FromPeriod::num * ToPeriod::den / (FromPeriod::den * ToPeriod::num)
        using ratio = fl::ratio_divide<FromPeriod, ToPeriod>;
        return static_cast<ToRep>(
            static_cast<FromRep>(count) * static_cast<FromRep>(ratio::num) / static_cast<FromRep>(ratio::den)
        );
    }
};

/// @brief Cast one duration type to another
/// @tparam ToDuration Target duration type
/// @tparam Rep Source tick type
/// @tparam Period Source tick period
/// @param d Source duration
/// @return Duration converted to target type
///
/// Example:
/// @code
/// fl::chrono::seconds s(1);
/// auto ms = fl::chrono::duration_cast<fl::chrono::milliseconds>(s);
/// // ms.count() == 1000
/// @endcode
template<typename ToDuration, typename Rep, typename Period>
constexpr ToDuration duration_cast(const duration<Rep, Period>& d) FL_NOEXCEPT {
    return ToDuration(d);
}

// Common duration type aliases using standard SI ratios
// These are compatible with std::chrono duration types

/// Nanoseconds - duration with period of 1/1,000,000,000 seconds
using nanoseconds = duration<fl::i64, fl::nano>;

/// Microseconds - duration with period of 1/1,000,000 seconds
using microseconds = duration<fl::i64, fl::micro>;

/// Milliseconds - duration with period of 1/1,000 seconds
using milliseconds = duration<fl::i64, fl::milli>;

/// Seconds - duration with period of 1 second
using seconds = duration<fl::i64>;

/// Minutes - duration with period of 60 seconds
using minutes = duration<fl::i32, fl::ratio<60>>;

/// Hours - duration with period of 3600 seconds
using hours = duration<fl::i32, fl::ratio<3600>>;

/// @brief Represents a point in time relative to a clock
/// @tparam Clock The clock type this time_point is measured against
/// @tparam Duration The duration type used to measure time since epoch
///
/// This is a simplified version of std::chrono::time_point that provides:
/// - Storage for a time point as a duration since the clock's epoch
/// - Comparison operators
/// - Arithmetic with other time_points and durations
template<typename Clock, typename Duration = typename Clock::duration>
class time_point {
public:
    using clock = Clock;
    using duration = Duration;
    using rep = typename Duration::rep;
    using period = typename Duration::period;

    /// Default constructor - epoch time point
    constexpr time_point() FL_NOEXCEPT : mDuration() {}

    /// Construct from a duration since epoch
    constexpr explicit time_point(const Duration& d) FL_NOEXCEPT : mDuration(d) {}

    /// Get the duration since epoch
    constexpr Duration time_since_epoch() const FL_NOEXCEPT { return mDuration; }

    // Comparison operators
    constexpr bool operator<=(const time_point& rhs) const FL_NOEXCEPT {
        return mDuration.count() <= rhs.mDuration.count();
    }
    constexpr bool operator<(const time_point& rhs) const FL_NOEXCEPT {
        return mDuration.count() < rhs.mDuration.count();
    }
    constexpr bool operator>=(const time_point& rhs) const FL_NOEXCEPT {
        return mDuration.count() >= rhs.mDuration.count();
    }
    constexpr bool operator>(const time_point& rhs) const FL_NOEXCEPT {
        return mDuration.count() > rhs.mDuration.count();
    }
    constexpr bool operator==(const time_point& rhs) const FL_NOEXCEPT {
        return mDuration.count() == rhs.mDuration.count();
    }
    constexpr bool operator!=(const time_point& rhs) const FL_NOEXCEPT {
        return mDuration.count() != rhs.mDuration.count();
    }

    /// Subtract two time_points to get a duration
    constexpr Duration operator-(const time_point& rhs) const FL_NOEXCEPT {
        return Duration(mDuration.count() - rhs.mDuration.count());
    }

    /// Add a duration to a time_point
    constexpr time_point operator+(const Duration& d) const FL_NOEXCEPT {
        return time_point(Duration(mDuration.count() + d.count()));
    }

    /// Subtract a duration from a time_point
    constexpr time_point operator-(const Duration& d) const FL_NOEXCEPT {
        return time_point(Duration(mDuration.count() - d.count()));
    }

private:
    Duration mDuration;
};

// Forward declarations for clock types (now() implemented after fl::micros() declaration)
struct steady_clock;  // IWYU pragma: keep
struct system_clock;  // IWYU pragma: keep

/// @brief Monotonic clock that never goes backwards
///
/// Uses fl::micros() as the underlying time source, providing microsecond
/// resolution across all FastLED platforms.
struct steady_clock {
    using duration = microseconds;
    using rep = duration::rep;
    using period = duration::period;
    using time_point = fl::chrono::time_point<steady_clock>;
    static constexpr bool is_steady = true;

    /// Returns the current time point (implemented after fl::micros() declaration)
    static time_point now() FL_NOEXCEPT;
};

/// @brief Wall clock (may not be monotonic)
///
/// On embedded platforms this behaves identically to steady_clock since
/// there is no real-time clock available. Uses fl::micros() as the
/// underlying time source.
struct system_clock {
    using duration = microseconds;
    using rep = duration::rep;
    using period = duration::period;
    using time_point = fl::chrono::time_point<system_clock>;
    static constexpr bool is_steady = false;

    /// Returns the current time point (implemented after fl::micros() declaration)
    static time_point now() FL_NOEXCEPT;
};

} // namespace chrono

///////////////////// TIME FUNCTIONS //////////////////////////////////////

/// Universal millisecond timer - returns milliseconds since system startup
///
/// This function provides consistent timing across all FastLED platforms:
/// - Arduino/AVR: Hardware timer-based millis()
/// - ESP32/ESP8266: ESP-IDF timing functions
/// - ARM: Platform-specific ARM timers
/// - WASM: JavaScript-based timing
/// - Native/Testing: std::chrono implementation
/// - Stub: Fake timer for testing
///
/// @return Number of milliseconds since the system started
/// @note Wraps around approximately every 49.7 days (2^32 milliseconds)
/// @note This function is designed to be zero-overhead - it compiles to a direct
///       platform call in optimized builds
///
/// @section Platform Behavior
/// - **Consistent**: All platforms return milliseconds since startup/initialization
/// - **Monotonic**: Time always increases (except on wraparound)
/// - **Resolution**: 1 millisecond resolution on all platforms
/// - **Wraparound**: Consistent wraparound behavior at 2^32 milliseconds
///
/// @section Usage Examples
/// @code
/// // Basic timing
/// fl::u32 start = fl::millis();
/// do_work();
/// fl::u32 elapsed = fl::millis() - start;
///
/// // Animation timing
/// static fl::u32 last_frame = 0;
/// fl::u32 now = fl::millis();
/// if (now - last_frame >= 16) { // 60 FPS
///     render_frame();
///     last_frame = now;
/// }
///
/// // Timeout handling
/// fl::u32 timeout = fl::millis() + 5000; // 5 second timeout
/// while (fl::millis() < timeout && !is_complete()) {
///     process_step();
/// }
/// @endcode
fl::u32 millis() FL_NOEXCEPT;

/// Universal microsecond timer - returns microseconds since system startup
///
/// This function provides consistent high-resolution timing across all FastLED platforms:
/// - Arduino/AVR: Hardware timer-based micros()
/// - ESP32/ESP8266: ESP-IDF timing functions
/// - ARM: Platform-specific ARM timers
/// - WASM: JavaScript-based timing with microsecond precision
/// - Native/Testing: std::chrono implementation
/// - Stub: Fake timer for testing
///
/// @return Number of microseconds since the system started
/// @note Wraps around approximately every 71.6 minutes (2^32 microseconds)
/// @note This function is designed to be zero-overhead - it compiles to a direct
///       platform call in optimized builds
///
/// @section Platform Behavior
/// - **Consistent**: All platforms return microseconds since startup/initialization
/// - **Monotonic**: Time always increases (except on wraparound)
/// - **Resolution**: 1 microsecond resolution on all platforms (platform-dependent precision)
/// - **Wraparound**: Consistent wraparound behavior at 2^32 microseconds
///
/// @section Usage Examples
/// @code
/// // Precise timing measurement
/// fl::u32 start = fl::micros();
/// fast_operation();
/// fl::u32 elapsed = fl::micros() - start;
///
/// // High-precision delay
/// fl::u32 target = fl::micros() + 100; // 100 microseconds
/// while (fl::micros() < target) {
///     // Busy wait
/// }
/// @endcode
fl::u32 micros() FL_NOEXCEPT;

// Now that fl::micros() is declared, implement clock::now() methods
inline chrono::steady_clock::time_point chrono::steady_clock::now() FL_NOEXCEPT {
    return time_point(duration(static_cast<fl::i64>(fl::micros())));
}

inline chrono::system_clock::time_point chrono::system_clock::now() FL_NOEXCEPT {
    return time_point(duration(static_cast<fl::i64>(fl::micros())));
}

/// 64-bit millisecond timer - returns milliseconds since system startup without wraparound
///
/// This function provides a 64-bit millisecond counter that never wraps around in any
/// practical timeframe (584 million years). It's built on top of fl::millis() and
/// automatically detects and corrects for 32-bit wraparound.
///
/// The implementation uses a function-local static for thread-safe accumulation on
/// platforms that support it. On platforms with native 64-bit timers, this function
/// may be optimized to use the native implementation directly.
///
/// @return Number of milliseconds since system startup as a 64-bit value
/// @note Thread-safe on platforms with proper static initialization guards
/// @note No wraparound in practical usage (584+ million years)
/// @note Slightly higher overhead than millis() due to wraparound detection
///
/// @section Usage Examples
/// @code
/// // Long-running timing without wraparound concerns
/// fl::u64 start = fl::millis64();
/// long_running_operation();
/// fl::u64 elapsed = fl::millis64() - start;
///
/// // Safe duration tracking across 49+ day boundaries
/// static fl::u64 session_start = fl::millis64();
/// fl::u64 uptime = fl::millis64() - session_start; // Never wraps
///
/// // Timestamps for logging and analytics
/// fl::u64 event_time = fl::millis64();
/// log_event("operation_complete", event_time);
/// @endcode
fl::u64 millis64() FL_NOEXCEPT;

/// Alias for millis64() - returns 64-bit millisecond time
///
/// This function is an alias for fl::millis64() and provides a convenient
/// shorter name for getting the 64-bit millisecond timer value.
///
/// @return Number of milliseconds since system startup as a 64-bit value
/// @note Inline function - zero overhead
/// @see millis64() for full documentation
inline fl::u64 time() FL_NOEXCEPT {
    return fl::millis64();
}

#ifdef FASTLED_TESTING

/// Type alias for time provider functions used in testing
using time_provider_t = fl::function<fl::u32()>;

/// Inject a custom time provider for testing
///
/// This function allows unit tests to control the timing returned by fl::millis().
/// Once injected, all calls to fl::millis() will use the provided function instead
/// of the platform's native timing.
///
/// @param provider Function that returns the current time in milliseconds
/// @note Only available in testing builds (when FASTLED_TESTING is defined)
/// @note Thread-safe: Uses appropriate locking in multi-threaded environments
///
/// @section Example Usage
/// @code
/// fl::MockTimeProvider mock(1000);
/// fl::inject_time_provider(mock);
///
/// ASSERT_EQ(fl::millis(), 1000);
///
/// mock.advance(500);
/// ASSERT_EQ(fl::millis(), 1500);
///
/// fl::clear_time_provider(); // Restore normal timing
/// @endcode
void inject_time_provider(const time_provider_t& provider) FL_NOEXCEPT;

/// Clear the injected time provider and restore platform default timing
///
/// After calling this function, fl::millis() will return to using the platform's
/// native timing implementation.
///
/// @note Only available in testing builds (when FASTLED_TESTING is defined)
/// @note Thread-safe: Uses appropriate locking in multi-threaded environments
/// @note Safe to call multiple times or when no provider is injected
void clear_time_provider() FL_NOEXCEPT;

/// Reset the millis64() internal state for testing
///
/// This function resets the internal accumulation state of millis64() to allow
/// tests to start with a clean state. Should be called between test cases that
/// use MockTimeProvider to ensure consistent behavior.
///
/// @note Only available in testing builds (when FASTLED_TESTING is defined)
/// @note Thread-safe: Uses appropriate locking in multi-threaded environments
void millis64_reset() FL_NOEXCEPT;

/// Mock time provider for controlled testing
///
/// This class provides a controllable time source for unit testing. It maintains
/// an internal time value that can be advanced manually or set to specific values.
///
/// @section Example Usage
/// @code
/// fl::MockTimeProvider mock(1000); // Start at 1000ms
/// fl::inject_time_provider(mock);
///
/// // Test timing-dependent code
/// EXPECT_EQ(fl::millis(), 1000);
///
/// mock.advance(100); // Advance by 100ms
/// EXPECT_EQ(fl::millis(), 1100);
///
/// mock.set_time(5000); // Jump to 5000ms
/// EXPECT_EQ(fl::millis(), 5000);
///
/// fl::clear_time_provider();
/// @endcode
class MockTimeProvider {
public:
    /// Constructor with initial time value
    /// @param initial_time Starting time in milliseconds (default: 0)
    explicit MockTimeProvider(fl::u32 initial_time = 0) FL_NOEXCEPT;

    /// Advance the mock time by the specified amount
    /// @param milliseconds Number of milliseconds to advance
    void advance(fl::u32 milliseconds) FL_NOEXCEPT;

    /// Set the mock time to a specific value
    /// @param milliseconds New time value in milliseconds
    void set_time(fl::u32 milliseconds) FL_NOEXCEPT;

    /// Get the current mock time
    /// @return Current time in milliseconds
    fl::u32 current_time() const FL_NOEXCEPT;

    /// Function call operator for use with inject_time_provider()
    /// @return Current time in milliseconds
    fl::u32 operator()() const FL_NOEXCEPT;

private:
    fl::u32 mCurrentTime;
};

#endif // FASTLED_TESTING

} // namespace fl
