#include "Renderable_Graph_Builder.hpp"
#include <stdexcept>
#include <utility>

Renderable_Graph_Builder::Renderable_Graph_Builder(
    std::uint64_t owner_id, Node_Id_Resolver resolve_node_id)
    : owner_id_(owner_id), resolve_node_id_(std::move(resolve_node_id)) {
    if (owner_id_ == 0 || !resolve_node_id_)
        throw std::invalid_argument("renderable graph builder requires an owner and id resolver");
}

Renderable_Graph_Builder::Task Renderable_Graph_Builder::emplace(
    std::string logical_key, std::string name,
    Prepare_Render_Node_Function function) {
    return emplace_function(std::move(logical_key), std::move(name),
                            Render_Node_Kind::prepare,
                            Render_Node_Function{std::move(function)});
}

Renderable_Graph_Builder::Task Renderable_Graph_Builder::emplace(
    std::string logical_key, std::string name,
    Paint_Render_Node_Function function) {
    return emplace_function(std::move(logical_key), std::move(name),
                            Render_Node_Kind::paint,
                            Render_Node_Function{std::move(function)});
}

Renderable_Graph_Builder::Task Renderable_Graph_Builder::emplace_function(
    std::string logical_key, std::string name, Render_Node_Kind kind,
    Render_Node_Function function) {
    if (logical_key.empty())
        throw std::invalid_argument("render node logical key must not be empty");
    if (tasks_.contains(logical_key))
        throw std::invalid_argument("duplicate render node logical key");
    if (std::visit([](const auto& value) { return !value; }, function))
        throw std::invalid_argument("render node function must not be empty");
    const Render_Node_Id id = resolve_node_id_(logical_key);
    const Task task = builder_.emplace(id, owner_id_, std::move(name), kind);
    functions_.emplace(id, std::move(function));
    tasks_.emplace(std::move(logical_key), task);
    return task;
}

void Renderable_Graph_Builder::precede(Task from, Task to) {
    builder_.precede(from, to);
}

Renderable_Graph_Builder::Task Renderable_Graph_Builder::find(
    std::string_view logical_key) const {
    const auto iterator = tasks_.find(std::string(logical_key));
    if (iterator == tasks_.end())
        throw std::out_of_range("render node logical key");
    return iterator->second;
}

Renderable_Graph Renderable_Graph_Builder::finish() && {
    Renderable_Graph result;
    result.graph = std::move(builder_).finish();
    result.functions.reserve(result.graph.nodes.size());
    for (const auto& node : result.graph.nodes)
        result.functions.push_back(std::move(functions_.at(node.node_id)));
    return result;
}
