#include "foundation/trace.h"

#include <limits>

namespace pp::foundation {

Result<TraceSpanId> TraceRecorder::begin_span(TraceSpanDesc desc, std::uint64_t start_us)
{
    if (desc.component.empty()) {
        return Result<TraceSpanId>::failure(
            Status::invalid_argument("trace component must not be empty"));
    }

    if (desc.name.empty()) {
        return Result<TraceSpanId>::failure(
            Status::invalid_argument("trace span name must not be empty"));
    }

    if (next_id_ == std::numeric_limits<TraceSpanId>::max()) {
        return Result<TraceSpanId>::failure(
            Status::out_of_range("trace span id space is exhausted"));
    }

    const auto id = next_id_++;
    ActiveSpan span;
    span.id = id;
    span.component.assign(desc.component);
    span.name.assign(desc.name);
    span.desc = desc;
    span.desc.component = span.component;
    span.desc.name = span.name;
    span.start_us = start_us;
    span.active = true;
    active_spans_.push_back(span);

    return Result<TraceSpanId>::success(id);
}

Status TraceRecorder::end_span(TraceSpanId id, std::uint64_t end_us)
{
    ActiveSpan* span = find_active_span(id);
    if (span == nullptr) {
        return Status::out_of_range("trace span id is not active");
    }

    if (end_us < span->start_us) {
        return Status::invalid_argument("trace span cannot end before it starts");
    }

    TraceEvent event;
    event.component = span->component;
    event.name = span->name;
    event.thread_id = span->desc.thread_id;
    event.frame_id = span->desc.frame_id;
    event.stroke_id = span->desc.stroke_id;
    event.start_us = span->start_us;
    event.duration_us = end_us - span->start_us;
    events_.push_back(event);

    span->active = false;
    return Status::success();
}

std::span<const TraceEvent> TraceRecorder::events() const noexcept
{
    return events_;
}

std::size_t TraceRecorder::active_span_count() const noexcept
{
    std::size_t count = 0;
    for (const auto& span : active_spans_) {
        if (span.active) {
            ++count;
        }
    }
    return count;
}

void TraceRecorder::clear() noexcept
{
    active_spans_.clear();
    events_.clear();
    next_id_ = 1;
}

TraceRecorder::ActiveSpan* TraceRecorder::find_active_span(TraceSpanId id) noexcept
{
    for (auto& span : active_spans_) {
        if (span.active && span.id == id) {
            return &span;
        }
    }

    return nullptr;
}

}
