#include "libinputdbusserver.h"

#include <fcntl.h>
#include <unistd.h>
#include <libudev.h>
#include <libinput.h>
#include <libevdev/libevdev.h>

#include <QtDBus/QDBusConnection>
#include <QDebug>

static int openRestricted(const char *path, int flags, void *userData);
static void closeRestricted(int fd, void *userData);

struct libinput_interface libinputInterface {
    openRestricted,
    closeRestricted
};


LibinputDBusServer::LibinputDBusServer(QObject* parent) : QObject(parent)
{
    if (!QDBusConnection::systemBus().registerService("org.ukui.libinput.proxy"))
        qWarning() << "register dbus service error!";

    if (!QDBusConnection::systemBus().registerObject("/org/ukui/libinput/proxy",
                                                     "org.ukui.libinput.proxy", this,
                                                     QDBusConnection :: ExportAllSlots |
                                                     QDBusConnection :: ExportAllSignals))
        qWarning() << "register object error;";
}


int LibinputDBusServer::libinputTabletSwitchState()
{
    struct libinput *li = nullptr;
    struct libinput_event *ev = nullptr;
    struct udev* udev = nullptr;

    udev = udev_new();
    li = libinput_udev_create_context(
                &libinputInterface, nullptr, udev);
    if (!li) {
        throw std::runtime_error{"Error initialising UKUI-Input-Gather-Server: libinput"};
    }

    int seat = libinput_udev_assign_seat(li, "seat0");
    if (seat != 0) {
        throw std::runtime_error{"Error initialising UKUI-Input-Gather-Server: libinput seat"};
    }

    libinput_dispatch(li);
    while ((ev = libinput_get_event(li))) {

        if (libinput_event_get_type(ev) == LIBINPUT_EVENT_DEVICE_ADDED) {
            int state = deviceTabletModeState(ev);
            if (state >= 0)
                return state;
        }

        libinput_event_destroy(ev);
        libinput_dispatch(li);
    }

    return -1;
}

int LibinputDBusServer::deviceTabletModeState(libinput_event *e)
{
    struct libinput_device *device = libinput_event_get_device(e);

    int hasSwitch = libinput_device_switch_has_switch(device, LIBINPUT_SWITCH_TABLET_MODE);

    if (hasSwitch > 0) {
        struct libevdev *evdev = NULL;
        const char* sysname   = libinput_device_get_sysname(device);
        std::string eventPath = std::string("/dev/input/") + sysname;

        int fd = open(eventPath.c_str(), O_RDONLY | O_NONBLOCK);
        int rc = libevdev_new_from_fd(fd, &evdev);
        if (rc < 0) {
            close(fd);
            qCritical() << "Failed to init libevdev" << strerror(-rc);
            return -1;
        }

        int state = libevdev_get_event_value(evdev, EV_SW, SW_TABLET_MODE);

        libevdev_free(evdev);
        return state;
    }

    return -1;
}

int openRestricted(const char *path, int flags, void *userData)
{
    bool *grab = static_cast<bool*>(userData);
    int fd = open(path, flags);

    if (fd < 0)
        fprintf(stderr, "Failed to open %s (%s)\n",
            path, strerror(errno));
    else if (grab && *grab && ioctl(fd, EVIOCGRAB, (void*)1) == -1)
        fprintf(stderr, "Grab requested, but failed for %s (%s)\n",
            path, strerror(errno));

    return fd < 0 ? -errno : fd;
}
void closeRestricted(int fd, void *userData)
{
    Q_UNUSED(userData)
    close(fd);
}
