#include <gtest/gtest.h>
#include <algorithm>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <memory>
#include <mutex>
#include <thread>
#include <vector>
#include "renderive/renderable/Renderable.hpp"
#include "renderive/renderable/Renderable_Test_Harness.hpp"
#include "renderive/scene/Scene.hpp"
#include "Scene_Test_Helpers.hpp"
namespace {
using namespace std::chrono_literals;
class Gate {
public:
    void arrive_and_wait() {
        std::unique_lock lock(mutex_);
        arrived_ = true;
        condition_.notify_all();
        condition_.wait(lock, [this] { return open_; });
    }
    void wait_until_arrived() {
        std::unique_lock lock(mutex_);
        ASSERT_TRUE(condition_.wait_for(lock, 2s, [this] { return arrived_; }));
    }
    void open() {
        std::lock_guard lock(mutex_);
        open_ = true;
        condition_.notify_all();
    }
private:
    std::mutex mutex_;
    std::condition_variable condition_;
    bool arrived_{};
    bool open_{};
};
template <class Predicate>
bool wait_until(Predicate&& predicate) {
    const auto deadline = std::chrono::steady_clock::now() + 2s;
    while (!predicate()) {
        if (std::chrono::steady_clock::now() >= deadline)
            return false;
        std::this_thread::yield();
    }
    return true;
}
class Counting_Renderable : public Renderable_Test_Harness {
public:
    Counting_Renderable()
        : Renderable_Test_Harness({.cache_enabled = false}) {}
    std::atomic<int> prepare_count{};
protected:
    void prepare_for_test(const Prepare_Render_Context&) override {
        prepare_count.fetch_add(1, std::memory_order_release);
    }
};
class Blocking_Renderable : public Counting_Renderable {
public:
    Blocking_Renderable(std::shared_ptr<Gate> gate, int block_on_call = 1, std::atomic<bool>* destroyed = nullptr)
        : gate_(std::move(gate)), block_on_call_(block_on_call), destroyed_(destroyed) {}
    ~Blocking_Renderable() override {
        if (destroyed_)
            destroyed_->store(true, std::memory_order_release);
    }
protected:
    void prepare_for_test(const Prepare_Render_Context&) override {
        const int call = prepare_count.fetch_add(1, std::memory_order_acq_rel) + 1;
        if (call == block_on_call_)
            gate_->arrive_and_wait();
    }
private:
    std::shared_ptr<Gate> gate_;
    int block_on_call_{};
    std::atomic<bool>* destroyed_{};
};
TEST(dynamic_renderable_lifecycle_test, edit_waits_until_current_frame_finishes) {
    Scene2D_Context<> scene;
    auto gate = std::make_shared<Gate>();
    auto current = renderive_Owner<Blocking_Renderable>::make(gate);
    auto added = renderive_Owner<Counting_Renderable>::make();
    attach_initial(scene, current);
    scene.render();
    gate->wait_until_arrived();
    std::atomic<bool> edited{};
    scene.edit_renderables([added, &edited](auto& editor) {
        editor.attach(added);
        edited.store(true, std::memory_order_release);
    });
    EXPECT_FALSE(edited.load(std::memory_order_acquire));
    gate->open();
    scene.wait_for_render();
    EXPECT_TRUE(wait_until([&] { return edited.load(std::memory_order_acquire); }));
    EXPECT_EQ(added->prepare_count.load(std::memory_order_acquire), 0);
    scene.render();
    scene.wait_for_render();
    EXPECT_EQ(added->prepare_count.load(std::memory_order_acquire), 1);
}
TEST(dynamic_renderable_lifecycle_test, detach_waits_until_current_frame_finishes_before_releasing_scene_ownership) {
    Scene2D_Context<> scene;
    auto gate = std::make_shared<Gate>();
    std::atomic<bool> destroyed{};
    auto renderable = renderive_Owner<Blocking_Renderable>::make(gate, 1, &destroyed);
    std::weak_ptr<Blocking_Renderable> weak = renderable.share();
    attach_initial(scene, renderable);
    scene.render();
    gate->wait_until_arrived();
    std::atomic<bool> edited{};
    scene.edit_renderables([renderable, &edited](auto& editor) {
        editor.detach(renderable);
        edited.store(true, std::memory_order_release);
    });
    renderable.reset();
    EXPECT_FALSE(destroyed.load(std::memory_order_acquire));
    gate->open();
    scene.wait_for_render();
    EXPECT_TRUE(wait_until([&] { return edited.load(std::memory_order_acquire); }));
    EXPECT_TRUE(wait_until([&] { return destroyed.load(std::memory_order_acquire); }));
    EXPECT_TRUE(weak.expired());
    EXPECT_EQ(scene.renderable_count(), 0u);
}
TEST(dynamic_renderable_lifecycle_test, reparent_edit_changes_only_the_next_frame_dependency_plan) {
    Scene2D_Context<> scene;
    auto first_gate = std::make_shared<Gate>();
    auto second_gate = std::make_shared<Gate>();
    auto first_parent = renderive_Owner<Blocking_Renderable>::make(first_gate, 1);
    auto second_parent = renderive_Owner<Blocking_Renderable>::make(second_gate, 2);
    auto child = renderive_Owner<Counting_Renderable>::make();
    {
        auto attach = scene.attach_builder();
        attach.attach(first_parent);
        attach.attach(second_parent);
        attach.attach(child);
        attach.set_dependency_parent(child, first_parent);
    }
    scene.render();
    first_gate->wait_until_arrived();
    std::atomic<bool> edited{};
    scene.edit_renderables([child, second_parent, &edited](auto& editor) {
        editor.set_dependency_parent(child, second_parent);
        edited.store(true, std::memory_order_release);
    });
    EXPECT_EQ(child->prepare_count.load(std::memory_order_acquire), 0);
    first_gate->open();
    scene.wait_for_render();
    EXPECT_TRUE(wait_until([&] { return edited.load(std::memory_order_acquire); }));
    EXPECT_EQ(child->prepare_count.load(std::memory_order_acquire), 1);
    scene.render();
    second_gate->wait_until_arrived();
    EXPECT_EQ(child->prepare_count.load(std::memory_order_acquire), 1);
    second_gate->open();
    scene.wait_for_render();
    EXPECT_EQ(child->prepare_count.load(std::memory_order_acquire), 2);
}
class Rebuildable_Renderable final : public Renderable_Test_Harness {
public:
    explicit Rebuildable_Renderable(std::shared_ptr<Gate> first_graph_gate)
        : Renderable_Test_Harness({.cache_enabled = false}), first_graph_gate_(std::move(first_graph_gate)) {}
    void publish_second_graph() {
        graph_version_.store(2, std::memory_order_release);
        rebuild_render_graph_for_test();
    }
    std::vector<int> executions() const {
        std::lock_guard lock(executions_mutex_);
        return executions_;
    }
protected:
    void build_prepare_graph_for_test(Renderable_Graph_Builder& builder) override {
        const int version = graph_version_.load(std::memory_order_acquire);
        builder.emplace("versioned_prepare", version == 1 ? "Graph A" : "Graph B", [this, version](const Prepare_Render_Context&) {
            if (version == 1)
                first_graph_gate_->arrive_and_wait();
            std::lock_guard lock(executions_mutex_);
            executions_.push_back(version);
        });
    }
private:
    std::shared_ptr<Gate> first_graph_gate_;
    std::atomic<int> graph_version_{1};
    mutable std::mutex executions_mutex_;
    std::vector<int> executions_;
};
TEST(dynamic_renderable_lifecycle_test, graph_rebuild_uses_the_same_no_inflight_frame_barrier) {
    Scene2D_Context<> scene;
    auto gate = std::make_shared<Gate>();
    auto renderable = renderive_Owner<Rebuildable_Renderable>::make(gate);
    attach_initial(scene, renderable);
    scene.render();
    gate->wait_until_arrived();
    renderable->publish_second_graph();
    gate->open();
    scene.wait_for_render();
    EXPECT_EQ(renderable->executions(), std::vector<int>({1}));
    scene.render();
    scene.wait_for_render();
    EXPECT_EQ(renderable->executions(), std::vector<int>({1, 2}));
}
class Sequence_Parent final : public Renderable_Test_Harness {
public:
    explicit Sequence_Parent(std::atomic<std::uint64_t>& completed_sequence)
        : Renderable_Test_Harness({.cache_enabled = false}), completed_sequence_(completed_sequence) {}
protected:
    void prepare_for_test(const Prepare_Render_Context& context) override {
        completed_sequence_.store(context.frame.render_sequence, std::memory_order_release);
    }
private:
    std::atomic<std::uint64_t>& completed_sequence_;
};
class Sequence_Child final : public Renderable_Test_Harness {
public:
    Sequence_Child(std::atomic<std::uint64_t>& parent_sequence, std::atomic<int>& violations)
        : Renderable_Test_Harness({.cache_enabled = false}), parent_sequence_(parent_sequence), violations_(violations) {}
protected:
    void prepare_for_test(const Prepare_Render_Context& context) override {
        if (parent_sequence_.load(std::memory_order_acquire) != context.frame.render_sequence)
            violations_.fetch_add(1, std::memory_order_relaxed);
    }
private:
    std::atomic<std::uint64_t>& parent_sequence_;
    std::atomic<int>& violations_;
};
TEST(dynamic_renderable_lifecycle_test, one_runtime_callback_can_apply_a_complete_relationship_change) {
    Scene2D_Context<> scene;
    std::atomic<std::uint64_t> parent_sequence{};
    std::atomic<int> violations{};
    auto parent = renderive_Owner<Sequence_Parent>::make(parent_sequence);
    auto child = renderive_Owner<Sequence_Child>::make(parent_sequence, violations);
    attach_initial(scene, parent);
    wait_renderable_edit(scene, [parent, child](auto& editor) {
        editor.attach(child);
        editor.set_dependency_parent(child, parent);
        editor.set_display_parent(child, parent);
    });
    scene.render();
    scene.wait_for_render();
    EXPECT_EQ(violations.load(std::memory_order_acquire), 0);
    wait_renderable_edit(scene, [child](auto& editor) {
        editor.clear_dependency_parent(child);
        editor.clear_display_parent(child);
        editor.detach(child);
    });
    EXPECT_EQ(scene.renderable_count(), 1u);
}
TEST(dynamic_renderable_lifecycle_test, queued_edits_and_render_submission_preserve_complete_topologies) {
    Scene2D_Context<> scene;
    std::atomic<std::uint64_t> parent_sequence{};
    std::atomic<int> violations{};
    auto parent = renderive_Owner<Sequence_Parent>::make(parent_sequence);
    attach_initial(scene, parent);
    constexpr int iterations = 200;
    for (int index = 0; index < iterations; ++index) {
        auto child = renderive_Owner<Sequence_Child>::make(parent_sequence, violations);
        scene.edit_renderables([parent, child](auto& editor) {
            editor.attach(child);
            editor.set_dependency_parent(child, parent);
            editor.set_display_parent(child, parent);
        });
        scene.edit_renderables([child](auto& editor) {
            editor.clear_dependency_parent(child);
            editor.clear_display_parent(child);
            editor.detach(child);
        });
    }
    scene.render();
    scene.wait_for_render();
    EXPECT_EQ(violations.load(std::memory_order_acquire), 0);
    EXPECT_EQ(scene.renderable_count(), 1u);
}
TEST(dynamic_renderable_lifecycle_test, captures_before_and_after_runtime_attach_keep_both_plan_versions_resolvable) {
    Scene2D_Context<> scene;
    auto first = renderive_Owner<Counting_Renderable>::make();
    auto second = renderive_Owner<Counting_Renderable>::make();
    attach_initial(scene, first);
    const Capture_Session_Id session_id = scene.capture_frames(2);
    scene.render();
    scene.wait_for_render();
    wait_renderable_edit(scene, [second](auto& editor) {
        editor.attach(second);
    });
    scene.render();
    scene.wait_for_render();
    const auto session = scene.capture_session(session_id);
    ASSERT_TRUE(session);
    ASSERT_EQ(session->frames.size(), 2u);
    const auto first_version = session->frames[0].snapshot->render_plan_version;
    const auto second_version = session->frames[1].snapshot->render_plan_version;
    EXPECT_NE(first_version, second_version);
    const auto first_plan = scene.find_render_plan(first_version);
    const auto second_plan = scene.find_render_plan(second_version);
    ASSERT_TRUE(first_plan);
    ASSERT_TRUE(second_plan);
    EXPECT_TRUE(std::ranges::none_of(first_plan->graph.nodes, [&](const Render_Node& node) {
        return node.owner_id == second->renderable_id();
    }));
    EXPECT_TRUE(std::ranges::any_of(second_plan->graph.nodes, [&](const Render_Node& node) {
        return node.owner_id == second->renderable_id();
    }));
}
}
