/*
    * InputEvents.cpp
    * Shared input activity event source
    * Copyright (c) 2026 Daniel Hammer
*/

#include "InputEvents.hpp"
#include <atomic>
#include <Sched/Scheduler.hpp>
#include <Timekeeping/ApicTimer.hpp>

namespace Drivers::InputEvents {

    static std::atomic<uint64_t> g_serial{1};
    static uint8_t g_waitObject;

    struct WaitContext {
        uint64_t observedSerial;
    };

    static bool SerialStillObserved(void* rawContext) {
        auto* context = static_cast<WaitContext*>(rawContext);
        return context != nullptr &&
               g_serial.load(std::memory_order_acquire) == context->observedSerial;
    }

    uint64_t GetSerial() {
        return g_serial.load(std::memory_order_acquire);
    }

    void NotifyActivity() {
        Timekeeping::NoteInteractiveActivity();
        g_serial.fetch_add(1, std::memory_order_release);
        Sched::WakeObjectWaiters(&g_waitObject);
    }

    uint64_t WaitForChange(uint64_t observedSerial, uint64_t timeoutMs) {
        uint64_t current = GetSerial();
        if (current != observedSerial || timeoutMs == 0) {
            return current;
        }

        WaitContext context{observedSerial};
        uint64_t start = Timekeeping::GetMilliseconds();

        for (;;) {
            current = GetSerial();
            if (current != observedSerial) {
                return current;
            }

            uint64_t waitMs = 0;
            if (timeoutMs == UINT64_MAX) {
                waitMs = 0;
            } else {
                uint64_t elapsed = Timekeeping::GetMilliseconds() - start;
                if (elapsed >= timeoutMs) {
                    return current;
                }
                waitMs = timeoutMs - elapsed;
                if (waitMs == 0) {
                    waitMs = 1;
                }
            }

            Sched::BlockOnObjectIf(&g_waitObject, waitMs, SerialStillObserved, &context);
        }
    }

}
