
//! \warning Because BOOL and Status definitions conflict,
//! must be include befor other header files
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDebug>

#include "ukuiinputgatherclient.h"

#include <X11/extensions/XInput.h>
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <netinet/in.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
#include <thread>
#include <cstdio>


UKUIInputGatherClient::UKUIInputGatherClient()
    :m_event(nullptr), m_sendEventCallBack(nullptr)
{

    m_event = new Event;
    char *strEnv = getenv("XDG_SESSION_TYPE");
    if (!strncmp(strEnv,"wayland",sizeof("wayland")) == 0) {
        obtainDeviceList();
    }

}

UKUIInputGatherClient::~UKUIInputGatherClient()
{
    if(m_event)
        delete m_event;
}

void UKUIInputGatherClient::setEventCallBack(std::function<void (Event *)> sendEvent)
{
    if(!sendEvent){
        // TOOD log
        return;
    }
//    //手势标志位==false  禁止上传事件
//    if(!m_flagGesture)
//        return;
    m_sendEventCallBack = sendEvent;
}

void UKUIInputGatherClient::startToReceiveEvent()
{
    printf("UKUIInputGatherClient: 开始进行监听Socket!获取事件\n");
    char *strEnv = getenv("XDG_SESSION_TYPE");
    if (!strncmp(strEnv,"wayland",sizeof("wayland")) == 0) {
        checkTouchpadState();
    }
    std::thread receiveThread{[ this ]() {
        bool connected = false;
        while(true)
        {
            if(!connected){
                if(connectToServer()){
                    printf("UKUIInputGatherClient: 服务端链接成功!\n");
                    connected = true;
                }else{
                    connected = false;
                }
            }
            if (connected) {
                int total = read(m_socket, m_event, sizeof(Event));
                if (total <= 0) {
                    connected = false;
                } else {
                    if(m_sendEventCallBack){
                        if(m_flagGesture)
                            m_sendEventCallBack(m_event);
                    }
                }
            }
            if (!connected) {
                close(m_socket);
                int retryTime = 5;
                printf("UKUIInputGatherClient: Socket链接失败！%ds 后开始重新尝试链接！\n", retryTime);
                std::this_thread::sleep_for(std::chrono::seconds(retryTime));
            }
        }
    }};
    receiveThread.detach();
}

bool UKUIInputGatherClient::connectToServer()
{
    bool connected = false;

    m_socket = ::socket(AF_UNIX, SOCK_STREAM, 0);
    if (m_socket == -1) {
        return false;
    }

    struct sockaddr_un addr = {};
    addr.sun_family         = AF_UNIX;
    memset(&addr.sun_path[ 0 ], 0, sizeof(addr.sun_path));
    SOCKET_NAME.copy(&addr.sun_path[ 0 ], SOCKET_NAME.size(), 0);

    auto socketAddr = reinterpret_cast<sockaddr *>(&addr); // NOLINT
    if (connect(m_socket, socketAddr, sizeof(addr)) == -1) {
        connected = false;
    } else {
        connected = true;
    }

    return connected;
}
bool UKUIInputGatherClient::deviceHasProperty (Display *display,XDevice* device,char *property_name)
{
    Atom realtype, prop;
    int realformat;
    unsigned long nitems, bytes_after;
    unsigned char *data;

    prop = XInternAtom (display, property_name, True);
    if (!prop)
            return false;

    try {
        if ((XGetDeviceProperty (display, device, prop, 0, 1, False,
                                XA_INTEGER, &realtype, &realformat, &nitems,
                                &bytes_after, &data) == Success) && (realtype != None)) {
                XFree (data);
                return true;
        }

    } catch (int x) {

    }
    return false;
}
XDevice* UKUIInputGatherClient::deviceIsTouchpad (Display *display,XDeviceInfo *deviceinfo)
{
    XDevice *device;
    if(!display)
        return NULL;
    if (deviceinfo->type != XInternAtom (display, XI_TOUCHPAD, True))
            return NULL;

    try {
        device = XOpenDevice (display, deviceinfo->id);
        if (device == NULL)
            throw 1;

        if (deviceHasProperty (display,device, (char*)"libinput Tapping Enabled") ||
            deviceHasProperty (display,device, (char*)"Synaptics Off")) {
                return device;
        }
        XCloseDevice (display, device);

    } catch (int x) {
        return NULL;
    }
    return NULL;
}
Atom UKUIInputGatherClient::hasPropertyFromName (Display *display, const char *propertyName)
{
    return XInternAtom (display, propertyName, False);
}
void UKUIInputGatherClient::findTouchpadState(Display* display, XDeviceInfo *deviceInfo)
{
    XDevice * device = deviceIsTouchpad(display,deviceInfo);
    if(!device)
        return;
    int realformat;
    unsigned long nitems, bytes_after;
    unsigned char *data;
    Atom realtype, prop;
    prop = hasPropertyFromName(display, "Device Enabled");
    if(!prop)
        return;
//    printf("device info name %s\n",device_info->name);
    int iResult = XGetDeviceProperty (display, device, prop, 0, 1, False,XA_INTEGER, &realtype, &realformat, &nitems,
                                      &bytes_after, &data);
    if (iResult== Success) {
//        printf("date = %u\n",data[0]);
        if(data[0] == 1){
            m_flagGesture = true;
        }else{
            m_flagGesture = false;
        }
        XFree(data);
    }
    XCloseDevice (display, device);
}


void UKUIInputGatherClient::obtainDeviceList()
{
    int numdevices = 0;
    auto display = XOpenDisplay(nullptr);
    if (!display)
        return;

    XDeviceInfo *devicelist = XListInputDevices(display,&numdevices);
    if (devicelist == NULL){
        return;
    }
    for (int i = 0; i < numdevices; i++) {
        findTouchpadState(display, &devicelist[i]);
    }
    XFreeDeviceList (devicelist);
    XCloseDisplay(display);
}
void UKUIInputGatherClient::checkTouchpadState()
{

//    printf("checkTouchpadState------每隔一段事件对设备属性状态进行查询----\n");
    std::thread touchpadPropsThread([ this ]() {
        auto display = XOpenDisplay(nullptr);
        int numdevices = 0;
        XDevice *device;
        char *name;
        XEvent event;
        XDevicePropertyNotifyEvent *dpev;
        int type_prop;
        XEventClass cls_prop;

        int realformat;
        unsigned long nitems, bytes_after;
        unsigned char *data;
        Atom realtype;

        XDeviceInfo *deviceInfo = XListInputDevices(display,&numdevices);
        if (deviceInfo == NULL){
            return;
        }
        for (int i = 0; i < numdevices; i++) {
            device = deviceIsTouchpad(display,&deviceInfo[i]);
            if(!device){
                continue;
            }else{
                break;
            }
        }
        if(!device)
            return;
        DevicePropertyNotify(device,type_prop,cls_prop);
        XSelectExtensionEvent(display,DefaultRootWindow(display),&cls_prop,1);
        while(true)
        {
            XNextEvent(display,&event);
            dpev = (XDevicePropertyNotifyEvent*)&event;
            if(dpev->type != type_prop)
                continue;
            name = XGetAtomName(display,dpev->atom);
//            printf("Property '%s' changed:\n",name);
            if(strncmp(name,"Device Enabled",sizeof("Device Enabled")) == 0){
//                printf("touchpadState is changed!!!\n");
                printf("\t%s (%ld) : \t",name,dpev->atom);
                int iResult = XGetDeviceProperty (display, device, dpev->atom, 0, 1, False,XA_INTEGER, &realtype, &realformat, &nitems,
                                                  &bytes_after, &data);
                if (iResult== Success) {
//                    printf("date = %u\n",data[0]);
                    if(data[0] == 1){
                        m_flagGesture = true;
                    }else{
                        m_flagGesture = false;
                    }
                    XFree(data);
                }
            }
            XFree(name);
        }

        XCloseDevice(display, device);
    });
    touchpadPropsThread.detach();
}

int UKUIInputGatherClient::libinputTabletSwitchState()
{
    QDBusMessage msg = QDBusMessage::createMethodCall("org.ukui.libinput.proxy",
                                                      "/org/ukui/libinput/proxy",
                                                      "org.ukui.libinput.proxy",
                                                      "libinputTabletSwitchState");
    QDBusMessage re = QDBusConnection::systemBus().call(msg);
    if (re.type() == QDBusMessage::ErrorMessage) {
        qWarning() << "Call method libinputTabletSwitchState failed" << re.errorName() << re.errorMessage();
        return -1;
    }
    else if (re.type() == QDBusMessage::ReplyMessage) {
        return re.arguments().takeFirst().toInt();
    }
    return -1;
}
