#include "registry.h"
#include "input.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdatomic.h>
#include <poll.h>
#include <errno.h>
#include <wayland-client.h>
#include "xdg-shell-client-protocol.h"

static void wm_base_ping(void *data, struct xdg_wm_base *wm_base, uint32_t serial)
{
    (void)data;
    xdg_wm_base_pong(wm_base, serial);
}

static struct xdg_wm_base_listener wm_base_listener = {
    .ping = wm_base_ping
};


/* Глобальные объекты, привязанные к единому registry-потоку (общие для процесса) */
static struct wl_compositor *global_compositor;
static struct wl_shm *global_shm;
static struct xdg_wm_base *global_wm_base;
/* Глобальный реестр живёт на весь срок программы (удаляется при сбросе) */
static struct wl_registry *global_registry = NULL;

/* Поток обработки глобальных событий: использует дефолтную очередь */
static pthread_t global_thread;
static _Atomic int global_thread_running = 0;
static struct wl_display *global_display = NULL;

static uint32_t clamp_version(uint32_t announced, uint32_t supported)
{
    return (announced < supported) ? announced : supported;
}

static void registry_global(void *data, struct wl_registry *reg, uint32_t name, const char *intf, uint32_t version)
{
    printf("%u\t%s\tv:%u\n", name, intf, version);
    if (!strcmp(intf, wl_compositor_interface.name))
    {
        uint32_t ver = clamp_version(version, wl_compositor_interface.version);
        /* Привязать compositor на дефолтной очереди сразу */
        if (!global_compositor)
            global_compositor = wl_registry_bind(reg, name, &wl_compositor_interface, ver);
    }
    else if (!strcmp(intf, wl_shm_interface.name))
    {
        uint32_t ver = clamp_version(version, wl_shm_interface.version);
        if (!global_shm)
            global_shm = wl_registry_bind(reg, name, &wl_shm_interface, ver);
    }
    else if (!strcmp(intf, xdg_wm_base_interface.name))
    {
        uint32_t ver = clamp_version(version, xdg_wm_base_interface.version);
        if (!global_wm_base)
            global_wm_base = wl_registry_bind(reg, name, &xdg_wm_base_interface, ver);
        if (global_wm_base)
            xdg_wm_base_add_listener(global_wm_base, &wm_base_listener, NULL);
    }
    else if (!strcmp(intf, wl_seat_interface.name))
    {
        uint32_t ver = clamp_version(version, wl_seat_interface.version);
        /* Привязать seat на дефолтной очереди */
        struct wl_seat *seat = wl_registry_bind(reg, name, &wl_seat_interface, ver);
        if (seat)
            input_register_seat(seat);
    }
}

static void registry_global_remove(void *data, struct wl_registry *reg, uint32_t name)
{
    (void)data;
    (void)reg;
    (void)name;
}

static const struct wl_registry_listener registry_listener = {
    .global = registry_global,
    .global_remove = registry_global_remove
};


static void destroy_global_objects(void)
{
    if (global_compositor)
    {
        wl_compositor_destroy(global_compositor);
        global_compositor = NULL;
    }
    if (global_shm)
    {
        wl_shm_destroy(global_shm);
        global_shm = NULL;
    }
    if (global_wm_base)
    {
        xdg_wm_base_destroy(global_wm_base);
        global_wm_base = NULL;
    }
    if (global_registry)
    {
        wl_registry_destroy(global_registry);
        global_registry = NULL;
    }
}


/* Привязка глобального реестра — привязать compositor/shm/xdg_wm_base и
 * запустить поток обработки глобальной очереди. Возвращает 0 при успехе,
 * -1 при ошибке.
 */
static void *registry_global_thread(void *arg)
{
    struct wl_display *display = arg;
    int fd = wl_display_get_fd(display);
    while (global_thread_running)
    {
        struct pollfd pfd;
        pfd.fd = fd;
        pfd.events = POLLIN;
        pfd.revents = 0;
        int ret = poll(&pfd, 1, 100); /* таймаут 100 ms для проверки флага запуска */
        if (ret == -1)
        {
            if (errno == EINTR)
                continue;
            break;
        }
        if (ret == 0)
            continue;
        if (pfd.revents & POLLIN)
        {
            int r = wl_display_dispatch(display);
            if (r < 0)
                break;
        }
    }
    return NULL;
}

int registry_global_bind(struct wl_display *display)
{
    if (!display)
        return -1;
    if (global_thread_running)
        return 0; /* уже привязан */
    /* Используем дефолтную очередь для глобальных объектов.
     * Добавим listener и сделаем roundtrip, чтобы вызывающий код
     * мог не делать этого заранее. Реестр привяжет глобалы сразу
     * при получении событий. */

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

    wl_registry_add_listener(registry, &registry_listener, NULL);
    if (wl_display_roundtrip(display) < 0)
    {
        /* произошла ошибка — уничтожить registry и вернуть ошибку */
        wl_registry_destroy(registry);
        return -1;
    }
    global_registry = registry;

    if (!global_compositor || !global_shm || !global_wm_base)
    {
        destroy_global_objects();
        return -1;
    }

    /* Запустить поток обработки событий дефолтной очереди */
    global_thread_running = 1;
    global_display = display;
    int thr_res = pthread_create(&global_thread, NULL, registry_global_thread, display);
    if (thr_res != 0)
    {
        global_thread_running = 0;
        destroy_global_objects();
        return -1;
    }

    return 0;
}

void registry_global_unbind(void)
{
    if (!global_thread_running)
        return;

    /* Корректно остановить поток */
    global_thread_running = 0;
    pthread_join(global_thread, NULL);
    global_display = NULL;

    destroy_global_objects();

    return;
}

struct wl_compositor *registry_get_compositor(void)
{
    return global_compositor;
}

struct wl_shm *registry_get_shm(void)
{
    return global_shm;
}

struct xdg_wm_base *registry_get_xdg_wm_base(void)
{
    return global_wm_base;
}

