/*
 * Copyright (C) 2024 Uniontech Technology Co., Ltd.
 *
 * Author:     xinbo wang <wangxinbo@uniontech.com>
 *
 * Maintainer: xinbo wang <wangxinbo@uniontech.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "displayjack_kvm.h"

#include "wayland-dde-kvm-client-protocol.h"

#include <string.h>
#include <pthread.h>
#include <map>

struct wl_display *m_pDisplay = NULL;
struct wl_registry *m_pRegistry = NULL;
struct dde_kvm *dde_kvm = NULL;
struct dde_kvm_pointer *dde_kvm_pointer = NULL;
struct dde_kvm_keyboard *dde_kvm_keyboard = NULL;
pthread_mutex_t m_connectLock;
pthread_t m_dispatch;
bool m_lock = true;
std::map<kvm_pointer_motion_cb, void *> pointer_motion_cb_map;
std::map<kvm_pointer_button_cb, void *> pointer_button_cb_map;
std::map<kvm_pointer_axis_cb, void *> pointer_axis_cb_map;
std::map<kvm_keyboard_key_cb, void *> keyboard_key_cb_map;

void handle_pointer_motion(void *data,
                          struct dde_kvm_pointer *dde_kvm_pointer,
                          uint32_t time,
                          wl_fixed_t x,
                          wl_fixed_t y)
{
    for (auto it = pointer_motion_cb_map.begin(); it != pointer_motion_cb_map.end(); ++it) {
        if (it->first) {
            it->first(it->second, time, x, y);
        }
    }
}

void handle_pointer_button(void *data,
                          struct dde_kvm_pointer *dde_kvm_pointer,
                          uint32_t serial,
                          uint32_t time,
                          uint32_t state)
{
    for (auto it = pointer_button_cb_map.begin(); it != pointer_button_cb_map.end(); ++it) {
        if (it->first) {
            it->first(it->second, time, serial, (kvm_pointer_button_state)state);
        }
    }
}

void handle_pointer_axis(void *data,
                        struct dde_kvm_pointer *dde_kvm_pointer,
                        uint32_t time,
                        uint32_t type,
                        wl_fixed_t value)
{
    for (auto it = pointer_axis_cb_map.begin(); it != pointer_axis_cb_map.end(); ++it) {
        if (it->first) {
            it->first(it->second, time, (kvm_pointer_scroll_type)type, value);
        }
    }
}

void handle_keyboard_key(void *data,
                        struct dde_kvm_keyboard *dde_kvm_keyboard,
                        uint32_t serial,
                        uint32_t time,
                        uint32_t key,
                        uint32_t state)
{
    for (auto it = keyboard_key_cb_map.begin(); it != keyboard_key_cb_map.end(); ++it) {
        if (it->first) {
            it->first(it->second, time, serial, (kvm_keyboard_key_state)state, key);
        }
    }
}

static const struct dde_kvm_pointer_listener dde_kvm_pointer_listener = {
    .motion = handle_pointer_motion,
    .button = handle_pointer_button,
    .axis = handle_pointer_axis,
};

static const struct dde_kvm_keyboard_listener dde_kvm_keyboard_listener = {
    .key = handle_keyboard_key,
};

void kvm_handle_global_listener(void *data, struct wl_registry *wl_registry, int name, const char *interface, int version)
{
    if (strcmp(interface, dde_kvm_interface.name) == 0) {
        if (dde_kvm == NULL) {
            dde_kvm = (struct dde_kvm *)wl_registry_bind(wl_registry, name, &dde_kvm_interface, version);

            dde_kvm_pointer = dde_kvm_get_dde_kvm_pointer(dde_kvm);
            dde_kvm_keyboard = dde_kvm_get_dde_kvm_keyboard(dde_kvm);

            dde_kvm_pointer_add_listener(dde_kvm_pointer, &dde_kvm_pointer_listener, nullptr);
            dde_kvm_keyboard_add_listener(dde_kvm_keyboard, &dde_kvm_keyboard_listener, nullptr);
        }
    }
}

static void handle_global(void *data,
                          struct wl_registry *wl_registry,
                          uint32_t name,
                          const char *interface,
                          uint32_t version)
{
    kvm_handle_global_listener(data, wl_registry, name, interface, version);
}

static void handle_global_remove(void *data,
                                  struct wl_registry *wl_registry,
                                  uint32_t name)
{
    // Who cares?
}

static const struct wl_registry_listener registry_listener = {
    .global = handle_global,
    .global_remove = handle_global_remove,
};


static void *wayland_dispatch(void *arg)
{
    if (!m_pDisplay)
        return NULL;

    while (m_lock) {
        wl_display_dispatch(m_pDisplay);
    }

    return NULL;
}

int init_kvm()
{
    m_pDisplay = wl_display_connect(NULL);
    if (!m_pDisplay) {
        return -1;
    }

    m_pRegistry = wl_display_get_registry(m_pDisplay);
    if (!m_pRegistry) {
        return -2;
    }

    wl_registry_add_listener(m_pRegistry, &registry_listener, nullptr);
    wl_display_roundtrip(m_pDisplay);

    pthread_mutex_init(&m_connectLock, NULL);
    m_lock = true;
    pthread_create(&m_dispatch, NULL, wayland_dispatch, NULL);

    return 0;
}

int destory_kvm()
{
    if (!m_pDisplay) {
        return -1;
    }

    pthread_mutex_lock(&m_connectLock);
    m_lock = false;
    pthread_mutex_unlock(&m_connectLock);

    // cancel pthread
    struct wl_callback *callback = wl_display_sync(m_pDisplay);
    wl_display_flush(m_pDisplay);
    if (callback) {
        wl_callback_destroy(callback);
    }
    pthread_join(m_dispatch, NULL);

    if (dde_kvm) {
        dde_kvm_destroy(dde_kvm);
    }

    wl_display_disconnect(m_pDisplay);
    return 0;
}

void kvm_enable_pointer(kvm_bool is_enable)
{
    if (!dde_kvm_pointer) {
        return;
    }
    dde_kvm_pointer_enable_pointer(dde_kvm_pointer, is_enable);
    wl_display_flush(m_pDisplay);
}

void kvm_enable_cursor(kvm_bool is_enable)
{
    if (!dde_kvm_pointer) {
        return;
    }
    dde_kvm_pointer_enable_cursor(dde_kvm_pointer, is_enable);
    wl_display_flush(m_pDisplay);
}

void kvm_pointer_set_pos(double x, double y)
{
    if (!dde_kvm_pointer) {
        return;
    }
    dde_kvm_pointer_set_pos(dde_kvm_pointer, x, y);
    wl_display_flush(m_pDisplay);
}

void kvm_enable_keyboard(kvm_bool is_enable)
{
    if (!dde_kvm_keyboard) {
        return;
    }
    dde_kvm_keyboard_enable_keyboard(dde_kvm_keyboard, is_enable);
    wl_display_flush(m_pDisplay);
}

void kvm_register_pointer_motion(void *data, kvm_pointer_motion_cb cb)
{
    pointer_motion_cb_map[cb] = data;
}

void kvm_register_pointer_button(void* data, kvm_pointer_button_cb cb)
{
    pointer_button_cb_map[cb] = data;
}

void kvm_register_pointer_axis(void* data, kvm_pointer_axis_cb cb)
{
    pointer_axis_cb_map[cb] = data;
}

void kvm_register_keyboard_key(void* data, kvm_keyboard_key_cb cb)
{
    keyboard_key_cb_map[cb] = data;
}

void kvm_unregister_pointer_motion(kvm_pointer_motion_cb cb)
{
    pointer_motion_cb_map.erase(cb);
}

void kvm_unregister_pointer_button(kvm_pointer_button_cb cb)
{
    pointer_button_cb_map.erase(cb);
}

void kvm_unregister_pointer_axis(kvm_pointer_axis_cb cb)
{
    pointer_axis_cb_map.erase(cb);
}

void kvm_unregister_keyboard_key(kvm_keyboard_key_cb cb)
{
    keyboard_key_cb_map.erase(cb);
}
