#pragma once

#include <chrono>
#include <vector>
#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;

    struct InputSlot {
        uint32_t frame;
        glm::vec3 input;
        bool valid;
    };
    // buffers inputs with history frame_idx stamp. Can be then read with get_input(frame_idx)
    std::vector<InputSlot> m_input_ring;

    uint32_t m_last_input_frame;

    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_input_ring(64),
        m_last_input_frame(0),
        m_frame_idx(0)
    {
        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;
    }

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

    /**
     * Returns the most recently received input, or zero vector if none was ever set.
     * This is what the simulation consumes: frame stamps come from the client's own
     * counter, so they cannot be looked up by a server-side frame number.
     */
    glm::vec3 latest_input() const {
        const auto& slot = m_input_ring[m_last_input_frame % m_input_ring.size()];
        return slot.valid ? slot.input : glm::vec3(0.0f);
    }

    /**
     * Returns input for specific frame or zero vector if not yet set
     */
    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.valid && slot.frame == frame_idx) {
            return slot.input;
        }

        return glm::vec3(0.0f);
    }
};

}
