/*
 * DWN - Desktop Window Manager
 * retoor <retoor@molodetz.nl>
 * Timer/Scheduler Implementation - Delayed Execution
 */

#include "threading.h"
#include "util.h"

#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/timerfd.h>
#include <errno.h>
#include <poll.h>

#define MAX_TIMERS 256
#define TIMER_HASH_SIZE 64

/* ============================================================================
 * Timer Types
 * ============================================================================ */

typedef struct Timer {
    TimerId id;
    uint64_t fire_time;
    uint64_t interval;
    TimerCallback callback;
    void *user_data;
    bool active;
    bool repeating;
    struct Timer *next;  /* For hash collision chain */
} Timer;

static struct {
    Timer timers[MAX_TIMERS];
    Timer *id_hash[TIMER_HASH_SIZE];
    pthread_mutex_t mutex;
    int timer_fd;
    _Atomic uint64_t next_id;
    bool initialized;
} g_timer_state;

/* ============================================================================
 * Helper Functions
 * ============================================================================ */

static uint64_t get_monotonic_ms(void)
{
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}

static uint32_t hash_timer_id(TimerId id)
{
    return (uint32_t)(id % TIMER_HASH_SIZE);
}

static Timer* find_timer(TimerId id)
{
    uint32_t hash = hash_timer_id(id);
    Timer *t = g_timer_state.id_hash[hash];
    while (t) {
        if (t->id == id && t->active) return t;
        t = t->next;
    }
    return NULL;
}

static void insert_timer(Timer *timer)
{
    uint32_t hash = hash_timer_id(timer->id);
    timer->next = g_timer_state.id_hash[hash];
    g_timer_state.id_hash[hash] = timer;
}

static void remove_timer(Timer *timer)
{
    uint32_t hash = hash_timer_id(timer->id);
    Timer **pp = &g_timer_state.id_hash[hash];
    while (*pp) {
        if (*pp == timer) {
            *pp = timer->next;
            return;
        }
        pp = &(*pp)->next;
    }
}

static void update_timer_fd(void)
{
    /* Find next timer to fire */
    uint64_t next_fire = 0;
    bool has_timer = false;
    
    for (int i = 0; i < MAX_TIMERS; i++) {
        if (g_timer_state.timers[i].active) {
            if (!has_timer || g_timer_state.timers[i].fire_time < next_fire) {
                next_fire = g_timer_state.timers[i].fire_time;
                has_timer = true;
            }
        }
    }
    
    if (!has_timer) return;
    
    /* Set timerfd to fire at next_fire */
    struct itimerspec its;
    uint64_t now = get_monotonic_ms();
    
    if (next_fire <= now) {
        its.it_value.tv_sec = 0;
        its.it_value.tv_nsec = 1; /* Fire immediately */
    } else {
        uint64_t diff = next_fire - now;
        its.it_value.tv_sec = diff / 1000;
        its.it_value.tv_nsec = (diff % 1000) * 1000000;
    }
    
    its.it_interval.tv_sec = 0;
    its.it_interval.tv_nsec = 0;
    
    timerfd_settime(g_timer_state.timer_fd, 0, &its, NULL);
}

/* ============================================================================
 * Timer API Implementation
 * ============================================================================ */

bool timer_init(void)
{
    if (g_timer_state.initialized) return true;
    
    memset(&g_timer_state, 0, sizeof(g_timer_state));
    
    if (pthread_mutex_init(&g_timer_state.mutex, NULL) != 0) {
        return false;
    }
    
    g_timer_state.timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
    if (g_timer_state.timer_fd < 0) {
        pthread_mutex_destroy(&g_timer_state.mutex);
        return false;
    }
    
    atomic_store_explicit(&g_timer_state.next_id, 1, ATOMIC_RELEASE);
    g_timer_state.initialized = true;
    
    return true;
}

void timer_shutdown(void)
{
    if (!g_timer_state.initialized) return;
    
    close(g_timer_state.timer_fd);
    pthread_mutex_destroy(&g_timer_state.mutex);
    g_timer_state.initialized = false;
}

static TimerId allocate_timer_id(void)
{
    return atomic_fetch_add_explicit(&g_timer_state.next_id, 1, ATOMIC_RELAXED);
}

TimerId timer_schedule(uint64_t delay_ms, TimerCallback callback, void *user_data)
{
    return timer_schedule_repeating(delay_ms, callback, user_data);
}

TimerId timer_schedule_repeating(uint64_t interval_ms, TimerCallback callback, 
                                  void *user_data)
{
    if (!g_timer_state.initialized || !callback || interval_ms == 0) return 0;
    
    pthread_mutex_lock(&g_timer_state.mutex);
    
    /* Find free slot */
    Timer *timer = NULL;
    for (int i = 0; i < MAX_TIMERS; i++) {
        if (!g_timer_state.timers[i].active) {
            timer = &g_timer_state.timers[i];
            break;
        }
    }
    
    if (!timer) {
        pthread_mutex_unlock(&g_timer_state.mutex);
        LOG_ERROR("No free timer slots");
        return 0;
    }
    
    timer->id = allocate_timer_id();
    timer->fire_time = get_monotonic_ms() + interval_ms;
    timer->interval = interval_ms;
    timer->callback = callback;
    timer->user_data = user_data;
    timer->active = true;
    timer->repeating = true;
    
    insert_timer(timer);
    update_timer_fd();
    
    pthread_mutex_unlock(&g_timer_state.mutex);
    
    return timer->id;
}

bool timer_cancel(TimerId id)
{
    if (!g_timer_state.initialized || id == 0) return false;
    
    pthread_mutex_lock(&g_timer_state.mutex);
    
    Timer *timer = find_timer(id);
    if (timer) {
        timer->active = false;
        remove_timer(timer);
        pthread_mutex_unlock(&g_timer_state.mutex);
        return true;
    }
    
    pthread_mutex_unlock(&g_timer_state.mutex);
    return false;
}

bool timer_exists(TimerId id)
{
    if (!g_timer_state.initialized || id == 0) return false;
    
    pthread_mutex_lock(&g_timer_state.mutex);
    bool exists = (find_timer(id) != NULL);
    pthread_mutex_unlock(&g_timer_state.mutex);
    
    return exists;
}

uint32_t timer_process(void)
{
    if (!g_timer_state.initialized) return 0;
    
    /* Acknowledge timerfd */
    uint64_t expirations;
    ssize_t n = read(g_timer_state.timer_fd, &expirations, sizeof(expirations));
    if (n < 0 && errno != EAGAIN) {
        return 0;
    }
    
    uint64_t now = get_monotonic_ms();
    uint32_t fired = 0;
    
    pthread_mutex_lock(&g_timer_state.mutex);
    
    for (int i = 0; i < MAX_TIMERS; i++) {
        Timer *timer = &g_timer_state.timers[i];
        if (!timer->active) continue;
        
        if (timer->fire_time <= now) {
            /* Fire timer (outside lock to avoid deadlock) */
            TimerCallback cb = timer->callback;
            void *data = timer->user_data;
            TimerId id = timer->id;
            bool repeating = timer->repeating;
            uint64_t interval = timer->interval;
            
            if (!repeating) {
                timer->active = false;
                remove_timer(timer);
            } else {
                timer->fire_time = now + interval;
            }
            
            pthread_mutex_unlock(&g_timer_state.mutex);
            cb(id, data);
            pthread_mutex_lock(&g_timer_state.mutex);
            
            fired++;
        }
    }
    
    update_timer_fd();
    pthread_mutex_unlock(&g_timer_state.mutex);
    
    return fired;
}

int timer_get_poll_fd(void)
{
    return g_timer_state.initialized ? g_timer_state.timer_fd : -1;
}
