#pragma once

#include "app.h"
#include "hmd.h"
#include "log.h"

#include <atomic>
#include <condition_variable>
#include <functional>
#include <memory>
#include <mutex>
#include <thread>

namespace pp::platform::windows {

[[nodiscard]] App* bound_app() noexcept;

struct VrShellState final {
    std::mutex snapshot_mutex;
    VrSessionSnapshot session;
    std::jthread hmd_renderer;
    std::mutex hmd_render_mutex;
    std::condition_variable hmd_render_cv;
    std::unique_ptr<Vive> vive;
    std::atomic<int> vr_frames{0};
    std::atomic_bool vr_running{false};
};

inline int current_vr_fps(const VrShellState& state)
{
    return state.vr_frames.load(std::memory_order_relaxed);
}

inline VrSessionSnapshot read_vr_session_snapshot(VrShellState& state)
{
    std::lock_guard<std::mutex> lock(state.snapshot_mutex);
    return state.session;
}

inline void write_vr_session_snapshot(
    VrShellState& state,
    bool has_vr,
    bool vr_active,
    const VRController& primary_controller,
    const glm::mat4& vr_head)
{
    std::lock_guard<std::mutex> lock(state.snapshot_mutex);
    state.session.has_vr = has_vr;
    state.session.vr_active = vr_active;
    state.session.vr_controllers[0] = primary_controller;
    state.session.vr_head = vr_head;
}

inline void clear_vr_session_snapshot(VrShellState& state, bool has_vr)
{
    std::lock_guard<std::mutex> lock(state.snapshot_mutex);
    state.session.has_vr = has_vr;
    state.session.vr_active = false;
    state.session.vr_controllers[0] = {};
    state.session.vr_head = glm::mat4(1.0f);
}

inline void request_stop_and_join_vr_thread(VrShellState& state)
{
    if (state.hmd_renderer.joinable())
    {
        state.hmd_renderer.request_stop();
        state.hmd_renderer.join();
    }
}

inline bool start_vr_shell(VrShellState& state, bool sandboxed, std::atomic<int>& running)
{
    if (sandboxed)
        return false;

    auto* app = bound_app();
    state.vive = std::make_unique<Vive>();
    state.vive->on_draw = [app](const glm::mat4& proj, const glm::mat4& view, const glm::mat4& pose) {
        app->vr_draw(proj, view, pose);
    };
    if (!state.vive->Initialize())
    {
        state.vive.reset();
        LOG("VR: failed to initialize vive");
        return false;
    }

    if (state.hmd_renderer.joinable())
    {
        state.hmd_renderer.request_stop();
        state.hmd_renderer.join();
    }

    state.hmd_renderer = std::jthread([app, &state, &running](std::stop_token stop_token) {
        if (!state.vive)
            return;

        BT_SetTerminate();
        LOG("start hmd render thread");
        clear_vr_session_snapshot(state, true);
        state.vr_running.store(true, std::memory_order_relaxed);

        state.vive->on_analog_button = std::bind(&App::vr_analog, app, std::placeholders::_1,
            std::placeholders::_2, std::placeholders::_3, std::placeholders::_4);
        state.vive->on_button = std::bind(&App::vr_digital, app, std::placeholders::_1,
            std::placeholders::_2, std::placeholders::_3, std::placeholders::_4);

        app->render_task([app] {
            app->vr_draw_ui();
        });

        const float target_tick_rate = 90;
        auto t0 = GetTickCount64();
        float one_sec_timer = 0;
        int frames = 0;
        while (!stop_token.stop_requested() &&
            state.vr_running.load(std::memory_order_relaxed) &&
            running.load(std::memory_order_relaxed) == 1 &&
            state.vive->Valid())
        {
            std::unique_lock<std::mutex> lock(state.hmd_render_mutex);
            auto t1 = GetTickCount64();
            float dt = (float)(t1 - t0) / 1000.0f;

            one_sec_timer += dt;
            if (one_sec_timer >= 1.f)
            {
                one_sec_timer = 0;
                state.vr_frames.store(frames, std::memory_order_relaxed);
                frames = 0;
            }
            frames++;

            state.vive->Update();
            write_vr_session_snapshot(
                state,
                true,
                state.vive->m_active,
                state.vive->m_controllers[0],
                state.vive->m_pose);
            app->vr_update(dt);

            if (state.vr_running.load(std::memory_order_relaxed) && state.vive->m_active)
            {
                app->render_task([&state] {
                    state.vive->Draw();
                });
            }

            const int framerate = (1.f / target_tick_rate) * 1000;
            const int diff = framerate - (t1 - t0);
            (void)diff;
            //state.hmd_render_cv.wait_for(lock, std::chrono::milliseconds(diff));
            t0 = t1;
        }

        clear_vr_session_snapshot(state, false);
        state.vr_running.store(false, std::memory_order_relaxed);
        state.vive->Terminate();
        LOG("hmd renderer terminated");
    });
    return true;
}

inline void stop_vr_shell(VrShellState& state)
{
    if (state.vive)
    {
        state.vr_running.store(false, std::memory_order_relaxed);
        request_stop_and_join_vr_thread(state);
        state.vive->Terminate();
        state.vive.reset();
    }
}

}
