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

using pp::foundation::StatusCode;
using pp::foundation::TaskItem;
using pp::foundation::TaskQueue;
using pp::foundation::max_task_queue_entries;

namespace {

struct Counter {
    int value = 0;
};

void increment(void* user_data) noexcept
{
    auto* counter = static_cast<Counter*>(user_data);
    ++counter->value;
}

void add_two(void* user_data) noexcept
{
    auto* counter = static_cast<Counter*>(user_data);
    counter->value += 2;
}

void runs_tasks_in_fifo_order(pp::tests::Harness& h)
{
    Counter counter;
    TaskQueue queue(4);

    PP_EXPECT(h, queue.push(TaskItem { .callback = increment, .user_data = &counter, .id = 1 }).ok());
    PP_EXPECT(h, queue.push(TaskItem { .callback = add_two, .user_data = &counter, .id = 2 }).ok());
    PP_EXPECT(h, queue.size() == 2U);
    PP_EXPECT(h, queue.run_next().ok());
    PP_EXPECT(h, counter.value == 1);
    PP_EXPECT(h, queue.run_next().ok());
    PP_EXPECT(h, counter.value == 3);
    PP_EXPECT(h, queue.empty());
}

void pops_without_running(pp::tests::Harness& h)
{
    Counter counter;
    TaskQueue queue(2);

    PP_EXPECT(h, queue.push(TaskItem { .callback = increment, .user_data = &counter, .id = 42 }).ok());
    const auto task = queue.pop();

    PP_EXPECT(h, task.ok());
    PP_EXPECT(h, task.value().id == 42U);
    PP_EXPECT(h, counter.value == 0);
    PP_EXPECT(h, queue.empty());
}

void rejects_invalid_or_excessive_work(pp::tests::Harness& h)
{
    Counter counter;
    TaskQueue queue(1);
    TaskQueue invalid_queue(0);
    TaskQueue too_large(max_task_queue_entries + 1U);

    const auto null_task = queue.push(TaskItem {});
    PP_EXPECT(h, !null_task.ok());
    PP_EXPECT(h, null_task.code == StatusCode::invalid_argument);

    PP_EXPECT(h, queue.push(TaskItem { .callback = increment, .user_data = &counter }).ok());
    const auto full = queue.push(TaskItem { .callback = increment, .user_data = &counter });
    PP_EXPECT(h, !full.ok());
    PP_EXPECT(h, full.code == StatusCode::out_of_range);

    const auto invalid_capacity = invalid_queue.push(TaskItem { .callback = increment, .user_data = &counter });
    const auto excessive_capacity = too_large.push(TaskItem { .callback = increment, .user_data = &counter });
    PP_EXPECT(h, !invalid_capacity.ok());
    PP_EXPECT(h, invalid_capacity.code == StatusCode::out_of_range);
    PP_EXPECT(h, !excessive_capacity.ok());
    PP_EXPECT(h, excessive_capacity.code == StatusCode::out_of_range);
}

void run_all_and_clear_are_bounded(pp::tests::Harness& h)
{
    Counter counter;
    TaskQueue queue(4);

    PP_EXPECT(h, queue.max_entries() == 4U);
    PP_EXPECT(h, queue.push(TaskItem { .callback = increment, .user_data = &counter }).ok());
    PP_EXPECT(h, queue.push(TaskItem { .callback = increment, .user_data = &counter }).ok());
    PP_EXPECT(h, queue.run_all() == 2U);
    PP_EXPECT(h, counter.value == 2);

    PP_EXPECT(h, queue.push(TaskItem { .callback = add_two, .user_data = &counter }).ok());
    queue.clear();
    PP_EXPECT(h, queue.empty());
    const auto empty_pop = queue.pop();
    PP_EXPECT(h, !empty_pop.ok());
    PP_EXPECT(h, empty_pop.status().code == StatusCode::out_of_range);
}

}

int main()
{
    pp::tests::Harness harness;
    harness.run("runs_tasks_in_fifo_order", runs_tasks_in_fifo_order);
    harness.run("pops_without_running", pops_without_running);
    harness.run("rejects_invalid_or_excessive_work", rejects_invalid_or_excessive_work);
    harness.run("run_all_and_clear_are_bounded", run_all_and_clear_are_bounded);
    return harness.finish();
}
