#include <gtest/gtest.h>
#include <atomic>
#include <cstdint>
#include <functional>
#include <memory>
#include <mutex>
#include <thread>
#include <vector>
#include "renderive/real_time_data/Real_Time_Data.hpp"
#include "renderive/renderable/Renderable.hpp"
#include "renderive/renderable/Renderable_Test_Harness.hpp"
#include "renderive/scene/Scene.hpp"
#include "renderive/scene/Scene_Test_Helpers.hpp"
struct Real_Time_Data_Test_Time_Source {
    std::shared_ptr<std::atomic<std::uint64_t>> time_ns{std::make_shared<std::atomic<std::uint64_t>>()};
    std::uint64_t now_ns() const noexcept {
        return time_ns->fetch_add(10, std::memory_order_relaxed);
    }
};
struct Real_Time_Data_Test_Renderable : Renderable_Test_Harness {
    explicit Real_Time_Data_Test_Renderable() = default;
    void prepare_for_test(const Prepare_Render_Context&) override {
        ++render_count;
    }
    std::atomic<int> render_count{};
};
using Real_Time_Data_Test_Bridge = Observer_State<Frame_Strategy_Real_Time_Data_Observer, Real_Time_Data_Test_Time_Source>;
using Real_Time_Data_Test_Latest = Latest_Real_Time_Data<int, std::mutex, Real_Time_Data_Test_Bridge>;
using Real_Time_Data_Test_History = History_Real_Time_Data<int, std::vector<int>, std::mutex, Real_Time_Data_Test_Bridge>;
using Real_Time_Data_Test_Attachment = Attach_Real_Time_Data<Real_Time_Data_Test_Renderable, Real_Time_Data_Test_Latest, Real_Time_Data_Test_History>;
class Real_Time_Data_Render_Probe : public Renderable_Test_Harness {
    class Bound_Latest : public Latest_Real_Time_Data<int> {
    public:
        explicit Bound_Latest(Renderable_Base& owner)
            : binding_(bind_renderable(owner)) {}
    private:
        Real_Time_Data_Binding binding_;
    };
public:
    explicit Real_Time_Data_Render_Probe() : data(*this) {}
    void prepare_for_test(const Prepare_Render_Context& context) override {
        const auto& value = context.frame.render_state.get(data);
        rendered_value = value.value_or(-1);
    }
    Bound_Latest data;
    int rendered_value{-1};
};
TEST(real_time_data_render_state_test, publishes_once_at_frame_boundary_and_reads_without_snapshot) {
    Scene2D_Context<> scene;
    auto renderable = renderive_Owner<Real_Time_Data_Render_Probe>::make();
    {
        auto attach = scene.attach_builder();
        attach.attach(renderable);
    }
    renderable->data.update(1);
    scene.publish_frame_state();
    renderable->data.update(2);
    scene.render();
    scene.wait_for_render();
    EXPECT_EQ(renderable->rendered_value, 1);
    scene.publish_frame_state();
    scene.render();
    scene.wait_for_render();
    EXPECT_EQ(renderable->rendered_value, 2);
}
TEST(latest_real_time_data_test, retains_only_latest_value) {
    Latest_Real_Time_Data<int> data;
    data.update(1);
    data.update(2);
    ASSERT_TRUE(data.snapshot());
    EXPECT_EQ(*data.snapshot(), 2);
    EXPECT_EQ(data.update_state().retained_value_count, 1);
    EXPECT_EQ(data.revision(), 2);
}
TEST(history_real_time_data_test, retains_all_values_until_explicit_discard) {
    using Observer = Observer_State<Disabled_Observer, Real_Time_Data_Test_Time_Source>;
    History_Real_Time_Data<int, std::vector<int>, std::mutex, Observer> data;
    data.update(1);
    data.update(2);
    data.update(3);
    EXPECT_EQ(data.snapshot(), (std::vector<int>{1, 2, 3}));
    EXPECT_EQ(data.discard_before_time_ns(15), 2);
    EXPECT_EQ(data.snapshot(), (std::vector<int>{3}));
}
TEST(history_real_time_data_test, retains_latest_requested_value_count) {
    History_Real_Time_Data<int> data;
    data.update(1);
    data.update(2, 2);
    data.update(3, 2);
    EXPECT_EQ(data.snapshot(), (std::vector<int>{2, 3}));
    EXPECT_EQ(data.revision(), 3);
    EXPECT_EQ(data.retain_latest(2), 0);
    data.update(4);
    EXPECT_EQ(data.retain_latest(2), 1);
    EXPECT_EQ(data.snapshot(), (std::vector<int>{3, 4}));
}
TEST(real_time_data_attachment_test, binds_updates_to_renderable_frame_strategy) {
    Scene2D_Context<> scene;
    auto latest = std::make_shared<Real_Time_Data_Test_Latest>();
    auto history = std::make_shared<Real_Time_Data_Test_History>();
    auto renderable = renderive_Owner<Real_Time_Data_Test_Attachment>::make(With_Real_Time_Data(latest, history));
    renderable->discard_stale_frame_on_latest_data_update(true);
    {
        auto attach = scene.attach_builder();
        attach.attach(renderable);
    }
    {
        auto frame = scene.frame_control.acquire_painter();
        ASSERT_TRUE(frame);
    }
    latest->update(1);
    EXPECT_EQ(scene.frame_control.counter_statistics().manually_discarded_frame_count, 1);
    auto frame = scene.frame_control.acquire_renderer();
    EXPECT_FALSE(frame);
}
TEST(real_time_data_attachment_test, discards_history_older_than_one_frame_interval) {
    using Strategy = Low_Latency_Strategy<Scene2D_Frame_Data>;
    Scene2D_Context<Strategy> scene(Observer_State<>{}, Strategy::Configuration{100'000'000.0});
    auto latest = std::make_shared<Real_Time_Data_Test_Latest>();
    auto history = std::make_shared<Real_Time_Data_Test_History>();
    auto renderable = renderive_Owner<Real_Time_Data_Test_Attachment>::make(With_Real_Time_Data(latest, history));
    renderable->real_time_data_discard_mode = Real_Time_Data_Discard_Mode::retain_frame_interval;
    history->update(1);
    history->update(2);
    history->update(3);
    {
        auto attach = scene.attach_builder();
        attach.attach(renderable);
    }
    scene.render();
    scene.wait_for_render();
    EXPECT_EQ(history->snapshot(), (std::vector<int>{2, 3}));
    EXPECT_EQ(renderable->render_count.load(), 1);
}
TEST(real_time_data_attachment_test, rejects_null_real_time_data_source) {
    std::shared_ptr<Real_Time_Data_Test_Latest> latest;
    EXPECT_THROW((With_Real_Time_Data(latest)), std::invalid_argument);
}
using Real_Time_Data_Test_Latest_Attachment = Attach_Real_Time_Data<Real_Time_Data_Test_Renderable, Real_Time_Data_Test_Latest>;
TEST(real_time_data_attachment_test, rejects_binding_one_source_to_multiple_renderables) {
    auto latest = std::make_shared<Real_Time_Data_Test_Latest>();
    auto first = renderive_Owner<Real_Time_Data_Test_Latest_Attachment>::make(With_Real_Time_Data(latest));
    EXPECT_THROW((renderive_Owner<Real_Time_Data_Test_Latest_Attachment>::make(With_Real_Time_Data(latest))), std::logic_error);
    first.reset();
    EXPECT_NO_THROW((renderive_Owner<Real_Time_Data_Test_Latest_Attachment>::make(With_Real_Time_Data(latest))));
}
TEST(real_time_data_attachment_test, detached_renderable_no_longer_drives_frame_strategy) {
    Scene2D_Context<> scene;
    auto latest = std::make_shared<Real_Time_Data_Test_Latest>();
    auto renderable = renderive_Owner<Real_Time_Data_Test_Latest_Attachment>::make(With_Real_Time_Data(latest));
    renderable->discard_stale_frame_on_latest_data_update(true);
    {
        auto attach = scene.attach_builder();
        attach.attach(renderable);
    }
    {
        auto frame = scene.frame_control.acquire_painter();
        ASSERT_TRUE(frame);
    }
    latest->update(1);
    EXPECT_EQ(scene.frame_control.counter_statistics().manually_discarded_frame_count, 1);
    wait_renderable_edit(scene, [renderable](auto& editor) {
        editor.detach(renderable);
    });
    {
        auto frame = scene.frame_control.acquire_painter();
        ASSERT_TRUE(frame);
    }
    latest->update(2);
    EXPECT_EQ(scene.frame_control.counter_statistics().manually_discarded_frame_count, 1);
}
TEST(real_time_data_attachment_test, unbind_is_synchronized_with_concurrent_updates) {
    Scene2D_Context<> scene;
    auto latest = std::make_shared<Real_Time_Data_Test_Latest>();
    auto renderable = renderive_Owner<Real_Time_Data_Test_Latest_Attachment>::make(With_Real_Time_Data(latest));
    std::atomic<bool> running{true};
    std::atomic<bool> updated{};
    std::thread updater([&] {
        int value{};
        while (running.load(std::memory_order_acquire)) {
            latest->update(++value);
            updated.store(true, std::memory_order_release);
        }
    });
    while (!updated.load(std::memory_order_acquire)) {
        std::this_thread::yield();
    }
    renderable.reset();
    running.store(false, std::memory_order_release);
    updater.join();
    EXPECT_TRUE(latest->revision() > 0);
}
TEST(real_time_data_attachment_test, runtime_discard_configuration_is_safe_during_updates_and_rendering) {
    Scene2D_Context<> scene;
    auto latest = std::make_shared<Real_Time_Data_Test_Latest>();
    auto history = std::make_shared<Real_Time_Data_Test_History>();
    auto renderable = renderive_Owner<Real_Time_Data_Test_Attachment>::make(With_Real_Time_Data(latest, history));
    {
        auto attach = scene.attach_builder();
        attach.attach(renderable);
    }
    std::thread updater([&] {
        for (int value = 0; value < 1000; ++value) {
            latest->update(value);
        }
    });
    std::thread configurator([&] {
        for (int index = 0; index < 1000; ++index) {
            renderable->discard_stale_frame_on_latest_data_update(
                index % 2 != 0);
            renderable->real_time_data_discard_mode = index % 2 == 0 ? Real_Time_Data_Discard_Mode::retain_all : Real_Time_Data_Discard_Mode::retain_frame_interval;
        }
    });
    for (int index = 0; index < 20; ++index) {
        scene.render();
        scene.wait_for_render();
    }
    updater.join();
    configurator.join();
    EXPECT_EQ(latest->revision(), 1000);
}
TEST(real_time_data_attachment_test, ignores_updates_after_bound_scene_is_destroyed) {
    auto latest = std::make_shared<Real_Time_Data_Test_Latest>();
    renderive_Owner<Real_Time_Data_Test_Latest_Attachment> renderable;
    {
        Scene2D_Context<> scene;
        renderable = renderive_Owner<Real_Time_Data_Test_Latest_Attachment>::make(With_Real_Time_Data(latest));
        {
            auto attach = scene.attach_builder();
            attach.attach(renderable);
        }
        latest->update(1);
        EXPECT_EQ(scene.frame_control.state().real_time_data_update_sequence, 1);
    }
    EXPECT_NO_THROW(latest->update(2));
    EXPECT_THROW(renderable->scene_for_test(), std::logic_error);
    renderable.reset();
}
TEST(real_time_data_attachment_test, source_binding_is_owned_by_renderable_lifetime) {
    auto latest = std::make_shared<Real_Time_Data_Test_Latest>();
    auto renderable = renderive_Owner<Real_Time_Data_Test_Latest_Attachment>::make(With_Real_Time_Data(latest));
    EXPECT_THROW((renderive_Owner<Real_Time_Data_Test_Latest_Attachment>::make(With_Real_Time_Data(latest))), std::logic_error);
    renderable.reset();
    EXPECT_NO_THROW((renderive_Owner<Real_Time_Data_Test_Latest_Attachment>::make(With_Real_Time_Data(latest))));
}
TEST(real_time_data_attachment_test, attachment_owns_real_time_data_sources) {
    Scene2D_Context<> scene;
    auto latest = std::make_shared<Real_Time_Data_Test_Latest>();
    std::weak_ptr<Real_Time_Data_Test_Latest> weak = latest;
    auto renderable = renderive_Owner<Real_Time_Data_Test_Latest_Attachment>::make(With_Real_Time_Data(latest));
    latest.reset();
    EXPECT_FALSE(weak.expired());
    renderable->real_time_data<Real_Time_Data_Test_Latest>().update(1);
    renderable.reset();
    EXPECT_TRUE(weak.expired());
}
struct Real_Time_Data_Reentrant_Frame_Observer_Data {
    std::function<void()> callback;
};
struct Real_Time_Data_Reentrant_Frame_Observer {
    static constexpr bool enabled = true;
    std::shared_ptr<Real_Time_Data_Reentrant_Frame_Observer_Data> data{std::make_shared<Real_Time_Data_Reentrant_Frame_Observer_Data>()};
    template <class Observation>
    void observe(const Observation&) noexcept {
        if (data->callback) {
            data->callback();
        }
    }
};
using Real_Time_Data_Reentrant_Frame_Observer_State = Observer_State<Real_Time_Data_Reentrant_Frame_Observer, Real_Time_Data_Test_Time_Source>;
using Real_Time_Data_Reentrant_Strategy = Low_Latency_Strategy<Scene2D_Frame_Data, std::mutex, Real_Time_Data_Reentrant_Frame_Observer_State>;
TEST(real_time_data_attachment_test, frame_strategy_observer_can_reacquire_scene_lifetime_without_deadlock) {
    Real_Time_Data_Reentrant_Frame_Observer observer;
    auto observer_data = observer.data;
    Real_Time_Data_Reentrant_Frame_Observer_State observer_state(observer, Real_Time_Data_Test_Time_Source{});
    Real_Time_Data_Reentrant_Strategy::Configuration configuration{60.0};
    Scene2D_Context<Real_Time_Data_Reentrant_Strategy> scene(std::move(observer_state), configuration);
    auto latest = std::make_shared<Real_Time_Data_Test_Latest>();
    auto renderable = renderive_Owner<Real_Time_Data_Test_Latest_Attachment>::make(With_Real_Time_Data(latest));
    {
        auto attach = scene.attach_builder();
        attach.attach(renderable);
    }
    std::atomic<bool> reacquired{};
    observer_data->callback = [&] {
        reacquired.store(&renderable->scene_for_test() == &scene,
                         std::memory_order_release);
    };
    latest->update(1);
    EXPECT_TRUE(reacquired.load(std::memory_order_acquire));
}
class Real_Time_Data_Failing_Memory_Resource : public std::pmr::memory_resource {
private:
    void* do_allocate(std::size_t, std::size_t) override {
        throw std::bad_alloc();
    }
    void do_deallocate(void*, std::size_t, std::size_t) override {}
    bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override {
        return this == &other;
    }
};
TEST(history_real_time_data_test, failed_timestamp_allocation_rolls_back_value_and_revision) {
    // MSVC's debug vector allocates a proxy at construction, so the production
    // container must defer binding this deliberately failing resource to update().
    Real_Time_Data_Failing_Memory_Resource memory_resource;
    History_Real_Time_Data<int> data(memory_resource);
    EXPECT_THROW(data.update(7), std::bad_alloc);
    EXPECT_TRUE(data.snapshot().empty());
    EXPECT_EQ(data.revision(), 0);
    const auto state = data.update_state();
    EXPECT_EQ(state.total_update_count, 0);
    EXPECT_EQ(state.retained_value_count, 0);
    EXPECT_EQ(state.update_time_ns, 0);
}
static_assert(Unique_Real_Time_Data_Types<Real_Time_Data_Test_Latest, Real_Time_Data_Test_History>);
static_assert(!Unique_Real_Time_Data_Types<Real_Time_Data_Test_Latest, Real_Time_Data_Test_Latest>);
