/*
 * DWN - Desktop Window Manager
 * retoor <retoor@molodetz.nl>
 * Channel Implementation - Thread-Safe Communication
 */

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

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/eventfd.h>
#include <unistd.h>

/* ============================================================================
 * Channel Implementation
 * ============================================================================ */

struct Channel {
    uint32_t capacity;
    uint32_t head;
    uint32_t tail;
    uint32_t count;
    ChannelType type;
    
    void **buffer;
    
    pthread_mutex_t mutex;
    pthread_cond_t not_full;
    pthread_cond_t not_empty;
    
    int event_fd;           /* For select() integration */
    _Atomic int closed;
    _Atomic int waiters;    /* Number of threads waiting */
};

Channel* channel_create(uint32_t capacity)
{
    Channel *ch = dwn_malloc(sizeof(Channel));
    if (!ch) return NULL;
    
    ch->capacity = capacity;
    ch->head = 0;
    ch->tail = 0;
    ch->count = 0;
    ch->type = (capacity == 0) ? CHANNEL_UNBUFFERED : CHANNEL_BUFFERED;
    ch->event_fd = -1;
    atomic_store_explicit(&ch->closed, 0, ATOMIC_RELEASE);
    atomic_store_explicit(&ch->waiters, 0, ATOMIC_RELEASE);
    
    if (capacity > 0) {
        ch->buffer = dwn_malloc(sizeof(void*) * capacity);
        if (!ch->buffer) {
            dwn_free(ch);
            return NULL;
        }
    } else {
        ch->buffer = NULL;
    }
    
    if (pthread_mutex_init(&ch->mutex, NULL) != 0) {
        dwn_free(ch->buffer);
        dwn_free(ch);
        return NULL;
    }
    
    if (pthread_cond_init(&ch->not_full, NULL) != 0) {
        pthread_mutex_destroy(&ch->mutex);
        dwn_free(ch->buffer);
        dwn_free(ch);
        return NULL;
    }
    
    if (pthread_cond_init(&ch->not_empty, NULL) != 0) {
        pthread_cond_destroy(&ch->not_full);
        pthread_mutex_destroy(&ch->mutex);
        dwn_free(ch->buffer);
        dwn_free(ch);
        return NULL;
    }
    
    /* Create eventfd for async notification */
    ch->event_fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
    if (ch->event_fd < 0) {
        pthread_cond_destroy(&ch->not_empty);
        pthread_cond_destroy(&ch->not_full);
        pthread_mutex_destroy(&ch->mutex);
        dwn_free(ch->buffer);
        dwn_free(ch);
        return NULL;
    }
    
    return ch;
}

void channel_destroy(Channel *ch)
{
    if (!ch) return;
    
    channel_close(ch);
    
    pthread_cond_destroy(&ch->not_empty);
    pthread_cond_destroy(&ch->not_full);
    pthread_mutex_destroy(&ch->mutex);
    
    if (ch->event_fd >= 0) {
        close(ch->event_fd);
    }
    
    dwn_free(ch->buffer);
    dwn_free(ch);
}

static void channel_notify(Channel *ch)
{
    /* Signal eventfd */
    uint64_t one = 1;
    ssize_t w __attribute__((unused)) = write(ch->event_fd, &one, sizeof(one));
}

static bool channel_is_full(Channel *ch)
{
    return ch->count >= ch->capacity;
}

static bool channel_is_empty(Channel *ch)
{
    return ch->count == 0;
}

static void channel_push_internal(Channel *ch, void *data)
{
    if (ch->capacity > 0) {
        ch->buffer[ch->head] = data;
        ch->head = (ch->head + 1) % ch->capacity;
        ch->count++;
    }
}

static void* channel_pop_internal(Channel *ch)
{
    if (ch->capacity > 0 && ch->count > 0) {
        void *data = ch->buffer[ch->tail];
        ch->tail = (ch->tail + 1) % ch->capacity;
        ch->count--;
        return data;
    }
    return NULL;
}

ThreadStatus channel_send(Channel *ch, void *data)
{
    return channel_send_timeout(ch, data, 0);
}

ThreadStatus channel_send_timeout(Channel *ch, void *data, uint64_t timeout_ms)
{
    if (!ch || !data) return THREAD_ERROR_INVALID;
    
    pthread_mutex_lock(&ch->mutex);
    
    if (atomic_load_explicit(&ch->closed, ATOMIC_ACQUIRE)) {
        pthread_mutex_unlock(&ch->mutex);
        return THREAD_ERROR_CLOSED;
    }
    
    /* Wait for space */
    if (ch->capacity > 0) {
        while (channel_is_full(ch) && !atomic_load_explicit(&ch->closed, ATOMIC_ACQUIRE)) {
            atomic_fetch_add_explicit(&ch->waiters, 1, ATOMIC_RELAXED);
            
            if (timeout_ms == 0) {
                pthread_cond_wait(&ch->not_full, &ch->mutex);
            } else {
                struct timespec ts;
                clock_gettime(CLOCK_REALTIME, &ts);
                ts.tv_sec += timeout_ms / 1000;
                ts.tv_nsec += (timeout_ms % 1000) * 1000000;
                if (ts.tv_nsec >= 1000000000) {
                    ts.tv_sec++;
                    ts.tv_nsec -= 1000000000;
                }
                
                int ret = pthread_cond_timedwait(&ch->not_full, &ch->mutex, &ts);
                if (ret == ETIMEDOUT) {
                    atomic_fetch_sub_explicit(&ch->waiters, 1, ATOMIC_RELAXED);
                    pthread_mutex_unlock(&ch->mutex);
                    return THREAD_ERROR_TIMEOUT;
                }
            }
            
            atomic_fetch_sub_explicit(&ch->waiters, 1, ATOMIC_RELAXED);
        }
    }
    
    if (atomic_load_explicit(&ch->closed, ATOMIC_ACQUIRE)) {
        pthread_mutex_unlock(&ch->mutex);
        return THREAD_ERROR_CLOSED;
    }
    
    channel_push_internal(ch, data);
    pthread_cond_signal(&ch->not_empty);
    pthread_mutex_unlock(&ch->mutex);
    
    channel_notify(ch);
    
    return THREAD_OK;
}

ThreadStatus channel_try_send(Channel *ch, void *data)
{
    if (!ch || !data) return THREAD_ERROR_INVALID;
    
    if (pthread_mutex_trylock(&ch->mutex) != 0) {
        return THREAD_ERROR_BUSY;
    }
    
    if (atomic_load_explicit(&ch->closed, ATOMIC_ACQUIRE)) {
        pthread_mutex_unlock(&ch->mutex);
        return THREAD_ERROR_CLOSED;
    }
    
    if (ch->capacity > 0 && channel_is_full(ch)) {
        pthread_mutex_unlock(&ch->mutex);
        return THREAD_ERROR_BUSY;
    }
    
    channel_push_internal(ch, data);
    pthread_cond_signal(&ch->not_empty);
    pthread_mutex_unlock(&ch->mutex);
    
    channel_notify(ch);
    
    return THREAD_OK;
}

ThreadStatus channel_recv(Channel *ch, void **data)
{
    return channel_recv_timeout(ch, data, 0);
}

ThreadStatus channel_recv_timeout(Channel *ch, void **data, uint64_t timeout_ms)
{
    if (!ch || !data) return THREAD_ERROR_INVALID;
    
    pthread_mutex_lock(&ch->mutex);
    
    /* Wait for data */
    while (channel_is_empty(ch) && !atomic_load_explicit(&ch->closed, ATOMIC_ACQUIRE)) {
        atomic_fetch_add_explicit(&ch->waiters, 1, ATOMIC_RELAXED);
        
        if (timeout_ms == 0) {
            pthread_cond_wait(&ch->not_empty, &ch->mutex);
        } else {
            struct timespec ts;
            clock_gettime(CLOCK_REALTIME, &ts);
            ts.tv_sec += timeout_ms / 1000;
            ts.tv_nsec += (timeout_ms % 1000) * 1000000;
            if (ts.tv_nsec >= 1000000000) {
                ts.tv_sec++;
                ts.tv_nsec -= 1000000000;
            }
            
            int ret = pthread_cond_timedwait(&ch->not_empty, &ch->mutex, &ts);
            if (ret == ETIMEDOUT) {
                atomic_fetch_sub_explicit(&ch->waiters, 1, ATOMIC_RELAXED);
                pthread_mutex_unlock(&ch->mutex);
                return THREAD_ERROR_TIMEOUT;
            }
        }
        
        atomic_fetch_sub_explicit(&ch->waiters, 1, ATOMIC_RELAXED);
    }
    
    if (channel_is_empty(ch)) {
        pthread_mutex_unlock(&ch->mutex);
        return THREAD_ERROR_CLOSED;
    }
    
    *data = channel_pop_internal(ch);
    pthread_cond_signal(&ch->not_full);
    pthread_mutex_unlock(&ch->mutex);
    
    return THREAD_OK;
}

ThreadStatus channel_try_recv(Channel *ch, void **data)
{
    if (!ch || !data) return THREAD_ERROR_INVALID;
    
    if (pthread_mutex_trylock(&ch->mutex) != 0) {
        return THREAD_ERROR_BUSY;
    }
    
    if (channel_is_empty(ch)) {
        pthread_mutex_unlock(&ch->mutex);
        return atomic_load_explicit(&ch->closed, ATOMIC_ACQUIRE) ? 
               THREAD_ERROR_CLOSED : THREAD_ERROR_BUSY;
    }
    
    *data = channel_pop_internal(ch);
    pthread_cond_signal(&ch->not_full);
    pthread_mutex_unlock(&ch->mutex);
    
    return THREAD_OK;
}

void channel_close(Channel *ch)
{
    if (!ch) return;
    
    pthread_mutex_lock(&ch->mutex);
    atomic_store_explicit(&ch->closed, 1, ATOMIC_RELEASE);
    pthread_cond_broadcast(&ch->not_full);
    pthread_cond_broadcast(&ch->not_empty);
    pthread_mutex_unlock(&ch->mutex);
    
    channel_notify(ch);
}

bool channel_is_closed(Channel *ch)
{
    if (!ch) return true;
    return atomic_load_explicit(&ch->closed, ATOMIC_ACQUIRE) != 0;
}

/* ============================================================================
 * Select Implementation
 * ============================================================================ */

int channel_select(Channel **channels, uint32_t count, uint64_t timeout_ms, 
                   void **out_data)
{
    if (!channels || count == 0) return -1;
    
    uint64_t start_time = 0;
    if (timeout_ms > 0) {
        struct timespec ts;
        clock_gettime(CLOCK_MONOTONIC, &ts);
        start_time = ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
    }
    
    for (;;) {
        /* Try non-blocking receive on all channels */
        for (uint32_t i = 0; i < count; i++) {
            if (channel_try_recv(channels[i], out_data) == THREAD_OK) {
                return (int)i;
            }
        }
        
        /* Check timeout */
        if (timeout_ms > 0) {
            struct timespec ts;
            clock_gettime(CLOCK_MONOTONIC, &ts);
            uint64_t now = ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
            if (now - start_time >= timeout_ms) {
                return -1;
            }
        }
        
        /* Short sleep to avoid busy-wait */
        struct timespec sleep_ts = {0, 1000000}; /* 1ms */
        nanosleep(&sleep_ts, NULL);
    }
}
