#include "Renderable.h"
#include <renderive/scene/base/Scene_Base.hpp>

#include "../render/Blend2D_Cache.h"

#include <renderive/scene/base/Render_Contexts.hpp>

namespace renderive {

Renderable_Observation Renderable_Observer::observation() const noexcept {
    std::lock_guard lock(mutex_);
    return observation_;
}

void Renderable_Observer::observe_state(Renderable_Observer_Event event,
                                        std::uint64_t time_ns,
                                        std::uint64_t cache_update_count,
                                        std::uint64_t publish_count) noexcept {
    std::lock_guard lock(mutex_);
    observation_.event = event;
    observation_.event_time_ns = time_ns;
    observation_.cache_update_count = cache_update_count;
    observation_.publish_count = publish_count;
}

Renderable::Renderable(::Scene_Base& scene, bool cache_enabled)
    : ::Renderable_Base(scene, {.cache_enabled = cache_enabled}) {}

Renderable::~Renderable() = default;

Renderable_Cache_Mode Renderable::get_cache_mode() const noexcept {
    return configuration().cache_enabled
               ? Renderable_Cache_Mode::Local_Pixel
               : Renderable_Cache_Mode::Direct;
}

void Renderable::set_cache_mode(Renderable_Cache_Mode mode) {
    scene().set_renderable_configuration(
        shared_from_this(),
        {.cache_enabled = mode == Renderable_Cache_Mode::Local_Pixel});
}

std::string Renderable::object_name() const {
    std::lock_guard lock(metadata_mutex_);
    return object_name_;
}

void Renderable::set_object_name(std::string name) {
    std::lock_guard lock(metadata_mutex_);
    object_name_ = std::move(name);
}

Renderable_Observation Renderable::observation() const noexcept {
    return observer_.observation();
}

void Renderable::observe_state(Renderable_Observer_Event event,
                               std::uint64_t time_ns,
                               std::uint64_t cache_update_count,
                               std::uint64_t publish_count) noexcept {
    observer_.observe_state(event, time_ns, cache_update_count, publish_count);
}

void Renderable::build_prepare_graph(Renderable_Graph_Builder& builder) {
    add_prepare_task(builder, "prepare", "Prepare",
        [this](const Prepare_Render_Context& context) {
            prepare_frame(context);
        });
}

void Renderable::build_paint_graph(Renderable_Graph_Builder& builder) {
    const auto paint_task = add_paint_task(builder, "paint", "Paint",
        [this](detail::Painter& painter, const Paint_Render_Context& context) {
        paint(painter, context);
    });
    builder.precede(builder.find("prepare"), paint_task);
}

Renderable_Graph_Builder::Task Renderable::add_prepare_task(
    Renderable_Graph_Builder& builder,
    std::string logical_key,
        std::string name,
        Prepare_Task_Function function) {
    return builder.emplace(std::move(logical_key), std::move(name),
                           std::move(function));
}

Renderable_Graph_Builder::Task Renderable::add_paint_task(
    Renderable_Graph_Builder& builder,
    std::string logical_key,
    std::string name,
    Paint_Task_Function function) {
    return builder.emplace(std::move(logical_key), std::move(name),
        [function = std::move(function)](const Paint_Render_Context& context) {
            if (!context.renderable.visible)
                return;
            auto* cache = dynamic_cast<detail::Blend2D_Color_Cache*>(&context.color_cache);
            if (!cache)
                return;
            detail::Painter painter(*cache, {context.frame.viewport.width,
                                             context.frame.viewport.height});
            if (painter)
                function(painter, context);
        });
}

void Renderable::changed() noexcept {
    invalidate_prepare();
    scene().notify_model_dirty();
}

void Renderable::paint_changed() noexcept {
    invalidate_paint();
    scene().notify_model_dirty();
}

void Renderable::render_graph_changed() {
    rebuild_render_graph();
}

} // namespace renderive
