#include "EventLoopManager.hpp"
#include "../../debug/log/Logger.hpp"
#include "../../Compositor.hpp"
#include "../../config/shared/inotify/ConfigWatcher.hpp"

#include <algorithm>
#include <cstring>
#include <limits>
#include <ranges>

#include <sys/timerfd.h>
#include <ctime>

#include <aquamarine/backend/Backend.hpp>
using namespace Hyprutils::OS;

#define TIMESPEC_NSEC_PER_SEC 1000000000L

static uint64_t LAST_DO_LATER_SEQ = 1;

SEventLoopDoLaterLock::SEventLoopDoLaterLock(uint64_t seq_) : seq(seq_) {
    ;
}

SEventLoopDoLaterLock::~SEventLoopDoLaterLock() {
    if (g_pEventLoopManager && seq > 0)
        g_pEventLoopManager->removeDoLater(seq);
}

CEventLoopManager::CEventLoopManager(wl_display* display, wl_event_loop* wlEventLoop) {
    m_timers.timerfd  = CFileDescriptor{timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC)};
    m_wayland.loop    = wlEventLoop;
    m_wayland.display = display;
}

CEventLoopManager::~CEventLoopManager() {
    for (auto const& [_, eventSourceData] : m_aqEventSources) {
        wl_event_source_remove(eventSourceData.eventSource);
    }

    m_readableWaiters.clear();

    if (m_wayland.eventSource)
        wl_event_source_remove(m_wayland.eventSource);
    if (m_idle.eventSource)
        wl_event_source_remove(m_idle.eventSource);
    if (m_configWatcherInotifySource)
        wl_event_source_remove(m_configWatcherInotifySource);
}

static int timerWrite(int fd, uint32_t mask, void* data) {
    if (!CFileDescriptor::isReadable(fd))
        Log::logger->log(Log::ERR, "timerWrite: triggered a non readable event on fd : {}", fd);
    else {
        uint64_t expirations;
        if (read(fd, &expirations, sizeof(expirations)) < 0)
            Log::logger->log(Log::ERR, "timerWrite: read failed on fd {}: {}", fd, strerror(errno));
    }

    g_pEventLoopManager->onTimerFire();
    return 0;
}

static int aquamarineFDWrite(int fd, uint32_t mask, void* data) {
    auto POLLFD = sc<Aquamarine::SPollFD*>(data);
    POLLFD->onSignal();
    return 0;
}

static int configWatcherWrite(int fd, uint32_t mask, void* data) {
    Config::watcher()->onInotifyEvent();
    return 0;
}

static int handleWaiterFD(int fd, uint32_t mask, void* data) {
    auto waiter = sc<CEventLoopManager::SReadableWaiter*>(data);

    if (!waiter) {
        Log::logger->log(Log::ERR, "handleWaiterFD: failed casting waiter");
        return 0;
    }

    if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) {
        Log::logger->log(Log::ERR, "handleWaiterFD: readable waiter error");
        g_pEventLoopManager->onFdReadableFail(waiter);
        return 0;
    }

    if (mask & WL_EVENT_READABLE)
        g_pEventLoopManager->onFdReadable(waiter);

    return 0;
}

void CEventLoopManager::onFdReadable(SReadableWaiter* waiter) {
    auto it = std::ranges::find_if(m_readableWaiters, [waiter](const UP<SReadableWaiter>& w) { return waiter == w.get() && w->fd == waiter->fd && w->source == waiter->source; });

    // ???
    if (it == m_readableWaiters.end())
        return;

    if (waiter->source) { // remove even_source if fn() somehow causes a reentry
        wl_event_source_remove(waiter->source);
        waiter->source = nullptr;
    }

    UP<SReadableWaiter> taken = std::move(*it);
    m_readableWaiters.erase(it);

    if (taken->fn)
        taken->fn();
}

void CEventLoopManager::onFdReadableFail(SReadableWaiter* waiter) {
    auto it = std::ranges::find_if(m_readableWaiters, [waiter](const UP<SReadableWaiter>& w) { return waiter == w.get() && w->fd == waiter->fd && w->source == waiter->source; });

    // ???
    if (it == m_readableWaiters.end())
        return;

    m_readableWaiters.erase(it);
}

void CEventLoopManager::enterLoop() {
    m_wayland.eventSource = wl_event_loop_add_fd(m_wayland.loop, m_timers.timerfd.get(), WL_EVENT_READABLE, timerWrite, nullptr);

    if (const auto& FD = Config::watcher()->getInotifyFD(); FD.isValid())
        m_configWatcherInotifySource = wl_event_loop_add_fd(m_wayland.loop, FD.get(), WL_EVENT_READABLE, configWatcherWrite, nullptr);

    syncPollFDs();
    m_listeners.pollFDsChanged = g_pCompositor->m_aqBackend->events.pollFDsChanged.listen([this] { syncPollFDs(); });

    // if we have a session, dispatch it to get the pending input devices
    if (g_pCompositor->m_aqBackend->hasSession())
        g_pCompositor->m_aqBackend->session->dispatchPendingEventsAsync();

    wl_display_run(m_wayland.display);

    Log::logger->log(Log::DEBUG, "Kicked off the event loop! :(");
}

void CEventLoopManager::onTimerFire() {
    const auto CPY = m_timers.timers;
    for (auto const& t : CPY) {
        if (t.strongRef() > 2 /* if it's 2, it was lost. Don't call it. */ && t->passed() && !t->cancelled())
            t->call(t);
    }

    scheduleRecalc();
}

void CEventLoopManager::addTimer(SP<CEventLoopTimer> timer) {
    if (std::ranges::contains(m_timers.timers, timer))
        return;
    m_timers.timers.emplace_back(timer);
    scheduleRecalc();
}

void CEventLoopManager::removeTimer(SP<CEventLoopTimer> timer) {
    if (!std::ranges::contains(m_timers.timers, timer))
        return;
    std::erase_if(m_timers.timers, [timer](const auto& t) { return timer == t; });
    scheduleRecalc();
}

static void timespecAddNs(timespec* pTimespec, int64_t delta) {
    auto delta_ns_low = delta % TIMESPEC_NSEC_PER_SEC;
    auto delta_s_high = delta / TIMESPEC_NSEC_PER_SEC;

    pTimespec->tv_sec += delta_s_high;

    pTimespec->tv_nsec += delta_ns_low;
    if (pTimespec->tv_nsec >= TIMESPEC_NSEC_PER_SEC) {
        pTimespec->tv_nsec -= TIMESPEC_NSEC_PER_SEC;
        ++pTimespec->tv_sec;
    }
}

void CEventLoopManager::scheduleRecalc() {
    // do not do it instantly, do it later. Avoid recursive access to the timer
    // vector, it could be catastrophic if we modify it while iterating

    if (m_timers.recalcScheduled)
        return;

    m_timers.recalcScheduled = true;

    doLater([this] { nudgeTimers(); });
}

void CEventLoopManager::nudgeTimers() {
    m_timers.recalcScheduled = false;

    // remove timers that have gone missing
    std::erase_if(m_timers.timers, [](const auto& t) { return t.strongRef() <= 1; });

    long nextTimerUs = 10L * 1000 * 1000; // 10s

    for (auto const& t : m_timers.timers) {
        if (auto const& µs = t->leftUs(); µs < nextTimerUs)
            nextTimerUs = µs;
    }

    nextTimerUs = std::clamp(nextTimerUs + 1, 1L, std::numeric_limits<long>::max());

    timespec now;
    clock_gettime(CLOCK_MONOTONIC, &now);
    timespecAddNs(&now, nextTimerUs * 1000L);

    itimerspec ts = {.it_value = now};

    timerfd_settime(m_timers.timerfd.get(), TFD_TIMER_ABSTIME, &ts, nullptr);
}

uint64_t CEventLoopManager::doLater(const std::function<void()>& fn) {
    const uint64_t NEW_SEQ = ++LAST_DO_LATER_SEQ;

    m_idle.fns.emplace_back(NEW_SEQ, fn);

    if (m_idle.eventSource)
        return NEW_SEQ;

    m_idle.eventSource = wl_event_loop_add_idle(
        m_wayland.loop,
        [](void* data) {
            auto IDLE = sc<CEventLoopManager::SIdleData*>(data);
            auto fns  = std::move(IDLE->fns);
            IDLE->fns.clear();
            IDLE->eventSource = nullptr;
            for (auto& f : fns) {
                if (f.second)
                    f.second();
            }
        },
        &m_idle);

    return NEW_SEQ;
}

void CEventLoopManager::removeDoLater(uint64_t seq) {
    std::erase_if(m_idle.fns, [&seq](const auto& e) { return e.first == seq; });

    if (m_idle.fns.empty() && m_idle.eventSource) {
        wl_event_source_remove(m_idle.eventSource);
        m_idle.eventSource = nullptr;
    }
}

UP<SEventLoopDoLaterLock> CEventLoopManager::doLaterLock(const std::function<void()>& fn) {
    return makeUnique<SEventLoopDoLaterLock>(doLater(fn));
}

void CEventLoopManager::doOnReadable(CFileDescriptor fd, std::function<void()>&& fn) {
    if (!fd.isValid() || fd.isReadable()) {
        fn();
        return;
    }

    auto& waiter   = m_readableWaiters.emplace_back(makeUnique<SReadableWaiter>(nullptr, std::move(fd), std::move(fn)));
    waiter->source = wl_event_loop_add_fd(g_pEventLoopManager->m_wayland.loop, waiter->fd.get(), WL_EVENT_READABLE, ::handleWaiterFD, waiter.get());
}

void CEventLoopManager::syncPollFDs() {
    auto aqPollFDs = g_pCompositor->m_aqBackend->getPollFDs();

    std::erase_if(m_aqEventSources, [&](const auto& item) {
        auto const& [fd, eventSourceData] = item;

        // If no pollFD has the same fd, remove this event source
        const bool shouldRemove = std::ranges::none_of(aqPollFDs, [&](const auto& pollFD) { return pollFD->fd == fd; });

        if (shouldRemove)
            wl_event_source_remove(eventSourceData.eventSource);

        return shouldRemove;
    });

    for (auto& fd : aqPollFDs | std::views::filter([&](SP<Aquamarine::SPollFD> fd) { return !m_aqEventSources.contains(fd->fd); })) {
        auto eventSource         = wl_event_loop_add_fd(m_wayland.loop, fd->fd, WL_EVENT_READABLE, aquamarineFDWrite, fd.get());
        m_aqEventSources[fd->fd] = {.pollFD = fd, .eventSource = eventSource};
    }
}
