/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*-
 * -*- coding: utf-8 -*-
 *
 * Copyright (C) 2022 KylinSoft Co., Ltd.
 *
 * 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/>.
 *
 * Authors: sundagao <sundagao@kylinos.cn>
 */

#include "input-device-factory.h"
#include "input-device-helper.h"
#include "input-x-device.h"
#include "input-wayland-device.h"
#include "usd_base_class.h"

using namespace InputDeviceHelper;

/*****X Factor*****/

InputXDeviceFactor::InputXDeviceFactor(QObject *parent)
{
    m_deviceManager = reinterpret_cast<InputDeviceManager*>(parent);
    m_monitorThread = new QThread(this);
    connectMonitor();
}

InputXDeviceFactor::~InputXDeviceFactor()
{
    disconnect(m_inputMonitor, &InputMonitor::deviceAdd, this, &InputXDeviceFactor::deviceAdd);
    disconnect(m_inputMonitor, &InputMonitor::deviceRemove, this, &InputXDeviceFactor::deviceRemove);
    m_inputMonitor->stopMontior();
}

void InputXDeviceFactor::initInputDevices()
{
    int ndevices;
    XDeviceInfo* deviceList = XListInputDevices(QX11Info::display(),&ndevices);
    for (int i = 0 ; i < ndevices ; ++i) {
        InputDevice* device = filterDevice(deviceList[i]);
        if (device) {
            m_deviceManager->deviceAdd(device);
        }
    }
    XFreeDeviceList(deviceList);
}

InputDevice *InputXDeviceFactor::createInputDevice(QVariant deviceId, DeviceType type, QString name)
{
    return new InputXDevice(deviceId, type, name);
}

InputDevice* InputXDeviceFactor::filterDevice(XDeviceInfo device)
{
    InputDevice *inputDevice = nullptr;
    if (device.type == properyToAtom(XI_MOUSE)){ //鼠标, 由于虚拟机内虚拟鼠标设备使用PS2协议，增加“VMware VMMouse”字段区分。
        if (strstr(device.name,R"(PS/2)") && !strstr(device.name, "VMware VMMouse")) {
            //ps2 触摸板上报为鼠标设备,所以标记为IN_TOUCHPAD
            inputDevice = createInputDevice(int(device.id), DeviceType::IN_TOUCHPAD, device.name);
        } else if (strstr(device.name, "TrackPoint")) {
            //lenovo track point设备

        } else {
            // mouse
            inputDevice = createInputDevice(int(device.id), DeviceType::IN_MOUSE, device.name);
        }
    } else if (device.type == properyToAtom(XI_TOUCHPAD)) {//触控板
        inputDevice = createInputDevice(int(device.id), DeviceType::IN_TOUCHPAD, device.name);
    } else if (device.type == properyToAtom(XI_TOUCHSCREEN) //触控屏
               || device.type == properyToAtom(XI_TABLET)) {//笔
        //绝对坐标映射设备进行映射
        QDBusInterface interface("org.ukui.SettingsDaemon",
                                 "/org/ukui/SettingsDaemon/xrandr",
                                 "org.ukui.SettingsDaemon.xrandr",
                                 QDBusConnection::sessionBus());
        if (interface.isValid()) {
            interface.call("setScreenMap");
        }
    }else if (device.type == properyToAtom(XI_JOYSTICK)) {//摇杆
        //to be continued
    }
    return inputDevice;
}

void InputXDeviceFactor::connectMonitor()
{
    //delete InputMonitor
    m_inputMonitor = InputMonitor::instance();
    m_inputMonitor->moveToThread(m_monitorThread);
    connect(m_monitorThread, &QThread::started, InputMonitor::instance(), &InputMonitor::startMonitor);
    m_monitorThread->start();
    connect(m_inputMonitor, &InputMonitor::deviceAdd, this, &InputXDeviceFactor::deviceAdd);
    connect(m_inputMonitor, &InputMonitor::deviceRemove, this, &InputXDeviceFactor::deviceRemove);
}

void InputXDeviceFactor::deviceAdd(int id)
{
    int ndevices;
    XDeviceInfo* deviceList = XListInputDevices(QX11Info::display(),&ndevices);
    for (int i = 0 ; i < ndevices ; ++i) {
        if (id == deviceList[i].id) {
            InputDevice* device = filterDevice(deviceList[i]);
            if (device) {
                m_deviceManager->deviceAdd(device);
            }
        }
    }
    XFreeDeviceList(deviceList);
}

void InputXDeviceFactor::deviceRemove(int id)
{
    m_deviceManager->deviceRemove(id);
}

/*********** InputWaylandDeviceFactor ***********/

#define UKUI_KWIN_OBJECT  "/org/ukui/KWin/InputDevice"
#define UKUI_KWIN_DEVICE_MANAGER "org.ukui.KWin.InputDeviceManager"

#define KDE_KWIN_OBJECT "/org/kde/KWin/InputDevice"
#define KDE_KWIN_DEVICE_MANAGER "org.kde.KWin.InputDeviceManager"

InputWaylandDeviceFactor::InputWaylandDeviceFactor(QObject *parent)
{
    m_deviceManager = reinterpret_cast<InputDeviceManager*>(parent);

    m_deviceInterface = new QDBusInterface(QStringLiteral(KDE_KWIN_SERVICE),
                                           QStringLiteral(KDE_KWIN_OBJECT),
                                           QStringLiteral(KDE_KWIN_DEVICE_MANAGER),
                                           QDBusConnection::sessionBus(), this);
    if (!m_deviceInterface->isValid()) {
        m_deviceInterface = new QDBusInterface(QStringLiteral(UKUI_KWIN_SERVICE),
                                               QStringLiteral(UKUI_KWIN_OBJECT),
                                               QStringLiteral(UKUI_KWIN_DEVICE_MANAGER),
                                               QDBusConnection::sessionBus(), this);
    }
    if (m_deviceInterface->isValid()) {
        connectMonitor();
    }
}

InputWaylandDeviceFactor::~InputWaylandDeviceFactor()
{
    disconnect(m_deviceInterface, SIGNAL(deviceAdded(QString)),this, SLOT(deviceAdd(QString)));
    disconnect(m_deviceInterface, SIGNAL(deviceRemoved(QString)),this, SLOT(deviceRemove(QString)));
}

void InputWaylandDeviceFactor::initInputDevices()
{
    if (m_deviceInterface->isValid()) {
        QStringList deviceList = m_deviceInterface->property("devicesSysNames").toStringList();
        for (QString device : deviceList) {
            managerAddDevice(device);
        }
    }
}

void InputWaylandDeviceFactor::connectMonitor()
{
    connect(m_deviceInterface, SIGNAL(deviceAdded(QString)),this, SLOT(deviceAdd(QString)));
    connect(m_deviceInterface, SIGNAL(deviceRemoved(QString)),this, SLOT(deviceRemove(QString)));
}

InputDevice *InputWaylandDeviceFactor::createInputDevice(QVariant deviceId, DeviceType type, QString name)
{
    return new InputWaylandDevice(deviceId, type, name);
}

InputDevice *InputWaylandDeviceFactor::filterDevice(QDBusInterface * interface)
{
    InputDevice *inputDevice = nullptr;

    if (interface->property("pointer").toBool() && !interface->property("keyboard").toBool()) {
        if (interface->property("touchpad").toBool()) {
            QString node = interface->property("sysName").toString();
            QString name = interface->property("name").toString();
            inputDevice = createInputDevice(node, DeviceType::IN_TOUCHPAD, name);
        } else if (interface->property("touch").toBool()) {
            //touch
        } else {
            QString node = interface->property("sysName").toString();
            QString name = interface->property("name").toString();
            inputDevice = createInputDevice(node, DeviceType::IN_MOUSE, name);
        }
    } else if (interface->property("keyboard").toBool()) {

    }
    return inputDevice;
}

void InputWaylandDeviceFactor::managerAddDevice(QString device)
{
    QDBusInterface *interface = new QDBusInterface(KDE_KWIN_SERVICE,
                                     KDE_KWIN_OBJECT_PATH + device,
                                     KDE_KWIN_INTERFACE,
                                     QDBusConnection::sessionBus());
    if (!interface->isValid()) {
        interface = new QDBusInterface(UKUI_KWIN_SERVICE,
                                       UKUI_KWIN_OBJECT_PATH + device,
                                       UKUI_KWIN_INTERFACE,
                                       QDBusConnection::sessionBus());
    }

    if (interface->isValid()) {
        InputDevice* inputDevice = filterDevice(interface);
        if (inputDevice) {
            m_deviceManager->deviceAdd(inputDevice);
        }
        delete interface;
        interface = nullptr;
    }
}

void InputWaylandDeviceFactor::deviceAdd(QString device)
{
    managerAddDevice(device);
}

void InputWaylandDeviceFactor::deviceRemove(QString device)
{
    m_deviceManager->deviceRemove(device);
}

/*InputDeviceFactorManager*/

InputDeviceFactor *InputDeviceFactorManager::createDeviceFactor(InputDeviceManager* manager)
{
    InputDeviceFactor* deviceFactor = nullptr;
    if (UsdBaseClass::isWayland()) {
        deviceFactor = new InputWaylandDeviceFactor(manager);
    } else {
        if (!supportXinputExtension()) {
            USD_LOG(LOG_WARNING,"X Input extension not available");
            return nullptr;
        }
        deviceFactor = new InputXDeviceFactor(manager);
    }
    return deviceFactor;
}
