#include "Abstract_Frame.hpp"
#include <chrono>
#include <stdexcept>

void Node_Execution_Metrics::set(Node_Metric_Kind kind, std::uint64_t value) noexcept {
    const auto index = static_cast<std::size_t>(kind);
    if (index >= values.size())
        return;
    values[index] = value;
    present |= std::uint32_t{1} << index;
}

bool Node_Execution_Metrics::contains(Node_Metric_Kind kind) const noexcept {
    const auto index = static_cast<std::size_t>(kind);
    return index < values.size() && (present & (std::uint32_t{1} << index)) != 0;
}

std::uint64_t Node_Execution_Metrics::get(Node_Metric_Kind kind) const noexcept {
    return contains(kind) ? values[static_cast<std::size_t>(kind)] : 0;
}

std::uint64_t Node_Execution::duration_ns() const noexcept {
    return end_time_ns >= start_time_ns ? end_time_ns - start_time_ns : 0;
}

std::uint64_t Frame_Snapshot::render_duration_ns() const noexcept {
    return render_end_ns >= render_start_ns ? render_end_ns - render_start_ns : 0;
}

void Abstract_Frame::begin_render(std::uint64_t frame_id, const Render_Plan& plan,
                                  bool capture, std::uint64_t render_start_ns) {
    if (rendering_)
        throw std::logic_error("frame render is already active");
    rendering_ = true;
    capture_ = capture;
    frame_id_ = frame_id;
    plan_version_ = plan.version;
    render_start_ns_ = render_start_ns;
    completed_snapshot_.reset();
    executions_.clear();
    if (!capture)
        return;
    executions_.resize(plan.graph.nodes.size());
    for (const auto& node : plan.graph.nodes)
        executions_.at(node.execution_index).node_id = node.node_id;
}

bool Abstract_Frame::capture_this_frame() const noexcept {
    return rendering_ && capture_;
}

Node_Execution* Abstract_Frame::execution_slot(std::size_t index) noexcept {
    return capture_this_frame() && index < executions_.size() ? &executions_[index] : nullptr;
}

std::shared_ptr<const Frame_Snapshot> Abstract_Frame::complete_render(
    std::uint64_t render_end_ns) {
    if (!rendering_)
        throw std::logic_error("frame render is not active");
    rendering_ = false;
    if (!capture_) {
        capture_ = false;
        return nullptr;
    }
    auto snapshot = std::make_shared<Frame_Snapshot>();
    snapshot->frame_id = frame_id_;
    snapshot->render_plan_version = plan_version_;
    snapshot->render_start_ns = render_start_ns_;
    snapshot->render_end_ns = render_end_ns;
    snapshot->node_executions = std::move(executions_);
    completed_snapshot_ = snapshot;
    capture_ = false;
    return snapshot;
}

void Abstract_Frame::discard_render() noexcept {
    rendering_ = false;
    capture_ = false;
    frame_id_ = 0;
    plan_version_ = 0;
    render_start_ns_ = 0;
    executions_.clear();
    completed_snapshot_.reset();
}

std::shared_ptr<const Frame_Snapshot> Abstract_Frame::completed_snapshot() const noexcept {
    return completed_snapshot_;
}

std::uint64_t render_clock_now_ns() noexcept {
    return static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(
        std::chrono::steady_clock::now().time_since_epoch()).count());
}
