#pragma once

#include <cstdint>
#include <array>
#include <glm/glm.hpp>
#include <entt/entt.hpp>

namespace tw {
class JoltPhysicsWorld;
}

namespace tw::net {

class PlayerRollback {
private:
    struct Record {
        uint32_t frame;
        bool valid;
        glm::vec3 input;
        glm::vec3 predicted_position;
    };

    static constexpr size_t RING_SIZE = 64;
    static constexpr float kPositionEpsilon = 0.05f;

    tw::JoltPhysicsWorld* m_physics;
    std::array<Record, RING_SIZE> m_records;

    uint32_t m_last_reconciled_ack = 0;
    uint64_t m_rollback_count = 0;
    float m_last_correction_distance = 0.0f;
    uint32_t m_last_replayed_frames = 0;
    uint32_t m_last_ack_frame = 0;

public:
    PlayerRollback(tw::JoltPhysicsWorld* physics);

    void set_input(uint32_t frame, glm::vec3 input);
    void record_prediction(uint32_t frame, glm::vec3 position);

    bool apply_correction(
            uint32_t ack_frame, glm::vec3 authoritative_position,
            entt::entity player, entt::registry* registry, uint32_t current_frame);

private:
    /**
     * Re-simulates `from_frame + 1` up to the newest frame, refreshing the stored
     * prediction for each. The inputs come from the character itself, so this
     * works even for frames this ring never recorded.
     */
    void replay_from(uint32_t from_frame, entt::entity player,
                     entt::registry* registry, uint32_t current_frame);

    /** Places the character at `position` without disturbing the stored frames. */
    void place_character(entt::entity player, entt::registry* registry,
                         glm::vec3 position, bool clear_velocity);

public:

    void reset_at(uint32_t frame);

    uint64_t rollback_count() const { return m_rollback_count; }
    float last_correction_distance() const { return m_last_correction_distance; }
    uint32_t last_replayed_frames() const { return m_last_replayed_frames; }
    uint32_t last_ack_frame() const { return m_last_ack_frame; }
};

}
