#include <gtest/gtest.h>

#include "render_3D/detail/Render_3D_Core.h"

#include <atomic>
#include <cstdint>
#include <thread>
#include <vector>

namespace renderive::detail {
namespace {

Render_3D_State state_with_width(std::uint32_t width) {
    Render_3D_State state;
    state.viewport = {width, 600};
    return state;
}

TEST(Render3DState, WriterDoesNotChangePublishedStateUntilPublish) {
    Logical_State_Buffer states(state_with_width(800));

    const auto front = states.publish_frame();

    states.update([](Render_3D_State& state) { state.viewport.width = 1920; });

    EXPECT_EQ(front.state.viewport.width, 800U);
    const auto frame = states.publish_frame();
    EXPECT_EQ(frame.state.viewport.width, 1920U);
    EXPECT_EQ(frame.revision, 2U);
}

TEST(Render3DFrameStrategy, LowLatencyDropsBeforeRenderTransaction) {
    Logical_State_Buffer states(state_with_width(1));
    Frame_Scheduler scheduler(Frame_3D_Control_Mode::Low_Latency, 60.0);

    for (const std::uint32_t width : {10U, 11U, 12U}) {
        states.update([width](Render_3D_State& state) { state.viewport.width = width; });
        ASSERT_TRUE(scheduler.prepare(states));
    }

    std::vector<std::uint32_t> emitted;
    std::vector<std::uint64_t> sequences;
    ASSERT_TRUE(scheduler.render([&](const Render_3D_Frame_Data& frame) {
        emitted.push_back(frame.state.viewport.width);
        sequences.push_back(frame.frame_sequence);
        return true;
    }));

    EXPECT_EQ(emitted, std::vector<std::uint32_t>({12U}));
    EXPECT_EQ(sequences, std::vector<std::uint64_t>({3U}));
}

TEST(Render3DFrameStrategy, ManualRefreshControlsVisibilityAndReplay) {
    Logical_State_Buffer states(state_with_width(1));
    Frame_Scheduler scheduler(Frame_3D_Control_Mode::Manual, 60.0);

    states.update([](Render_3D_State& state) { state.viewport.width = 20; });
    ASSERT_TRUE(scheduler.prepare(states));

    std::vector<std::uint32_t> rendered;
    std::vector<std::uint64_t> sequences;
    const auto render = [&](const Render_3D_Frame_Data& frame) {
        rendered.push_back(frame.state.viewport.width);
        sequences.push_back(frame.frame_sequence);
        return true;
    };
    EXPECT_FALSE(scheduler.render(render));
    ASSERT_TRUE(scheduler.refresh());
    EXPECT_TRUE(scheduler.render(render));
    EXPECT_TRUE(scheduler.render(render));
    EXPECT_EQ(rendered, std::vector<std::uint32_t>({20U, 20U}));
    EXPECT_EQ(sequences, std::vector<std::uint64_t>({1U, 1U}));
}

TEST(Render3DFrameStrategy, PlaybackExecutesEveryPublishedStateInOrder) {
    Logical_State_Buffer states(state_with_width(1));
    Frame_Scheduler scheduler(Frame_3D_Control_Mode::Playback, 60.0);

    for (const std::uint32_t width : {30U, 31U, 32U}) {
        states.update([width](Render_3D_State& state) { state.viewport.width = width; });
        ASSERT_TRUE(scheduler.prepare(states));
    }

    std::vector<std::uint32_t> rendered;
    std::vector<std::uint64_t> sequences;
    const auto render = [&](const Render_3D_Frame_Data& frame) {
        rendered.push_back(frame.state.viewport.width);
        sequences.push_back(frame.frame_sequence);
        return true;
    };
    EXPECT_TRUE(scheduler.render(render));
    EXPECT_TRUE(scheduler.render(render));
    EXPECT_TRUE(scheduler.render(render));
    EXPECT_FALSE(scheduler.render(render));
    EXPECT_EQ(rendered, std::vector<std::uint32_t>({30U, 31U, 32U}));
    EXPECT_EQ(sequences, std::vector<std::uint64_t>({1U, 2U, 3U}));
}

TEST(Render3DFrameStrategy, DiscardNeverStartsRenderTransaction) {
    Logical_State_Buffer states(state_with_width(40));
    Frame_Scheduler scheduler(Frame_3D_Control_Mode::Low_Latency, 60.0);

    ASSERT_TRUE(scheduler.prepare(states));
    ASSERT_TRUE(scheduler.discard_pending());

    std::size_t render_transaction_count{};
    EXPECT_FALSE(scheduler.render([&](const Render_3D_Frame_Data&) {
        ++render_transaction_count;
        return true;
    }));
    EXPECT_EQ(render_transaction_count, 0U);
}

TEST(Render3DState, ConcurrentPublishNeverExposesPartialLogicalState) {
    Logical_State_Buffer states(state_with_width(1));
    Frame_Scheduler scheduler(Frame_3D_Control_Mode::Low_Latency, 60.0);
    std::atomic<bool> producer_done{};
    std::atomic<bool> invalid_snapshot{};
    std::atomic<bool> non_monotonic_revision{};
    std::atomic<std::uint32_t> maximum_width{};

    std::thread producer([&] {
        for (std::uint32_t width = 2; width <= 500; ++width) {
            states.update([width](Render_3D_State& state) {
                state.viewport.width = width;
                state.viewport.height = width * 2;
            });
            (void)scheduler.prepare(states);
        }
        producer_done.store(true, std::memory_order_release);
    });

    std::thread renderer([&] {
        std::uint64_t last_revision{};
        while (!producer_done.load(std::memory_order_acquire) || maximum_width.load() < 500) {
            (void)scheduler.render([&](const Render_3D_Frame_Data& frame) {
                if (frame.state.viewport.height != frame.state.viewport.width * 2)
                    invalid_snapshot.store(true, std::memory_order_relaxed);
                if (frame.revision < last_revision)
                    non_monotonic_revision.store(true, std::memory_order_relaxed);
                last_revision = frame.revision;
                auto observed = maximum_width.load(std::memory_order_relaxed);
                while (observed < frame.state.viewport.width &&
                       !maximum_width.compare_exchange_weak(observed, frame.state.viewport.width)) {
                }
                return true;
            });
            std::this_thread::yield();
        }
    });

    producer.join();
    renderer.join();
    EXPECT_FALSE(invalid_snapshot.load());
    EXPECT_FALSE(non_monotonic_revision.load());
    EXPECT_EQ(maximum_width.load(), 500U);
}

} // namespace
} // namespace renderive::detail
