#include <gtest/gtest.h>

#include <algorithm>
#include <cstdint>

#include "Low_Latency_Strategy_Test_Types.hpp"

namespace {

void complete_pipeline(Low_Latency_Test_Strategy& strategy,
                       const Low_Latency_Test_Time_Source& time_source,
                       int value,
                       std::uint64_t paint_duration_ns,
                       std::uint64_t ready_wait_ns,
                       std::uint64_t render_duration_ns) {
    {
        auto frame = strategy.acquire_painter();
        ASSERT_TRUE(frame);
        frame->value = value;
        time_source.advance_ns(paint_duration_ns);
    }
    time_source.advance_ns(ready_wait_ns);
    {
        auto frame = strategy.acquire_renderer();
        ASSERT_TRUE(frame);
        EXPECT_EQ(frame->value, value);
        time_source.advance_ns(render_duration_ns);
    }
}

} // namespace

TEST(low_latency_pipeline_test, classifies_frequency_paint_and_render_limits_across_lifecycles) {
    Low_Latency_Test_Time_Source time_source;
    Low_Latency_Test_Observer observer;
    auto strategy = make_low_latency_test_strategy(time_source, observer);

    strategy.set_frequency_hz(100.0);
    complete_pipeline(strategy, time_source, 1, 2'000'000, 500'000, 3'000'000);
    auto state = strategy.state();
    EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::frequency_limited);
    EXPECT_EQ(state.target_interval_ns, 10'000'000u);
    EXPECT_EQ(state.bottleneck_duration_ns, 3'000'000u);
    EXPECT_EQ(state.next_refresh_interval_ns, 10'000'000u);

    strategy.set_frequency_hz(1'000.0);
    complete_pipeline(strategy, time_source, 2, 4'000'000, 250'000, 2'000'000);
    state = strategy.state();
    EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::paint_limited);
    EXPECT_EQ(state.paint_duration_ns, 4'000'000u);
    EXPECT_EQ(state.render_duration_ns, 2'000'000u);
    EXPECT_EQ(state.next_refresh_interval_ns, 4'000'000u);

    complete_pipeline(strategy, time_source, 3, 2'000'000, 125'000, 5'000'000);
    state = strategy.state();
    EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::render_limited);
    EXPECT_EQ(state.paint_duration_ns, 2'000'000u);
    EXPECT_EQ(state.render_duration_ns, 5'000'000u);
    EXPECT_EQ(state.next_refresh_interval_ns, 5'000'000u);
    EXPECT_EQ(state.completed_lifecycle_count, 3u);
    EXPECT_EQ(state.frame_sequence, 3u);
}

TEST(low_latency_pipeline_test, consumer_feedback_limits_refresh_without_knowing_the_consumer_type) {
    Low_Latency_Test_Time_Source time_source;
    Low_Latency_Test_Observer observer;
    auto strategy = make_low_latency_test_strategy(time_source, observer);
    strategy.set_frequency_hz(100.0);
    strategy.set_consumer_feedback({40'000'000});
    complete_pipeline(strategy, time_source, 1, 2'000'000, 0, 3'000'000);
    auto state = strategy.state();
    EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::consumer_limited);
    EXPECT_EQ(state.target_interval_ns, 10'000'000u);
    EXPECT_EQ(state.consumer_interval_ns, 40'000'000u);
    EXPECT_EQ(state.next_refresh_interval_ns, 40'000'000u);
    strategy.clear_consumer_feedback();
    state = strategy.state();
    EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::frequency_limited);
    EXPECT_EQ(state.consumer_interval_ns, 0u);
    EXPECT_EQ(state.next_refresh_interval_ns, 10'000'000u);
}

TEST(low_latency_pipeline_test, applies_frequency_and_equal_bottleneck_boundaries_deterministically) {
    Low_Latency_Test_Time_Source time_source;
    Low_Latency_Test_Observer observer;
    auto strategy = make_low_latency_test_strategy(time_source, observer);
    strategy.set_frequency_hz(1'000.0);

    complete_pipeline(strategy, time_source, 1, 1'000'000, 0, 999'999);
    auto state = strategy.state();
    EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::frequency_limited);
    EXPECT_EQ(state.bottleneck_duration_ns, state.target_interval_ns);

    complete_pipeline(strategy, time_source, 2, 2'000'000, 0, 2'000'000);
    state = strategy.state();
    EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::paint_limited);
    EXPECT_EQ(state.paint_duration_ns, state.render_duration_ns);
    EXPECT_EQ(state.bottleneck_duration_ns, 2'000'000u);
}

TEST(low_latency_pipeline_test, accepts_one_billion_hz_and_uses_the_measured_bottleneck) {
    Low_Latency_Test_Time_Source time_source;
    Low_Latency_Test_Observer observer;
    auto strategy = make_low_latency_test_strategy(time_source, observer);
    strategy.set_frequency_hz(1'000'000'000.0);

    complete_pipeline(strategy, time_source, 7, 20, 10, 30);
    const auto state = strategy.state();
    EXPECT_DOUBLE_EQ(state.frequency_hz, 1'000'000'000.0);
    EXPECT_EQ(state.target_interval_ns, 1u);
    EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::render_limited);
    EXPECT_EQ(state.next_refresh_interval_ns, 30u);
}

TEST(low_latency_pipeline_test, reports_exact_pipeline_timing_and_state_to_observers) {
    Low_Latency_Test_Time_Source time_source;
    Low_Latency_Test_Observer observer;
    auto data = observer.data;
    auto strategy = make_low_latency_test_strategy(time_source, observer);
    strategy.set_frequency_hz(500.0);

    complete_pipeline(strategy, time_source, 42, 3'000'000, 700'000, 4'000'000);
    const auto observations = low_latency_observations(data);
    ASSERT_EQ(observations.size(), 3u);
    EXPECT_EQ(observations[0].event,
              static_cast<int>(Low_Latency_Test_Strategy::Observation_Event::published));
    EXPECT_EQ(observations[1].event,
              static_cast<int>(Low_Latency_Test_Strategy::Observation_Event::rendered));
    EXPECT_EQ(observations[2].event,
              static_cast<int>(Low_Latency_Test_Strategy::Observation_Event::lifecycle_completed));

    const auto& completed = observations.back();
    EXPECT_EQ(completed.sequence, 1u);
    EXPECT_EQ(completed.frame_render_count, 1u);
    EXPECT_EQ(completed.paint_duration_ns, 3'000'000u);
    EXPECT_EQ(completed.ready_wait_ns, 700'000u);
    EXPECT_EQ(completed.frame_age_at_render_ns, 700'000u);
    EXPECT_EQ(completed.render_duration_ns, 4'000'000u);
    EXPECT_EQ(completed.end_to_end_ns, 7'700'000u);
    EXPECT_EQ(completed.published_frame_count, 1u);
    EXPECT_EQ(completed.render_pass_count, 1u);
    EXPECT_EQ(completed.target_interval_ns, 2'000'000u);
    EXPECT_EQ(completed.bottleneck_duration_ns, 4'000'000u);
    EXPECT_EQ(completed.next_refresh_interval_ns, 4'000'000u);
    EXPECT_EQ(completed.limit_state,
              static_cast<int>(Low_Latency_Test_Strategy::Limit_State::render_limited));
    EXPECT_EQ(completed.completed_lifecycle_count, 1u);
}

TEST(low_latency_pipeline_test, replaces_only_the_unconsumed_frame_and_renders_the_latest) {
    Low_Latency_Test_Time_Source time_source;
    Low_Latency_Test_Observer observer;
    auto data = observer.data;
    auto strategy = make_low_latency_test_strategy(time_source, observer);

    {
        auto frame = strategy.acquire_painter();
        frame->value = 1;
        time_source.advance_ns(100);
    }
    {
        auto frame = strategy.acquire_painter();
        frame->value = 2;
        time_source.advance_ns(200);
    }
    {
        auto frame = strategy.acquire_renderer();
        ASSERT_TRUE(frame);
        EXPECT_EQ(frame->value, 2);
        time_source.advance_ns(300);
    }

    const auto counters = strategy.counter_statistics();
    EXPECT_EQ(counters.published_frame_count, 2u);
    EXPECT_EQ(counters.abandoned_frame_count, 1u);
    EXPECT_EQ(counters.render_pass_count, 1u);
    EXPECT_EQ(strategy.state().completed_lifecycle_count, 1u);
    EXPECT_EQ(strategy.state().frame_sequence, 2u);

    const auto observations = low_latency_observations(data);
    ASSERT_EQ(observations.size(), 5u);
    EXPECT_EQ(observations[1].event,
              static_cast<int>(Low_Latency_Test_Strategy::Observation_Event::abandoned));
    EXPECT_EQ(observations.back().abandoned_frame_count, 1u);
}

TEST(low_latency_pipeline_test, rerender_without_new_frame_reports_failure_without_new_lifecycle) {
    Low_Latency_Test_Time_Source time_source;
    Low_Latency_Test_Observer observer;
    auto data = observer.data;
    auto strategy = make_low_latency_test_strategy(time_source, observer);
    complete_pipeline(strategy, time_source, 9, 100, 0, 200);

    {
        auto frame = strategy.acquire_renderer();
        ASSERT_TRUE(frame);
        EXPECT_EQ(frame->value, 9);
        time_source.advance_ns(400);
    }

    const auto state = strategy.state();
    const auto counters = strategy.counter_statistics();
    EXPECT_EQ(state.completed_lifecycle_count, 1u);
    EXPECT_EQ(counters.render_pass_count, 2u);
    EXPECT_EQ(counters.swap_failure_count, 1u);

    const auto observations = low_latency_observations(data);
    ASSERT_EQ(observations.size(), 5u);
    EXPECT_EQ(observations[3].event,
              static_cast<int>(Low_Latency_Test_Strategy::Observation_Event::swap_failed));
    EXPECT_EQ(observations[4].event,
              static_cast<int>(Low_Latency_Test_Strategy::Observation_Event::rendered));
    EXPECT_EQ(observations[4].render_pass_count, 2u);
    EXPECT_EQ(observations[4].completed_lifecycle_count, 1u);
}

TEST(low_latency_pipeline_test, completed_frame_keeps_the_real_time_revision_captured_at_paint) {
    Low_Latency_Test_Time_Source time_source;
    Low_Latency_Test_Observer observer;
    auto data = observer.data;
    auto strategy = make_low_latency_test_strategy(time_source, observer);
    strategy.on_real_time_data_update(
        {Real_Time_Data_Observation_Event::updated,
         {nullptr, Real_Time_Data_Retention::latest, 10, 100, 1, 1}});
    {
        auto frame = strategy.acquire_painter();
        frame->value = 1;
        time_source.advance_ns(100);
    }
    strategy.on_real_time_data_update(
        {Real_Time_Data_Observation_Event::updated,
         {nullptr, Real_Time_Data_Retention::latest, 11, 110, 2, 1}});
    auto observations = low_latency_observations(data);
    ASSERT_EQ(observations.size(), 3u);
    EXPECT_EQ(observations.back().event,
              static_cast<int>(Low_Latency_Test_Strategy::Observation_Event::real_time_data_updated));
    EXPECT_EQ(observations.back().published_frame_count, 1u);
    EXPECT_EQ(observations.back().real_time_data_update_sequence, 2u);
    {
        auto frame = strategy.acquire_renderer();
        ASSERT_TRUE(frame);
        time_source.advance_ns(200);
    }

    const auto state = strategy.state();
    EXPECT_EQ(state.real_time_data_update_sequence, 2u);
    EXPECT_EQ(state.completed_frame_real_time_data_update_sequence, 1u);
}
