/*
 * Copyright (c) KylinSoft Co., Ltd. 2025.All rights reserved.
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 */
#include "keyboardstatusmanager.h"

#include <QCoreApplication>
#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QDebug>
#include <QFile>

#define PROCBUSINPUTDEVICES "/proc/bus/input/devices"
#define DEVICEINFO_EV_KBD "EV=120013"

const QString m_serviceName = "org.fcitx.Fcitx5.KeyboardStatus";

namespace fcitx {

KeyboardStatusMonitor::KeyboardStatusMonitor(QObject *parent)
    : QObject(parent) {
    initSignalTimer();
    updateProcInputDevices();
    setupUdevMonitoring();
}

KeyboardStatusMonitor::~KeyboardStatusMonitor() {
    if (m_monitor != nullptr) {
        udev_monitor_unref(m_monitor);
        m_monitor = nullptr;
    }

    if (m_udev != nullptr) {
        udev_unref(m_udev);
        m_udev = nullptr;
    }
}

void KeyboardStatusMonitor::updateProcInputDevices() {
    std::cout << "func : " << __FUNCTION__ << std::endl;

    const auto content = readFile(PROCBUSINPUTDEVICES);
    if (content.isEmpty()) {
        return;
    }

    m_deviceInfoMap.clear();
    const auto deviceBlocks = splitIntoDeviceBlocks(content);
    parseAllDeviceBlocks(deviceBlocks);
}

void KeyboardStatusMonitor::initSignalTimer() {
    std::cout << "func : " << __FUNCTION__ << std::endl;
    m_signalMergingTimer = new QTimer(this);
    m_signalMergingTimer->setSingleShot(true);
    connect(m_signalMergingTimer, &QTimer::timeout, this,
            &KeyboardStatusMonitor::onFileChanged);
}

void KeyboardStatusMonitor::onFileChanged() {
    updateProcInputDevices();
    Q_EMIT keyboardStatusChanged();
}

void KeyboardStatusMonitor::addDeviceToMap(const QString &id,
                                           const DeviceInfo &info) {
    if (id.isEmpty()) {
        return;
    }

    if (m_deviceInfoMap.keys().contains(id)) {
        m_deviceInfoMap[id].append(info);
    } else {
        QList<DeviceInfo> devInfoList = {info};
        m_deviceInfoMap.insert(id, devInfoList);
    }

    std::cout << "add device to map, map size is :" << m_deviceInfoMap.size()
              << std::endl;
}

void KeyboardStatusMonitor::setupUdevMonitoring() {
    std::cout << "func : " << __FUNCTION__ << std::endl;
    m_udev = udev_new();
    if (m_udev == nullptr) {
        std::cerr << "func : " << __FUNCTION__ << "Failed to new udev"
                  << std::endl;
        return;
    }

    m_monitor = udev_monitor_new_from_netlink(m_udev, "udev");
    if (m_monitor == nullptr) {
        std::cerr << "func : " << __FUNCTION__ << "Failed to new udev monitor"
                  << std::endl;
        return;
    }

    udev_monitor_filter_add_match_subsystem_devtype(m_monitor, "input",
                                                    nullptr);
    udev_monitor_enable_receiving(m_monitor);

    setupSocketNotifier(udev_monitor_get_fd(m_monitor));
}

void KeyboardStatusMonitor::setupSocketNotifier(int fd) {
    std::cout << "func : " << __FUNCTION__ << " fd : " << fd << std::endl;
    auto notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this);
    connect(notifier, &QSocketNotifier::activated, this, [this] {
        udev_device *dev = udev_monitor_receive_device(m_monitor);
        if (dev != nullptr) {
            m_signalMergingTimer->start(1000);
            udev_device_unref(dev);
        }
    });
}

const QString KeyboardStatusMonitor::readFile(const QString &path) const {
    std::cout << "func : " << __FUNCTION__ << std::endl;
    QFile file(path);
    if (!file.open(QIODevice::ReadOnly)) {
        std::cerr << "func : " << __FUNCTION__ << "Failed to open file"
                  << std::endl;
        return "";
    }
    return QString::fromLocal8Bit(file.readAll());
}

QList<QStringList>
KeyboardStatusMonitor::splitIntoDeviceBlocks(const QString &content) const {
    std::cout << "func : " << __FUNCTION__ << std::endl;

    QList<QStringList> blocks;
    QStringList currentBlock;

    for (const auto &line : content.split('\n')) {
        if (line.trimmed().isEmpty()) {
            if (!currentBlock.isEmpty()) {
                blocks.append(currentBlock);
                currentBlock.clear();
            }
        } else {
            currentBlock.append(line);
        }
    }
    if (!currentBlock.isEmpty()) {
        blocks.append(currentBlock);
    }

    return blocks;
}

void KeyboardStatusMonitor::parseAllDeviceBlocks(
    const QList<QStringList> &blocks) {
    std::cout << "func : " << __FUNCTION__;

    for (const auto &block : blocks) {
        auto [id, info] = parseSingleDeviceBlock(block);
        std::cout << "id : " << id.toStdString() << std::endl;
        std::cout << " device info name : " << info.name.toStdString()
                  << std::endl;
        std::cout << " device info eventcap : "
                  << info.eventCapabilities.toStdString() << std::endl;
        addDeviceToMap(id, info);
    }
}

std::pair<QString, KeyboardStatusMonitor::DeviceInfo>
KeyboardStatusMonitor::parseSingleDeviceBlock(const QStringList &block) const {
    QString id;
    DeviceInfo info;

    for (const auto &line : block) {
        parseDeviceLine(line, id, info);
    }

    return {id, info};
}

void KeyboardStatusMonitor::parseDeviceLine(const QString &line, QString &outId,
                                            DeviceInfo &outInfo) const {
    // "X: "格式前缀长度
    constexpr auto PREFIX_LEN = 3;

    if (line.startsWith("I: ")) {
        outId = line.mid(PREFIX_LEN).trimmed();
    } else if (line.startsWith("N: ")) {
        outInfo.name = line.mid(PREFIX_LEN).trimmed();
    } else if (line.startsWith("P: ")) {
        outInfo.physPath = line.mid(PREFIX_LEN).trimmed();
    } else if (line.startsWith("S: ")) {
        outInfo.sysfsPath = line.mid(PREFIX_LEN).trimmed();
    } else if (line.startsWith("B: ") && line.contains("EV=")) {
        outInfo.eventCapabilities = line.mid(PREFIX_LEN).trimmed();
    }
}

int KeyboardStatusMonitor::getKeyboardNum() {
    int keyboardNum = 0;

    if (m_deviceInfoMap.isEmpty()) {
        std::cerr << "m_deviceInfoMap is empty.";
        return keyboardNum;
    }

    for (const auto &list : m_deviceInfoMap) {
        if (list.first().eventCapabilities.contains(DEVICEINFO_EV_KBD)) {
            keyboardNum++;
        }
    }

    std::cout << "func: " << __FUNCTION__ << " keyboardNum: " << keyboardNum
              << std::endl;
    return keyboardNum;
}

KeyboardStatusManager::KeyboardStatusManager(QObject *parent)
    : QObject(parent),
      m_keyboardStatusMonitor(new KeyboardStatusMonitor(parent)),
      m_keyboardStatusDBusService(
          new KeyboardStatusDBusService(parent, m_keyboardStatusMonitor)) {

    registerDBus();

    QObject::connect(
        m_keyboardStatusMonitor, &KeyboardStatusMonitor::keyboardStatusChanged,
        this, [this]() {
            if (m_keyboardStatusDBusService == nullptr) {
                return;
            }

            emit m_keyboardStatusDBusService->keyboardStatusChanged();
        });
}

KeyboardStatusManager::~KeyboardStatusManager() {
    if (m_keyboardStatusDBusService) {
        delete m_keyboardStatusDBusService;
        m_keyboardStatusDBusService = nullptr;
    }

    if (m_keyboardStatusMonitor) {
        delete m_keyboardStatusMonitor;
        m_keyboardStatusMonitor = nullptr;
    }
}

void KeyboardStatusManager::registerDBus() {

    QDBusConnection systemBus = QDBusConnection::systemBus();
    if (!systemBus.isConnected()) {
        std::cerr << "Failed to connect to system dbus. error message:"
                  << systemBus.lastError().message().toStdString();
        return;
    }

    // 查看dbus服务是否已经注册
    if (systemBus.interface()->isServiceRegistered(m_serviceName)) {
        std::cerr << "This service has already been registered. error message:"
                  << systemBus.lastError().message().toStdString();
        return;
    }

    // 注册服务名称
    if (!systemBus.registerService(m_serviceName)) {
        std::cerr << "Service registration failed. error message:"
                  << systemBus.lastError().message().toStdString();
        return;
    }

    // 注册对象路径
    if (!systemBus.registerObject("/", m_keyboardStatusDBusService,
                                  QDBusConnection::ExportAllSlots |
                                      QDBusConnection::ExportAllSignals)) {
        systemBus.unregisterService(m_serviceName);
        std::cerr << "Failed to register dbus service path. error message:"
                  << systemBus.lastError().message().toStdString();
        return;
    }

    std::cout << "register service successful." << std::endl;
}
} // namespace fcitx

int main(int argc, char *argv[]) {
    QCoreApplication app(argc, argv);

    fcitx::KeyboardStatusManager keyboardManager;

    return app.exec();
}
