#include "Coroutine.h"
#include <vector>

class dae::CoroutineRunner::CoroutineRunnerImpl
{
    struct ActiveCoroutine {
        CoroutineId id;
        Task task;
        float timer = 0.0f;

        ActiveCoroutine(CoroutineId id, Task t, float time)
            : id(id), task(std::move(t)), timer(time) {}

        ActiveCoroutine(ActiveCoroutine&&) noexcept = default;
        ActiveCoroutine& operator=(ActiveCoroutine&&) noexcept = default;

        ActiveCoroutine(const ActiveCoroutine&) = delete;
        ActiveCoroutine& operator=(const ActiveCoroutine&) = delete;
    };

    std::vector<ActiveCoroutine> coroutines;
public:
        dae::CoroutineId StartCoroutine(Task task)
        {
            static uint64_t nextId {1};
            CoroutineId id = { nextId++ };
            coroutines.emplace_back(id, std::move(task), 0.0f);
            return id;
        }

    bool StopCoroutine(CoroutineId id)
    {
        if (!id) return false;
        for (auto it = coroutines.begin(); it != coroutines.end(); ++it) {
            if (it->id == id) {
                coroutines.erase(it);
                return true;
            }
        }
        return false;
    }

    void Update(float deltaTime)
    {
        for (size_t i = 0; i < coroutines.size(); )
        {
            auto& active = coroutines[i];
            if (!active.task.handle || active.task.handle.done())
            {
                coroutines[i] = std::move(coroutines.back());
                coroutines.pop_back();
                continue;
            }

            if (active.timer > 0.0f)
            {
                active.timer -= deltaTime;
                if (active.timer > 0.0f) {
                    ++i;
                    continue; // Still waiting on time
                }
            }

            const auto& instruction = active.task.handle.promise().current_yield;
            if (std::holds_alternative<WaitUntil>(instruction))
            {
                const auto& waitUntil = std::get<WaitUntil>(instruction);
                if (waitUntil.predicate && !waitUntil.predicate())
                {
                    ++i;
                    continue; // Predicate is false -> skip resume, stay paused
                }
            }

            active.task.handle.resume();

            if (active.task.handle.done())
            {
                coroutines[i] = std::move(coroutines.back());
                coroutines.pop_back();
            }
            else
            {
                active.timer = 0.0f;
                auto& newInstruction = active.task.handle.promise().current_yield;
                if (std::holds_alternative<WaitForSeconds>(newInstruction))
                {
                    active.timer = std::get<WaitForSeconds>(newInstruction).seconds;
                }
                ++i;
            }
        }
    }

    void StopAllCoroutines()
    {
        coroutines.clear();
    }
};

dae::CoroutineRunner::CoroutineRunner() = default;

dae::CoroutineRunner::~CoroutineRunner()
{
    StopAllCoroutines();
};

dae::CoroutineId dae::CoroutineRunner::StartCoroutine(Task task)
{
    if (!impl) impl = std::make_unique<CoroutineRunnerImpl>();
    return impl->StartCoroutine(std::move(task));
}

bool dae::CoroutineRunner::StopCoroutine(CoroutineId id)
{
    if (!impl) return false;
    return impl->StopCoroutine(id);
}

void dae::CoroutineRunner::Update(float deltaTime)
{
    if (impl) impl->Update(deltaTime);
}

void dae::CoroutineRunner::StopAllCoroutines()
{
    if (impl) impl->StopAllCoroutines();
}