#include "Renderable_Base_p.hpp"

#include <algorithm>
#include <limits>
#include <stdexcept>
#include <utility>

#include "renderive/real_time_data/base/Real_Time_Data_Base.hpp"
#include "renderive/renderable/Renderive_Id_Allocator.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)) {}
}

} // namespace

Renderable_Base::Impl::Impl()
    : renderable_id(allocate_renderive_renderable_id()),
      composite_node_id(allocate_renderive_node_id()) {}

Renderable_Base::Impl::~Impl() {
    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::Impl::initialize(
    Renderable_Base& value,
    Renderable_Configuration initial_configuration,
    std::pmr::memory_resource& resource) noexcept {
    owner_ = &value;
    memory_resource_pointer = &resource;
    configuration.store(initial_configuration, std::memory_order_relaxed);
}

Renderable_Base& Renderable_Base::Impl::owner() noexcept {
    return *owner_;
}

const Renderable_Base& Renderable_Base::Impl::owner() const noexcept {
    return *owner_;
}

void Renderable_Base::Impl::invalidate_prepare() noexcept {
    advance_revision(prepare_revision);
}

void Renderable_Base::Impl::invalidate_paint() noexcept {
    advance_revision(paint_revision);
}

void Renderable_Base::Impl::bind_scene(
    std::shared_ptr<Scene_Lifetime> lifetime) {
    auto& current = real_time_data_state->scene_lifetime;
    if (!current)
        current = std::move(lifetime);
    else if (current != lifetime)
        throw std::invalid_argument("renderable belongs to another scene");
}

void Renderable_Base::Impl::rebuild_render_graph() {
    if (!attached()) {
        reset_render_graph();
        return;
    }
    scene().request_render_graph_rebuild(owner());
}

void Renderable_Base::Impl::reset_render_graph() noexcept {
    render_graph.reset();
    invalidate_prepare();
}

std::shared_ptr<const Renderable_Graph>
Renderable_Base::Impl::render_graph_snapshot() {
    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()
                        ? allocate_renderive_node_id()
                        : current->second;
                next_identities.emplace(logical_key, node_id);
                return node_id;
            });
        build_prepare_graph(builder);
        build_paint_graph(builder);
        render_graph = std::make_shared<Renderable_Graph>(
            std::move(builder).finish());
        node_identities = std::move(next_identities);
    }
    return render_graph;
}

std::pmr::memory_resource&
Renderable_Base::Impl::memory_resource() const noexcept {
    return *memory_resource_pointer;
}

Scene_Base& Renderable_Base::Impl::scene() const {
    const auto lifetime = real_time_data_state->scene_lifetime;
    if (!lifetime)
        throw std::logic_error("renderable is not bound to a scene");
    auto lease = lifetime->acquire();
    if (!lease)
        throw std::logic_error("renderable scene is no longer alive");
    return lease.scene();
}

bool Renderable_Base::Impl::attached() const noexcept {
    return real_time_data_state->attached.load(std::memory_order_acquire);
}

void Renderable_Base::Impl::set_configuration(
    Renderable_Configuration value) {
    const Renderable_Configuration previous =
        configuration.load(std::memory_order_acquire);
    if (previous == value)
        return;
    configuration.store(value, std::memory_order_release);
    invalidate_prepare();
    notify_scene_model_dirty();
}

void Renderable_Base::Impl::set_visible(bool value) {
    if (visible.exchange(value, std::memory_order_acq_rel) == value)
        return;
    invalidate_prepare();
    notify_scene_model_dirty();
}

void Renderable_Base::Impl::discard_stale_frame_on_latest_data_update(
    bool enabled) noexcept {
    real_time_data_state->discard_stale_frame_on_latest_data_update.store(
        enabled, std::memory_order_release);
}

void Renderable_Base::Impl::notify_scene_model_dirty() noexcept {
    const auto& state = real_time_data_state;
    if (!state->attached.load(std::memory_order_acquire) ||
        !state->scene_lifetime)
        return;
    auto lease = state->scene_lifetime->acquire();
    if (lease)
        lease.scene().notify_model_dirty();
}

void Renderable_Base::Impl::build_prepare_graph(
    Renderable_Graph_Builder& builder) {
    builder.emplace("prepare", "Prepare",
                    [this](const Prepare_Render_Context& context) {
                        execute_prepare(context);
                    });
}

void Renderable_Base::Impl::build_paint_graph(
    Renderable_Graph_Builder&) {}

void Renderable_Base::Impl::prepare(const Prepare_Render_Context&) {}

void Renderable_Base::Impl::add_prepare_action(Prepare_Action action) {
    if (!action)
        throw std::invalid_argument("renderable prepare action is empty");
    prepare_actions.push_back(std::move(action));
}

void Renderable_Base::Impl::execute_prepare(
    const Prepare_Render_Context& context) {
    for (const auto& action : prepare_actions)
        action();
    prepare(context);
}

Render_State_View Renderable_Base::Impl::render_state_view() noexcept {
    return {};
}

Real_Time_Data_Binding Renderable_Base::Impl::bind_real_time_data(
    Real_Time_Data_Base& data) {
    return data.bind_renderable(owner());
}

void Renderable_Base::Impl::mark_prepared(std::uint64_t revision) noexcept {
    prepared_revision.store(revision, std::memory_order_release);
}

void Renderable_Base::Impl::mark_painted(
    std::uint64_t current_paint_revision,
    std::uint64_t current_prepare_revision) noexcept {
    painted_prepare_revision.store(current_prepare_revision,
                                   std::memory_order_release);
    painted_revision.store(current_paint_revision, std::memory_order_release);
}

void Renderable_Base::Impl::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::Impl::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::Impl::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();
}

Renderable_Base::Renderable_Base(Renderable_Configuration configuration,
                                 std::pmr::memory_resource& memory_resource)
    : Renderable_Base(std::make_unique<Impl>(), configuration,
                      memory_resource) {}

Renderable_Base::Renderable_Base(std::unique_ptr<Impl> implementation,
                                 Renderable_Configuration configuration,
                                 std::pmr::memory_resource& memory_resource)
    : d_ptr(std::move(implementation)) {
    if (!d_ptr)
        throw std::invalid_argument("renderable implementation is null");
    d_ptr->initialize(*this, configuration, memory_resource);
}

Renderable_Base::~Renderable_Base() = default;

Renderable_Id Renderable_Base::renderable_id() const noexcept {
    return d_func().renderable_id;
}

bool Renderable_Base::is_visible() const noexcept {
    return d_func().visible.load(std::memory_order_acquire);
}

void Renderable_Base::set_visible(bool visible) {
    d_func().set_visible(visible);
}

void Renderable_Base::discard_stale_frame_on_latest_data_update(
    bool enabled) noexcept {
    d_func().discard_stale_frame_on_latest_data_update(enabled);
}
