#include <errno.h>
#include <pthread.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <wayland-client.h>

#include "input.h"
#include "registry.h"
#include "wayland_runtime.h"
#include "window.h"

#define MAX_WINDOW_THREADS 128

struct window_thread_slot
{
    pthread_t thread;
    int active;
    struct wl_event_queue *queue;
    struct wayland_window window;
};

static struct wl_display *g_display = NULL;
static struct window_thread_slot g_slots[MAX_WINDOW_THREADS];
static pthread_mutex_t g_thread_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t g_thread_cond = PTHREAD_COND_INITIALIZER;
static atomic_int g_active_threads = 0;
static atomic_bool g_shutdown = 0;
static int g_initialized = 0;

static void signal_thread_exit(struct window_thread_slot *slot)
{
    pthread_mutex_lock(&g_thread_lock);
    slot->active = 0;
    atomic_fetch_sub(&g_active_threads, 1);
    pthread_cond_broadcast(&g_thread_cond);
    pthread_mutex_unlock(&g_thread_lock);
}

static void *window_thread_main(void *arg)
{
    struct window_thread_slot *slot = arg;
    memset(&slot->window, 0, sizeof(slot->window));

    slot->queue = wl_display_create_queue(g_display);
    if (!slot->queue)
    {
        signal_thread_exit(slot);
        return NULL;
    }

    if (registry_thread_bind(g_display, slot->queue) < 0)
    {
        input_cleanup();
        wl_event_queue_destroy(slot->queue);
        slot->queue = NULL;
        signal_thread_exit(slot);
        return NULL;
    }

    if (window_init(g_display, &slot->window) < 0)
    {
        input_cleanup();
        registry_thread_unbind();
        wl_event_queue_destroy(slot->queue);
        slot->queue = NULL;
        signal_thread_exit(slot);
        return NULL;
    }

    while (!atomic_load(&g_shutdown) && !window_should_close(&slot->window))
    {
        int dispatch = wl_display_dispatch_queue(g_display, slot->queue);
        if (dispatch < 0)
        {
            if (errno == EINTR)
                continue;
            atomic_store(&g_shutdown, 1);
            break;
        }
    }
    printf("Window # closed");

    input_cleanup();
    window_destroy(&slot->window);
    registry_thread_unbind();

    if (slot->queue)
    {
        wl_event_queue_destroy(slot->queue);
        slot->queue = NULL;
    }

    signal_thread_exit(slot);
    return NULL;
}

int32_t init_wayland(void)
{
    if (g_initialized)
        return 0;

    g_display = wl_display_connect(NULL);
    if (!g_display)
        return -1;

    struct wl_registry *registry = wl_display_get_registry(g_display);
    if (!registry)
    {
        wl_display_disconnect(g_display);
        g_display = NULL;
        return -1;
    }

    registry_add_listener(registry);
    if (wl_display_roundtrip(g_display) < 0)
    {
        wl_registry_destroy(registry);
        wl_display_disconnect(g_display);
        g_display = NULL;
        return -1;
    }
    wl_registry_destroy(registry);

    atomic_store(&g_shutdown, 0);
    atomic_store(&g_active_threads, 0);
    g_initialized = 1;
    return 0;
}

int32_t run_window(void)
{
    if (!g_initialized || !g_display)
        return -1;

    int slot_index = -1;

    pthread_mutex_lock(&g_thread_lock);
    for (int i = 0; i < MAX_WINDOW_THREADS; ++i)
    {
        if (!g_slots[i].active)
        {
            slot_index = i;
            g_slots[i].active = 1;
            atomic_fetch_add(&g_active_threads, 1);
            break;
        }
    }
    pthread_mutex_unlock(&g_thread_lock);

    if (slot_index < 0)
        return -1;

    int create_res = pthread_create(&g_slots[slot_index].thread, NULL, window_thread_main, &g_slots[slot_index]);
    if (create_res != 0)
    {
        pthread_mutex_lock(&g_thread_lock);
        g_slots[slot_index].active = 0;
        atomic_fetch_sub(&g_active_threads, 1);
        pthread_mutex_unlock(&g_thread_lock);
        return -1;
    }

    pthread_detach(g_slots[slot_index].thread);
    return slot_index;
}

void wait_for_windows(void)
{
    pthread_mutex_lock(&g_thread_lock);
    while (atomic_load(&g_active_threads) > 0)
        pthread_cond_wait(&g_thread_cond, &g_thread_lock);
    pthread_mutex_unlock(&g_thread_lock);
}

void destroy_wayland(void)
{
    if (!g_initialized)
        return;

    wait_for_windows();

    input_cleanup();
    registry_reset_globals();

    if (g_display)
    {
        wl_display_disconnect(g_display);
        g_display = NULL;
    }

    memset(g_slots, 0, sizeof(g_slots));

    g_initialized = 0;
    atomic_store(&g_shutdown, 0);
}
