#include <gtest/gtest.h>
#include <atomic>
#include <cstdint>
#include <functional>
#include <memory>
#include <vector>
#include "renderive/scene/Scene.hpp"
#include "../state/State_Test_Types.hpp"
struct Scene_State_Observer_Test_State : Scene2D_State {
    int value{};
};
struct Scene_State_Observer_Test_Time_Source {
    std::uint64_t now_ns() const noexcept {
        return 100;
    }
};
struct Scene_State_Observer_Test_Data {
    std::vector<Scene_Base::Observation_Event> events;
};
struct Scene_State_Observer_Test_Recorder {
    static constexpr bool enabled = true;
    std::shared_ptr<Scene_State_Observer_Test_Data> data{std::make_shared<Scene_State_Observer_Test_Data>()};
    void observe(const Scene_Base::Observation& observation) noexcept {
        data->events.push_back(observation.event);
    }
};
using Scene_State_Observer_Test_Scene_Observer = Observer_State<Scene_State_Observer_Test_Recorder, Scene_State_Observer_Test_Time_Source>;
using Scene_State_Observer_Test_State_Observer = Observer_State<>;
TEST(scene_state_observer_test, scene_uses_double_state_and_observes_lifecycle) {
    State_Render_State_Reader render;
    Scene_State_Observer_Test_Recorder recorder;
    using Scene = Scene2D_Context<Low_Latency_Strategy<Scene2D_Frame_Data>, Recording_Color_Cache, Scene_State_Observer_Test_State, Scene_State_Observer_Test_State_Observer, Scene_State_Observer_Test_Scene_Observer>;
    Scene scene(With_Observer(Scene_State_Observer_Test_State_Observer{}), With_Observer(Scene_State_Observer_Test_Scene_Observer(recorder, Scene_State_Observer_Test_Time_Source{})));
    scene.Scene_State_Strategy::set<&Scene_State_Observer_Test_State::value>(9);
    scene.Scene_State_Strategy::publish();
    scene.render();
    scene.wait_for_render();
    EXPECT_EQ(render.read(static_cast<const Scene::Scene_State_Strategy&>(scene)).value, 9);
    EXPECT_EQ(recorder.data->events, (std::vector<Scene_Base::Observation_Event>{Scene_Base::Observation_Event::render_submitted, Scene_Base::Observation_Event::render_started, Scene_Base::Observation_Event::render_completed}));
}
struct Scene_Reentrant_Observer_Data {
    std::function<void(Scene_Base::Observation_Event)> callback;
};
struct Scene_Reentrant_Observer {
    static constexpr bool enabled = true;
    std::shared_ptr<Scene_Reentrant_Observer_Data> data{std::make_shared<Scene_Reentrant_Observer_Data>()};
    void observe(const Scene_Base::Observation& observation) noexcept {
        if (data->callback) {
            data->callback(observation.event);
        }
    }
};
using Scene_Reentrant_Observer_State = Observer_State<Scene_Reentrant_Observer, Scene_State_Observer_Test_Time_Source>;
TEST(scene_state_observer_test, render_worker_observer_can_wait_for_current_render_without_deadlock) {
    Scene_Reentrant_Observer recorder;
    auto data = recorder.data;
    using Scene = Scene2D_Context<Low_Latency_Strategy<Scene2D_Frame_Data>, Recording_Color_Cache, Scene_State_Observer_Test_State, Scene_State_Observer_Test_State_Observer, Scene_Reentrant_Observer_State>;
    Scene scene(With_Observer(Scene_State_Observer_Test_State_Observer{}), With_Observer(Scene_Reentrant_Observer_State(recorder, Scene_State_Observer_Test_Time_Source{})));
    std::atomic<bool> reentered{};
    data->callback = [&](Scene_Base::Observation_Event event) {
        if (event == Scene_Base::Observation_Event::render_started) {
            scene.wait_for_render();
            reentered.store(true, std::memory_order_release);
        }
    };
    scene.render();
    scene.wait_for_render();
    EXPECT_TRUE(reentered.load(std::memory_order_acquire));
}
TEST(scene_state_observer_test, render_submitted_observer_can_submit_next_render_without_deadlock) {
    Scene_Reentrant_Observer recorder;
    auto data = recorder.data;
    using Scene = Scene2D_Context<Low_Latency_Strategy<Scene2D_Frame_Data>, Recording_Color_Cache, Scene_State_Observer_Test_State, Scene_State_Observer_Test_State_Observer, Scene_Reentrant_Observer_State>;
    Scene scene(With_Observer(Scene_State_Observer_Test_State_Observer{}), With_Observer(Scene_Reentrant_Observer_State(recorder, Scene_State_Observer_Test_Time_Source{})));
    std::atomic<int> submitted{};
    data->callback = [&](Scene_Base::Observation_Event event) {
        if (event != Scene_Base::Observation_Event::render_submitted) {
            return;
        }
        if (submitted.fetch_add(1, std::memory_order_acq_rel) == 0) {
            scene.render();
        }
    };
    scene.render();
    scene.wait_for_render();
    EXPECT_EQ(submitted.load(std::memory_order_acquire), 2);
}
