#pragma once

#include <array>
#include <chrono>
#include <glm/glm.hpp>

#include "metrics/HistoryBuffer.hpp"

namespace tw {

/**
 * Controls the character's body
 */
class CharacterController {
private:
    float m_speed;

    using Clock = std::chrono::steady_clock;

    HistoryBuffer<Clock::time_point, glm::vec3> m_history;
    HistoryBuffer<Clock::time_point, glm::vec3> m_position_history;

    // Frame-indexed input ring, capacity 64
    struct InputSlot {
        uint32_t frame;
        glm::vec3 input;
        bool valid;
    };
    std::array<InputSlot, 64> m_input_ring;

    uint32_t m_last_input_frame;
    glm::vec3 m_last_input;

    uint32_t m_frame_idx;

public:
    GET(m_speed, speed);
    GET_MUT_REF(m_history, input_history);
    GET_MUT_REF(m_position_history, position_history);

    CharacterController(float speed) :
        m_speed(speed),
        m_history(Clock::now(), glm::vec3(), 10 * 20),
        m_position_history(Clock::now(), glm::vec3(), 10 * 20),
        m_last_input_frame(0),
        m_last_input(0.0f),
        m_frame_idx(0)
    {
        // Initialize input ring
        for(auto& slot : m_input_ring) {
            slot.frame = 0;
            slot.input = glm::vec3(0.0f);
            slot.valid = false;
        }
    }

    void set_input(uint32_t frame_idx, glm::vec3 input) {
        size_t idx = frame_idx % m_input_ring.size();
        m_input_ring[idx].frame = frame_idx;
        m_input_ring[idx].input = input;
        m_input_ring[idx].valid = true;

        m_last_input_frame = frame_idx;
        m_last_input = input;
    }

    void set_frame_idx(uint32_t idx) {
        m_frame_idx = idx;
    }

    glm::vec3 input() const {
        return m_last_input;
    }

    // Returns the input for the specified frame, or falls back to the most recently set input
    // if the frame slot has been overwritten or never written
    glm::vec3 input(uint32_t frame_idx) const {
        size_t idx = frame_idx % m_input_ring.size();
        const auto& slot = m_input_ring[idx];

        // If slot contains the exact frame we're looking for, return it
        if(slot.valid && slot.frame == frame_idx) {
            return slot.input;
        }

        // Otherwise, fall back to the most recent input
        // This handles dropped packets (slot never written) or wraparound (slot overwritten)
        return m_last_input;
    }
};

}
