#include "Renderable_Base.hpp"
#include <algorithm>
#include <limits>
#include <stdexcept>
#include "renderive/real_time_data/base/Real_Time_Data_Base.hpp"
#include "renderive/scene/base/Scene_Base.hpp"
namespace {
void advance_revision(std::atomic<std::uint64_t>& revision) noexcept {
    std::uint64_t value = revision.load(std::memory_order_relaxed);
    while (value != std::numeric_limits<std::uint64_t>::max() &&
           !revision.compare_exchange_weak(value, value + 1,
                                           std::memory_order_release,
                                           std::memory_order_relaxed)) {}
}
}
Renderable_Base::Renderable_Base(Scene_Base& scene, Renderable_Configuration configuration)
    : memory_domain_(scene.memory_domain_), real_time_data_state_(std::allocate_shared<Real_Time_Data_State>(Scene_Memory_Allocator<Real_Time_Data_State>{memory_domain_}, scene.scene_lifetime_)), discard_stale_frame_on_latest_data_update(real_time_data_state_->discard_stale_frame_on_latest_data_update), renderable_id_(scene.allocate_renderable_id()), composite_node_id_(scene.allocate_node_id()), configuration_(configuration) {}
Renderable_Base::~Renderable_Base() {
    real_time_data_state_->attached.store(false, std::memory_order_release);
    real_time_data_state_->renderable_alive.store(false, std::memory_order_release);
}
void Renderable_Base::invalidate_prepare() noexcept {
    advance_revision(prepare_revision_);
}
void Renderable_Base::invalidate_paint() noexcept {
    advance_revision(paint_revision_);
}
std::uint64_t Renderable_Base::prepare_revision() const noexcept {
    return prepare_revision_.load(std::memory_order_acquire);
}
std::uint64_t Renderable_Base::prepared_revision() const noexcept {
    return prepared_revision_.load(std::memory_order_acquire);
}
std::uint64_t Renderable_Base::paint_revision() const noexcept {
    return paint_revision_.load(std::memory_order_acquire);
}
std::uint64_t Renderable_Base::painted_revision() const noexcept {
    return painted_revision_.load(std::memory_order_acquire);
}
bool Renderable_Base::prepare_cache_valid() const noexcept {
    const std::uint64_t revision = prepare_revision();
    return revision != std::numeric_limits<std::uint64_t>::max() &&
           configuration().cache_enabled && prepared_revision() == revision;
}
bool Renderable_Base::paint_cache_valid() const noexcept {
    const std::uint64_t revision = paint_revision();
    return revision != std::numeric_limits<std::uint64_t>::max() &&
           configuration().cache_enabled && prepare_cache_valid() &&
           painted_revision() == revision &&
           painted_prepare_revision_.load(std::memory_order_acquire) == prepared_revision();
}
void Renderable_Base::rebuild_render_graph() {
    scene().request_render_graph_rebuild(*this);
}
void Renderable_Base::reset_render_graph() noexcept {
    {
        std::lock_guard lock(render_graph_mutex_);
        render_graph_.reset();
    }
    invalidate_prepare();
}
std::shared_ptr<const Renderable_Graph> Renderable_Base::render_graph() {
    std::lock_guard lock(render_graph_mutex_);
    if (!render_graph_) {
        std::unordered_map<std::string, Render_Node_Id> next_identities;
        Renderable_Graph_Builder builder(renderable_id_, [this, &next_identities](
                std::string_view key) {
            const std::string logical_key(key);
            if (next_identities.contains(logical_key))
                throw std::invalid_argument("duplicate render node logical key");
            const auto current = node_identities_.find(logical_key);
            const Render_Node_Id node_id = current == node_identities_.end()
                ? scene().allocate_node_id()
                : current->second;
            next_identities.emplace(logical_key, node_id);
            return node_id;
        });
        build_prepare_graph(builder);
        build_paint_graph(builder);
        auto graph = std::make_shared<Renderable_Graph>(std::move(builder).finish());
        node_identities_ = std::move(next_identities);
        render_graph_ = std::move(graph);
    }
    return render_graph_;
}
Renderable_Id Renderable_Base::renderable_id() const noexcept {
    return renderable_id_;
}
std::pmr::memory_resource& Renderable_Base::memory_resource() const noexcept {
    return memory_domain_->resource();
}
Scene_Base& Renderable_Base::scene() const {
    auto lease = real_time_data_state_->scene_lifetime->acquire();
    if (!lease) {
        throw std::logic_error("renderable scene is no longer alive");
    }
    return lease.scene();
}
Renderable_Configuration Renderable_Base::configuration() const noexcept {
    std::lock_guard lock(configuration_mutex_);
    return configuration_;
}
bool Renderable_Base::is_visible() const noexcept {
    std::lock_guard lock(configuration_mutex_);
    return visible_;
}
void Renderable_Base::set_visible(bool visible) {
    {
        std::lock_guard lock(configuration_mutex_);
        if (visible_ == visible)
            return;
        visible_ = visible;
        invalidate_prepare();
    }
    scene().notify_model_dirty();
}
void Renderable_Base::build_prepare_graph(Renderable_Graph_Builder& builder) {
    builder.emplace("prepare", "Prepare",
                    [this](const Prepare_Render_Context& context) {
                        prepare(context);
                    });
}
void Renderable_Base::build_paint_graph(Renderable_Graph_Builder&) {}
void Renderable_Base::prepare(const Prepare_Render_Context&) {}
Render_State_View Renderable_Base::render_state_view() noexcept {
    return {};
}
Real_Time_Data_Binding Renderable_Base::bind_real_time_data(Real_Time_Data_Base& data) {
    return data.bind_renderable(*this);
}
void Renderable_Base::mark_prepared(std::uint64_t revision) noexcept {
    prepared_revision_.store(revision, std::memory_order_release);
}
void Renderable_Base::mark_painted(std::uint64_t paint_revision,
                                   std::uint64_t prepare_revision) noexcept {
    painted_prepare_revision_.store(prepare_revision, std::memory_order_release);
    painted_revision_.store(paint_revision, std::memory_order_release);
}
void Renderable_Base::register_real_time_data(Real_Time_Data_Base& data) {
    std::lock_guard lock(real_time_data_mutex_);
    if (std::find(real_time_data_.begin(), real_time_data_.end(), &data) == real_time_data_.end()) {
        real_time_data_.push_back(&data);
    }
}
void Renderable_Base::unregister_real_time_data(Real_Time_Data_Base& data) noexcept {
    std::lock_guard lock(real_time_data_mutex_);
    std::erase(real_time_data_, &data);
}
void Renderable_Base::publish_real_time_data() {
    std::lock_guard lock(real_time_data_mutex_);
    for (Real_Time_Data_Base* data : real_time_data_) {
        data->publish_render_state();
    }
}
