#pragma once

#include <chrono>
#include <thread>
#include <print>

namespace tw {

/**
 * Controls thread sleep to run exactly `n` times per second
 */
class LockStep {
    using Clock = std::chrono::steady_clock;
    using Millis = std::chrono::microseconds;
    const uint64_t IN_SECOND = std::chrono::duration_cast<Millis>(std::chrono::seconds(1)).count();

    uint64_t milliseconds;

    std::chrono::time_point<Clock> m_last_point;

    uint64_t m_last_interval_ms;

    bool m_popped;

public:
    LockStep(uint32_t steps_per_second) {
        m_last_point = Clock::now();
        set_steps_per_second(steps_per_second);
    }

    inline uint64_t milliseconds_per_frame() const {
        return milliseconds;
    }

    inline void set_steps_per_second(uint32_t steps_per_second) {
        milliseconds = IN_SECOND / steps_per_second;
    }

    inline uint64_t wait_time_in_ms() {
        return m_last_interval_ms;
    }

    inline uint64_t fps() {
        if(m_last_interval_ms == 0) {
            return IN_SECOND;
        }
        return IN_SECOND / m_last_interval_ms;
    }

    inline double delta_time() const {
        return m_last_interval_ms / (double)IN_SECOND;
    }

    bool has_ticked() {
        auto now = Clock::now();

        m_last_interval_ms = duration_cast<Millis>(now - m_last_point).count();

        if(m_last_interval_ms >= milliseconds) {
            m_last_point = now;
            return true;
        }

        return false;
    }
    
    /**
     * Waits if time interval from previous call is less than time between two steps
     */
    bool wait_for_next_step() {
        auto now = Clock::now();

        m_last_interval_ms = duration_cast<Millis>(now - m_last_point).count();

        if(m_last_interval_ms < milliseconds) {
            std::this_thread::sleep_for(Millis(milliseconds - m_last_interval_ms));
            return true;
        }

        m_last_point = now;
        return false;
    }
};

}
