#include <gtest/gtest.h>
#include <algorithm>
#include <atomic>
#include <functional>
#include <memory>
#include <memory_resource>
#include <mutex>
#include <thread>
#include <vector>
#include "renderive/frame_control/Frame_Control.hpp"
struct Flow_Refresh_Test_Frame {
    int value{};
};
using Flow_Refresh_Test_Strategy = Flow_Refresh_Strategy<Flow_Refresh_Test_Frame>;
static_assert(Flow_Frame_Refresh_Strategy<Flow_Refresh_Test_Strategy>);
TEST(flow_refresh_strategy_test, preserves_all_frames_in_fifo_order) {
    Flow_Refresh_Test_Strategy strategy;
    for (int value = 1; value <= 3; ++value) {
        auto frame = strategy.acquire_painter();
        frame->value = value;
    }
    EXPECT_EQ(strategy.pending_frame_count(), 3);
    for (int value = 1; value <= 3; ++value) {
        auto frame = strategy.acquire_renderer();
        ASSERT_TRUE(frame);
        EXPECT_EQ(frame->value, value);
    }
    EXPECT_EQ(strategy.pending_frame_count(), 0);
    EXPECT_EQ(strategy.state().rendered_frame_count, 3);
}
TEST(flow_refresh_strategy_test, returns_empty_lease_when_queue_is_empty) {
    Flow_Refresh_Test_Strategy strategy;
    auto frame = strategy.acquire_renderer();
    EXPECT_FALSE(frame);
    EXPECT_EQ(strategy.state().empty_acquire_count, 1);
}
TEST(flow_refresh_strategy_test, accepts_multiple_concurrent_producers_without_losing_frames) {
    Flow_Refresh_Test_Strategy strategy;
    constexpr int producer_count = 4;
    constexpr int frames_per_producer = 100;
    std::vector<std::thread> producers;
    for (int producer = 0; producer < producer_count; ++producer) {
        producers.emplace_back([&strategy, producer] {
            for (int index = 0; index < frames_per_producer; ++index) {
                auto frame = strategy.acquire_painter();
                frame->value = producer * 1000 + index;
            }
        });
    }
    for (auto& producer : producers) {
        producer.join();
    }
    EXPECT_EQ(strategy.pending_frame_count(), producer_count * frames_per_producer);
    std::vector<int> values;
    while (auto frame = strategy.acquire_renderer()) {
        values.push_back(frame->value);
    }
    std::sort(values.begin(), values.end());
    EXPECT_EQ(values.size(), producer_count * frames_per_producer);
    for (int producer = 0; producer < producer_count; ++producer) {
        for (int index = 0; index < frames_per_producer; ++index) {
            EXPECT_TRUE(std::binary_search(values.begin(), values.end(), producer * 1000 + index));
        }
    }
}
TEST(flow_refresh_strategy_test, concurrent_consumer_does_not_underflow_pending_count) {
    Flow_Refresh_Test_Strategy strategy;
    constexpr int producer_count = 4;
    constexpr int frames_per_producer = 200;
    constexpr int frame_count = producer_count * frames_per_producer;
    std::atomic<int> producers_done{};
    std::atomic<int> consumed{};
    std::vector<int> values;
    values.reserve(frame_count);
    std::thread consumer([&] {
        while (consumed.load(std::memory_order_acquire) != frame_count) {
            auto frame = strategy.acquire_renderer();
            if (!frame) {
                if (producers_done.load(std::memory_order_acquire) == producer_count && strategy.pending_frame_count() == 0) {
                    break;
                }
                std::this_thread::yield();
                continue;
            }
            values.push_back(frame->value);
            consumed.fetch_add(1, std::memory_order_release);
        }
    });
    std::vector<std::thread> producers;
    for (int producer = 0; producer < producer_count; ++producer) {
        producers.emplace_back([&strategy, &producers_done, producer] {
            for (int index = 0; index < frames_per_producer; ++index) {
                auto frame = strategy.acquire_painter();
                frame->value = producer * 1000 + index;
            }
            producers_done.fetch_add(1, std::memory_order_release);
        });
    }
    for (auto& producer : producers) {
        producer.join();
    }
    consumer.join();
    EXPECT_EQ(consumed.load(std::memory_order_acquire), frame_count);
    EXPECT_EQ(strategy.pending_frame_count(), 0);
    std::sort(values.begin(), values.end());
    EXPECT_EQ(values.size(), frame_count);
    for (int producer = 0; producer < producer_count; ++producer) {
        for (int index = 0; index < frames_per_producer; ++index) {
            EXPECT_TRUE(std::binary_search(values.begin(), values.end(), producer * 1000 + index));
        }
    }
}
struct Flow_Refresh_Reentrant_Observer_Data {
    std::function<void(int)> callback;
};
struct Flow_Refresh_Reentrant_Observer {
    static constexpr bool enabled = true;
    std::shared_ptr<Flow_Refresh_Reentrant_Observer_Data> data{std::make_shared<Flow_Refresh_Reentrant_Observer_Data>()};
    template <class Observation>
    void observe(const Observation& observation) noexcept {
        if (data->callback) {
            data->callback(static_cast<int>(observation.event));
        }
    }
};
using Flow_Refresh_Reentrant_Observer_State = Observer_State<Flow_Refresh_Reentrant_Observer>;
using Flow_Refresh_Reentrant_Strategy = Flow_Refresh_Strategy<Flow_Refresh_Test_Frame, std::mutex, Flow_Refresh_Reentrant_Observer_State>;
TEST(flow_refresh_strategy_test, observer_can_reenter_observer_state_without_deadlock) {
    Flow_Refresh_Reentrant_Observer recorder;
    auto data = recorder.data;
    Flow_Refresh_Reentrant_Strategy strategy{Flow_Refresh_Reentrant_Observer_State(recorder)};
    std::atomic<bool> reentered{};
    data->callback = [&](int event) {
        if (event != static_cast<int>(Flow_Refresh_Reentrant_Strategy::Observation_Event::enqueued) || reentered.exchange(true)) {
            return;
        }
        strategy.on_real_time_data_update({Real_Time_Data_Observation_Event::updated, {nullptr, Real_Time_Data_Retention::latest, 1, 1, 1, 1}});
    };
    {
        auto frame = strategy.acquire_painter();
        frame->value = 1;
    }
    EXPECT_TRUE(reentered.load());
    EXPECT_EQ(strategy.state().real_time_data_update_sequence, 1);
}
struct Flow_Refresh_Render_Reentrant_Observer_Data {
    std::function<void()> callback;
};
struct Flow_Refresh_Render_Reentrant_Observer {
    static constexpr bool enabled = true;
    std::shared_ptr<Flow_Refresh_Render_Reentrant_Observer_Data> data{std::make_shared<Flow_Refresh_Render_Reentrant_Observer_Data>()};
    template <class Observation>
    void observe(const Observation& observation) noexcept {
        if (static_cast<int>(observation.event) == 1 && data->callback) {
            data->callback();
        }
    }
};
using Flow_Refresh_Render_Reentrant_Observer_State = Observer_State<Flow_Refresh_Render_Reentrant_Observer>;
using Flow_Refresh_Render_Reentrant_Strategy = Flow_Refresh_Strategy<Flow_Refresh_Test_Frame, std::mutex, Flow_Refresh_Render_Reentrant_Observer_State>;
TEST(flow_refresh_strategy_test, render_observer_reentry_does_not_deadlock_render_mutex) {
    Flow_Refresh_Render_Reentrant_Observer recorder;
    auto data = recorder.data;
    Flow_Refresh_Render_Reentrant_Strategy strategy{Flow_Refresh_Render_Reentrant_Observer_State(recorder)};
    for (int value = 1; value <= 2; ++value) {
        auto frame = strategy.acquire_painter();
        frame->value = value;
    }
    std::atomic<bool> nested_result{true};
    data->callback = [&] {
        auto nested = strategy.acquire_renderer();
        nested_result.store(static_cast<bool>(nested), std::memory_order_release);
    };
    {
        auto frame = strategy.acquire_renderer();
        ASSERT_TRUE(frame);
        EXPECT_EQ(frame->value, 1);
    }
    EXPECT_FALSE(nested_result.load(std::memory_order_acquire));
    auto second = strategy.acquire_renderer();
    ASSERT_TRUE(second);
    EXPECT_EQ(second->value, 2);
}
TEST(flow_refresh_strategy_test, concurrent_producers_and_consumer_support_unsynchronized_pmr_resource) {
    std::pmr::unsynchronized_pool_resource resource;
    Flow_Refresh_Test_Strategy strategy(resource);
    constexpr int producer_count = 8;
    constexpr int frames_per_producer = 200;
    constexpr int frame_count = producer_count * frames_per_producer;
    std::atomic<int> producers_done{};
    std::atomic<int> consumed{};
    std::thread consumer([&] {
        while (consumed.load(std::memory_order_acquire) < frame_count) {
            auto frame = strategy.acquire_renderer();
            if (frame) {
                consumed.fetch_add(1, std::memory_order_release);
                continue;
            }
            if (producers_done.load(std::memory_order_acquire) == producer_count && strategy.pending_frame_count() == 0) {
                break;
            }
            std::this_thread::yield();
        }
    });
    std::vector<std::thread> producers;
    producers.reserve(producer_count);
    for (int producer = 0; producer < producer_count; ++producer) {
        producers.emplace_back([&strategy, &producers_done, producer] {
            for (int index = 0; index < frames_per_producer; ++index) {
                auto frame = strategy.acquire_painter();
                frame->value = producer * frames_per_producer + index;
            }
            producers_done.fetch_add(1, std::memory_order_release);
        });
    }
    for (auto& producer : producers) {
        producer.join();
    }
    consumer.join();
    EXPECT_EQ(consumed.load(std::memory_order_acquire), frame_count);
    EXPECT_EQ(strategy.pending_frame_count(), 0);
}
