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

#include <vector>

namespace {

struct NestedPushPayload {
    pp::foundation::TaskQueue* queue = nullptr;
    std::vector<int>* order = nullptr;
};

struct MarkerPayload {
    std::vector<int>* order = nullptr;
    int marker = 0;
};

struct HandoffPayload {
    pp::foundation::TaskQueue* ui_queue = nullptr;
    std::vector<int>* order = nullptr;
    int worker_marker = 0;
    MarkerPayload* ui_marker = nullptr;
};

void nested_push_task(void* user_data) noexcept
{
    auto* payload = static_cast<NestedPushPayload*>(user_data);
    payload->order->push_back(1);
    payload->queue->push(pp::foundation::TaskItem { .callback = [](void* callback_data) noexcept {
        auto* inner = static_cast<std::vector<int>*>(callback_data);
        inner->push_back(2);
    }, .user_data = payload->order, .id = 2 });
}

void record_marker_task(void* user_data) noexcept
{
    auto* payload = static_cast<MarkerPayload*>(user_data);
    payload->order->push_back(payload->marker);
}

void handoff_to_ui_queue_task(void* user_data) noexcept
{
    const auto payload = static_cast<HandoffPayload*>(user_data);
    payload->order->push_back(payload->worker_marker);
    payload->ui_queue->push(pp::foundation::TaskItem {
        .callback = record_marker_task,
        .user_data = payload->ui_marker,
        .id = static_cast<std::uint64_t>(payload->worker_marker),
    });
}

void runs_nested_push_tasks_deterministically(pp::tests::Harness& harness)
{
    pp::foundation::TaskQueue queue;
    std::vector<int> order;
    NestedPushPayload payload { .queue = &queue, .order = &order };

    PP_EXPECT(harness, queue.push(pp::foundation::TaskItem {
        .callback = nested_push_task,
        .user_data = &payload,
        .id = 1
    }).ok());

    PP_EXPECT(harness, queue.run_all() == 2U);
    PP_EXPECT(harness, order.size() == 2U);
    PP_EXPECT(harness, order[0] == 1);
    PP_EXPECT(harness, order[1] == 2);
    PP_EXPECT(harness, queue.empty());
}

void worker_to_ui_queue_handoff_is_ordered(pp::tests::Harness& harness)
{
    pp::foundation::TaskQueue worker_queue;
    pp::foundation::TaskQueue ui_queue;
    std::vector<int> order;

    MarkerPayload marker_payloads[] = {
        { .order = &order, .marker = 2 },
        { .order = &order, .marker = 3 },
    };
    HandoffPayload worker_payloads[] = {
        {
            .ui_queue = &ui_queue,
            .order = &order,
            .worker_marker = 1,
            .ui_marker = &marker_payloads[0],
        },
        {
            .ui_queue = &ui_queue,
            .order = &order,
            .worker_marker = 4,
            .ui_marker = &marker_payloads[1],
        },
    };

    PP_EXPECT(harness, worker_queue.push(pp::foundation::TaskItem {
        .callback = handoff_to_ui_queue_task,
        .user_data = &worker_payloads[0],
        .id = 1,
    }).ok());
    PP_EXPECT(harness, worker_queue.push(pp::foundation::TaskItem {
        .callback = handoff_to_ui_queue_task,
        .user_data = &worker_payloads[1],
        .id = 2,
    }).ok());

    PP_EXPECT(harness, worker_queue.run_all() == 2U);
    PP_EXPECT(harness, order.size() == 2U);
    PP_EXPECT(harness, order[0] == 1);
    PP_EXPECT(harness, order[1] == 4);
    PP_EXPECT(harness, worker_queue.empty());
    PP_EXPECT(harness, !ui_queue.empty());

    PP_EXPECT(harness, ui_queue.run_all() == 2U);
    PP_EXPECT(harness, order.size() == 4U);
    PP_EXPECT(harness, order[2] == 2);
    PP_EXPECT(harness, order[3] == 3);
    PP_EXPECT(harness, ui_queue.empty());
}

void stress_batch_push_and_overflow_reported(pp::tests::Harness& harness)
{
    constexpr std::size_t batch_size = 32U;
    pp::foundation::TaskQueue queue(batch_size);

    int counter = 0;
    for (std::size_t i = 0U; i < batch_size; ++i)
    {
        PP_EXPECT(harness, queue.push(pp::foundation::TaskItem {
            .callback = [](void* user_data) noexcept
            {
                ++*static_cast<int*>(user_data);
            },
            .user_data = &counter,
            .id = static_cast<std::uint64_t>(i + 1),
        }).ok());
    }

    const auto overflow = queue.push(pp::foundation::TaskItem {
        .callback = [](void* user_data) noexcept
        {
            ++*static_cast<int*>(user_data);
        },
        .user_data = &counter,
        .id = batch_size + 1U,
    });
    PP_EXPECT(harness, !overflow.ok());
    PP_EXPECT(harness, queue.run_all() == batch_size);
    PP_EXPECT(harness, counter == static_cast<int>(batch_size));
    PP_EXPECT(harness, queue.empty());
}

}

int main()
{
    pp::tests::Harness harness;
    harness.run("nested pushes are executed deterministically", runs_nested_push_tasks_deterministically);
    harness.run("worker to UI queue handoff preserves order", worker_to_ui_queue_handoff_is_ordered);
    harness.run("stress batch push reports overflow and drains deterministically", stress_batch_push_and_overflow_reported);
    return harness.finish();
}
