#include "foundation/trace.h"
#include "test_harness.h"

using pp::foundation::StatusCode;
using pp::foundation::TraceRecorder;
using pp::foundation::TraceSpanDesc;

namespace {

void records_completed_spans_with_context(pp::tests::Harness& h)
{
    TraceRecorder recorder;

    const auto id = recorder.begin_span(
        TraceSpanDesc {
            .component = "paint",
            .name = "stroke_commit",
            .thread_id = 7,
            .frame_id = 11,
            .stroke_id = 13,
        },
        100);

    PP_EXPECT(h, id.ok());
    PP_EXPECT(h, recorder.active_span_count() == 1U);
    PP_EXPECT(h, recorder.end_span(id.value(), 145).ok());
    PP_EXPECT(h, recorder.active_span_count() == 0U);
    PP_EXPECT(h, recorder.events().size() == 1U);

    const auto& event = recorder.events()[0];
    PP_EXPECT(h, event.component == "paint");
    PP_EXPECT(h, event.name == "stroke_commit");
    PP_EXPECT(h, event.thread_id == 7U);
    PP_EXPECT(h, event.frame_id == 11U);
    PP_EXPECT(h, event.stroke_id == 13U);
    PP_EXPECT(h, event.start_us == 100U);
    PP_EXPECT(h, event.duration_us == 45U);
}

void rejects_invalid_span_descriptions(pp::tests::Harness& h)
{
    TraceRecorder recorder;

    const auto no_component = recorder.begin_span(
        TraceSpanDesc { .component = "", .name = "load" },
        1);
    const auto no_name = recorder.begin_span(
        TraceSpanDesc { .component = "assets", .name = "" },
        1);

    PP_EXPECT(h, !no_component.ok());
    PP_EXPECT(h, no_component.status().code == StatusCode::invalid_argument);
    PP_EXPECT(h, !no_name.ok());
    PP_EXPECT(h, no_name.status().code == StatusCode::invalid_argument);
    PP_EXPECT(h, recorder.events().empty());
}

void rejects_bad_end_calls_without_recording_events(pp::tests::Harness& h)
{
    TraceRecorder recorder;
    const auto id = recorder.begin_span(
        TraceSpanDesc { .component = "renderer", .name = "readback" },
        50);

    PP_EXPECT(h, id.ok());

    const auto backwards = recorder.end_span(id.value(), 49);
    PP_EXPECT(h, !backwards.ok());
    PP_EXPECT(h, backwards.code == StatusCode::invalid_argument);
    PP_EXPECT(h, recorder.active_span_count() == 1U);
    PP_EXPECT(h, recorder.events().empty());

    PP_EXPECT(h, recorder.end_span(id.value(), 51).ok());
    const auto duplicate = recorder.end_span(id.value(), 52);
    PP_EXPECT(h, !duplicate.ok());
    PP_EXPECT(h, duplicate.code == StatusCode::out_of_range);
    PP_EXPECT(h, recorder.events().size() == 1U);
}

void clear_resets_events_and_span_ids(pp::tests::Harness& h)
{
    TraceRecorder recorder;
    const auto first = recorder.begin_span(
        TraceSpanDesc { .component = "ui", .name = "layout" },
        10);
    PP_EXPECT(h, first.ok());
    PP_EXPECT(h, recorder.end_span(first.value(), 20).ok());
    recorder.clear();

    const auto second = recorder.begin_span(
        TraceSpanDesc { .component = "ui", .name = "layout" },
        30);
    PP_EXPECT(h, second.ok());
    PP_EXPECT(h, second.value() == first.value());
    PP_EXPECT(h, recorder.events().empty());
    PP_EXPECT(h, recorder.active_span_count() == 1U);
}

}

int main()
{
    pp::tests::Harness harness;
    harness.run("records_completed_spans_with_context", records_completed_spans_with_context);
    harness.run("rejects_invalid_span_descriptions", rejects_invalid_span_descriptions);
    harness.run("rejects_bad_end_calls_without_recording_events", rejects_bad_end_calls_without_recording_events);
    harness.run("clear_resets_events_and_span_ids", clear_resets_events_and_span_ids);
    return harness.finish();
}
