/*
 * 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 Library General Public License as
 * published by the Free Software Foundation; either version 2, or
 * (at your option) 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 Library General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include "windowmanagerinterface.h"

#include <algorithm>

#include <QGSettings>
#include <QSettings>
#include <QGuiApplication>
#include <QScreen>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusPendingCallWatcher>
#include <QDBusPendingReply>
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDir>
#include <mutex>

#include "ukui-window-switch-wlcom/multitaskviewmodelbywlcomhandler.h"

static constexpr int MAX_DESKTOP_COUNT = 15;
static const QString UKUI_STYLE_SCHEMA = "org.ukui.style";
static const QString UKUI_STYLE_SCHEMA_STYLENAME = "styleName";
static const QString UKUI_STYLE_SCHEMA_FONTSIZE = "systemFontSize";
static const QString UKUI_STYLE_SCHEMA_FONT = "systemFont";
static const QString UKUI_STYLE_SCHEMA_WIDGETNAME = "widgetThemeName";
const QString IGNORE_WINDOW_CONFIG = "/usr/share/ukui/ukui-panel/panel-commission.ini";
static const QString DESKTOP_NAME = "desktop-name";
const QString DESKTOP_CONFIG_DIR = QDir::homePath() + QStringLiteral("/.config/org.ukui/ukui-window-switch/");
const QString DESKTOP_CONFIG = DESKTOP_CONFIG_DIR + QStringLiteral("multitaskview.conf");
const QString UKUI_FRAMEWORK_SERVICE = "org.ukui.Framework";
const QString UKUI_FRAMEWORK_MOUSE_PATH = "/org/ukui/Framework/Devices/Mouse";
const QString UKUI_FRAMEWORK_MOUSE_INTERFACE = "org.ukui.Framework.Devices.Mouse";
const QString UKUI_FRAMEWORK_TOUCHPAD_PATH = "/org/ukui/Framework/Devices/Touchpad";
const QString UKUI_FRAMEWORK_TOUCHPAD_INTERFACE = "org.ukui.Framework.Devices.Touchpad";

WindowManagerInterface *WindowManagerInterface::getInstance()
{
    static WindowManagerInterface s_interface;
    return &s_interface;
}

WindowManagerInterface::WindowManagerInterface(QObject *parent) : QObject(parent)
{
    m_modelhandler = new MultitaskViewModelByWlcomHandler(this);

    m_screenManager = new ScreensManager(this);

    initTouchState();
    connectSignals();
    if (QGSettings::isSchemaInstalled(UKUI_STYLE_SCHEMA.toUtf8())) {
        m_settings = new QGSettings(UKUI_STYLE_SCHEMA.toUtf8());
    }

    updateScreenRectList();
    QList<Screen *> screenList = m_screenManager->screens();
    for (const auto &screen : screenList) {
        connect(screen, &Screen::geometryChanged, this, &WindowManagerInterface::onScreenSizeChanged);
    }
}

WindowManagerInterface::~WindowManagerInterface()
{
    if (m_modelhandler != nullptr) {
        delete m_modelhandler;
        m_modelhandler = nullptr;
    }
}

bool WindowManagerInterface::currentDesktopHasWindow() const
{
    for (int screenIndex = 0; screenIndex < logicalScreenCount(); screenIndex++) {
        if (m_modelhandler->createWindowList(getCurrentDesktop(), screenIndex).size() > 0) {
            return true;
        }
    }
    return false;
}

void WindowManagerInterface::setDesktopName(const int desktopIndex, const QString name)
{
    if (!validateDesktopIndex(desktopIndex)) {
        return;
    }
    m_modelhandler->setDesktopName(desktopIndex, name);
}

QString WindowManagerInterface::getDesktopUuid(int desktopIndex)
{
    if (!validateDesktopIndex(desktopIndex)) {
        return QString();
    }
    return m_modelhandler->getDesktopUuid(desktopIndex);
}

QString WindowManagerInterface::getOutputUuid(int outputIndex)
{
    return m_modelhandler->getOutputUuid(outputIndex);
}


void WindowManagerInterface::removeAppWindow(QString windowId)
{
    m_modelhandler->removeAppWindow(windowId);
}

void WindowManagerInterface::removeAllAppWindowOnDesktop(int desktopIndex, int screenIndex)
{
    QList<Window> windowList;
    windowList = m_modelhandler->createWindowList(desktopIndex, screenIndex);
    for (Window &window : windowList) {
        m_modelhandler->removeAppWindow(window.uuid());
    }
}

int WindowManagerInterface::getNumberOfDesktops() const
{
    return m_modelhandler->getNumberOfDesktops();
}

void WindowManagerInterface::moveAppWindowToDesktop(QString windowId, int srcDesktopInex, int destDesktopInex) const
{
    m_modelhandler->moveAppWindowToDesktop(windowId, srcDesktopInex, destDesktopInex);
}

void WindowManagerInterface::activateAppWindow(QString windowId)
{
    m_modelhandler->activateAppWindow(windowId);
}

QVariant WindowManagerInterface::getWindowIcon(QString windowId)
{
    return m_modelhandler->getWindowIcon(windowId);
}

void WindowManagerInterface::removeDesktop(int desktopIndex)
{
    if (m_modelhandler->getNumberOfDesktops() <= 1) {
        return;
    }
    if (!validateDesktopIndex(desktopIndex)) {
        return;
    }
    m_modelhandler->removeDesktop(desktopIndex);
}

void WindowManagerInterface::appendDesktop()
{
    if (m_modelhandler->getNumberOfDesktops() >= MAX_DESKTOP_COUNT) {
        return;
    }
    m_modelhandler->appendDesktop();
}

void WindowManagerInterface::moveDesktop(int srcDesktopIndex, int destDesktopIndex)
{
    if (srcDesktopIndex == destDesktopIndex) {
        return;
    }
    m_modelhandler->moveDesktop(srcDesktopIndex, destDesktopIndex);
}

QList<Window> WindowManagerInterface::createWindowList(int desktopIndex, int screenIndex)
{
    return m_modelhandler->createWindowList(desktopIndex, screenIndex);
}

int WindowManagerInterface::getCurrentDesktop() const
{
    return m_modelhandler->getCurrentDesktop();
}

bool WindowManagerInterface::isCurrentDesktopShowingDesktop() const
{
    return m_modelhandler->isCurrentDesktopShowingDesktop();
}

void WindowManagerInterface::setCurrentDesktop(int desktopIndex)
{
    if (!validateDesktopIndex(desktopIndex)) {
        return;
    }
    m_modelhandler->setCurrentDesktop(desktopIndex);
}

QRect WindowManagerInterface::getFullScreenGeometry(int desktopIndex, int screenIndex) const
{
    return m_modelhandler->getFullScreenGeometry(desktopIndex, screenIndex);
}

bool WindowManagerInterface::hasModalChildWindow(QString windowId)
{
    return m_modelhandler->hasModalChildWindow(windowId);
}


QString WindowManagerInterface::getUKUIStyleName() const
{
    // default to ukui-default
    QString styleName = "ukui-default";
    if (m_settings && m_settings->keys().contains(UKUI_STYLE_SCHEMA_STYLENAME)) {
        styleName = m_settings->get(UKUI_STYLE_SCHEMA_STYLENAME).toString();
    } else {
        qWarning() << QString("未查询到Gesetting的键值(%1)").arg(UKUI_STYLE_SCHEMA_STYLENAME);
    }
    return styleName;
}

double WindowManagerInterface::getUKUIFontSize() const
{
    // default to 11
    double fontSize = 11;
    if (m_settings && m_settings->keys().contains(UKUI_STYLE_SCHEMA_FONTSIZE)) {
        fontSize = m_settings->get(UKUI_STYLE_SCHEMA_FONTSIZE).toDouble();
    } else {
        qWarning() << QString("未查询到Gesetting的键值(%1)").arg(UKUI_STYLE_SCHEMA_FONTSIZE);
    }
    return fontSize;
}

QString WindowManagerInterface::getUKUIFont() const
{
    // default to 11
    QString font = "Noto Sans CJK SC";
    if (m_settings && m_settings->keys().contains(UKUI_STYLE_SCHEMA_FONT)) {
        font = m_settings->get(UKUI_STYLE_SCHEMA_FONT).toString();
    } else {
        qWarning() << QString("未查询到Gesetting的键值(%1)").arg(UKUI_STYLE_SCHEMA_FONT);
    }
    return font;
}

void WindowManagerInterface::setIsOpenGLBackend(bool isOpenGLBackend)
{
    m_isOpenGLBackend = isOpenGLBackend;
}

int WindowManagerInterface::getRadius(QString type) const
{
    return m_isOpenGLBackend ? getUKUIRadius(type) : 0;
}

int WindowManagerInterface::getUKUIRadius(QString type) const
{
    // default to 8
    QString widgetName = "default";
    if (m_settings && m_settings->keys().contains(UKUI_STYLE_SCHEMA_WIDGETNAME)) {
        widgetName = m_settings->get(UKUI_STYLE_SCHEMA_WIDGETNAME).toString();
    } else {
        qWarning() << QString("未查询到Gesetting的键值(%1)").arg(UKUI_STYLE_SCHEMA_WIDGETNAME);
        return 8;
    }

    QFile file(QString("/usr/share/qt5-ukui-platformtheme/themeconfig/%1.json").arg(widgetName));
    if (!file.open(QIODevice::ReadOnly)) {
        qWarning() << "无法打开JSON文件";
        return 8;
    }

    QByteArray jsonData = file.readAll();
    file.close();

    // 将JSON数据转换为QJsonDocument对象
    QJsonParseError error;
    QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error);
    if (error.error != QJsonParseError::NoError) {
        qWarning() << "JSON格式错误:" << error.errorString();
        return 8;
    }

    // 获取属性
    QJsonObject rootObj = jsonDoc.object();
    QJsonObject radiusObj = rootObj["Radius"].toObject();
    QJsonObject radiusValueObj = radiusObj[type].toObject();

    return radiusValueObj["value"].toString().toInt();
}

bool WindowManagerInterface::isExtensionMode()
{
    return m_modelhandler->isExtensionMode();
}

int WindowManagerInterface::logicalScreenCount() const
{
    return m_modelhandler->logicalScreenCount();
}

bool WindowManagerInterface::isTabletMode() const
{
    // default to PC mode.
    bool tabletMode = false;
    QDBusMessage message = QDBusMessage::createMethodCall("com.kylin.statusmanager.interface",
                                                          "/",
                                                          "com.kylin.statusmanager.interface",
                                                          "get_current_tabletmode");
    QDBusMessage response = QDBusConnection::sessionBus().call(message);
    if (response.type() == QDBusMessage::ReplyMessage) {
        tabletMode = response.arguments().takeFirst().toBool();
    } else {
        qDebug() << "get tablet mode failed " << response.type();
    }
    return tabletMode;
}

bool WindowManagerInterface::isTouchState() const
{
    return m_isTouchState;
}

QStringList WindowManagerInterface::getDesktopNames() const
{
    QSettings desktopSettings(DESKTOP_CONFIG, QSettings::IniFormat);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    desktopSettings.setIniCodec("utf-8");
#endif
    QStringList nameList = desktopSettings.value("desktops/name").toStringList();
    return nameList;
}

void WindowManagerInterface::saveDesktopNames(const QStringList &nameList)
{
    QDir configDir;
    if (!configDir.exists(DESKTOP_CONFIG_DIR)) {
        configDir.mkdir(DESKTOP_CONFIG_DIR);
    }

    QSettings desktopSettings(DESKTOP_CONFIG, QSettings::IniFormat);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    desktopSettings.setIniCodec("utf-8");
#endif
    desktopSettings.beginGroup("desktops");
    desktopSettings.setValue("name", QVariant(nameList));
    desktopSettings.endGroup();
    //qDebug() << "[MultitaskViewModel] save custom desktop names" << desktopSettings.value("desktops/name").toStringList();
}

QList<QRect> WindowManagerInterface::screenRectList()
{
    return m_screenRectList;
}

void WindowManagerInterface::updateScreenRectList()
{
    m_screenRectList = QList<QRect>();
    QList<Screen *> screenList = m_screenManager->screens();
    for (int index = 0; index < screenList.size(); ++index) {
        m_screenRectList.append(screenList[index]->geometry());
    }
    emit screenRectListChanged();
}

QString WindowManagerInterface::getCurrentLanguage() const
{
    // default to "Chinese".
    QLocale locale = QLocale::system();
    QString language = "Chinese";
    if (locale.language() == QLocale::Chinese) {
        language = "Chinese";
    } else if (locale.language() == QLocale::Tibetan) {
        language = "Tibetan";
    }
    return language;
}

bool WindowManagerInterface::validateDesktopIndex(int desktopIndex) const
{
    return desktopIndex >= 1 && desktopIndex <= m_modelhandler->getNumberOfDesktops();
}

bool WindowManagerInterface::validateScreenIndex(int screenIndex) const
{
    return screenIndex >= 0 || screenIndex < logicalScreenCount();
}

void WindowManagerInterface::connectSignals()
{
    QObject::connect(m_modelhandler, &MultitaskViewAbstractModelHandler::currentDesktopChanged,
                     this, &WindowManagerInterface::currentDesktopChanged, Qt::QueuedConnection);
    QObject::connect(m_modelhandler, &MultitaskViewAbstractModelHandler::desktopNumberChanged,
                     this, &WindowManagerInterface::desktopNumberChanged, Qt::QueuedConnection);
    QObject::connect(m_modelhandler, &MultitaskViewAbstractModelHandler::desktopRemoved,
                     this, &WindowManagerInterface::desktopRemoved, Qt::QueuedConnection);
    QObject::connect(m_modelhandler, &MultitaskViewAbstractModelHandler::desktopAppended,
                     this, &WindowManagerInterface::desktopAppended, Qt::QueuedConnection);
    QObject::connect(m_modelhandler, &MultitaskViewAbstractModelHandler::desktopMoved,
                     this, &WindowManagerInterface::desktopMoved, Qt::QueuedConnection);

    QObject::connect(m_modelhandler, &MultitaskViewAbstractModelHandler::appWindowCreated,
                     this, &WindowManagerInterface::appWindowCreated, Qt::QueuedConnection);
    QObject::connect(m_modelhandler, &MultitaskViewAbstractModelHandler::appWindowAdd,
                     this, &WindowManagerInterface::appWindowAdd, Qt::QueuedConnection);
    QObject::connect(m_modelhandler, &MultitaskViewAbstractModelHandler::appWindowRemoved,
                     this, &WindowManagerInterface::appWindowRemoved, Qt::QueuedConnection);
    QObject::connect(m_modelhandler, &MultitaskViewAbstractModelHandler::appWindowActivated,
                     this, &WindowManagerInterface::appWindowActivated, Qt::QueuedConnection);
    QObject::connect(m_modelhandler, &MultitaskViewAbstractModelHandler::appWindowUpdated,
                     this, &WindowManagerInterface::appWindowUpdated, Qt::QueuedConnection);
    QObject::connect(m_modelhandler, &MultitaskViewAbstractModelHandler::appWindowDesktopChanged,
                     this, &WindowManagerInterface::appWindowDesktopChanged, Qt::QueuedConnection);

    QObject::connect(m_modelhandler, &MultitaskViewAbstractModelHandler::screenSizeChanged,
                     this, &WindowManagerInterface::onScreenSizeChanged, Qt::QueuedConnection);
    connect(m_screenManager, &ScreensManager::screenAdded, this, &WindowManagerInterface::onScreenAdd);
    connect(m_screenManager, &ScreensManager::screenRemoved, this, &WindowManagerInterface::onScreenRemove);

    QDBusConnection::sessionBus().connect(UKUI_FRAMEWORK_SERVICE,
                                          UKUI_FRAMEWORK_MOUSE_PATH,
                                          UKUI_FRAMEWORK_MOUSE_INTERFACE,
                                          "MouseCountChanged",
                                          this,
                                          SLOT(onMouseCountChanged(const int)));
    QDBusConnection::sessionBus().connect(UKUI_FRAMEWORK_SERVICE,
                                          UKUI_FRAMEWORK_TOUCHPAD_PATH,
                                          UKUI_FRAMEWORK_TOUCHPAD_INTERFACE,
                                          "TouchpadChanged",
                                          this,
                                          SLOT(onTouchpadCountChanged(const int)));
    QDBusConnection::sessionBus().connect("com.kylin.statusmanager.interface",
                                          "/",
                                          "com.kylin.statusmanager.interface",
                                          "mode_change_signal",
                                          this,
                                          SIGNAL(tabletModeChanged(bool)));
}

void WindowManagerInterface::onMouseCountChanged(const int count)
{
    m_flagNeedGetMouse = false;
    if (count == -1) {
        // 接口返回 -1 代表获取设备数量失败
        qDebug() << UKUI_FRAMEWORK_SERVICE << "mouse count reply -1, keep default";
        return;
    }
    m_mouseAvailable = count > 0 ? true : false;
    updateTouchState();
}

void WindowManagerInterface::onTouchpadCountChanged(const int count)
{
    m_flagNeedGetTouchpad = false;
    if (count == -1) {
        // 接口返回 -1 代表获取设备数量失败
        qDebug() << UKUI_FRAMEWORK_SERVICE << "touchpad count reply -1, keep default";
        return;
    }
    m_touchpadAvailable = count > 0 ? true : false;
    updateTouchState();
}

void WindowManagerInterface::initTouchState()
{
    asyncGetGenericPointerCount(UKUI_FRAMEWORK_SERVICE,
                                UKUI_FRAMEWORK_MOUSE_PATH,
                                UKUI_FRAMEWORK_MOUSE_INTERFACE,
                                "GetMouseCount");
    asyncGetGenericPointerCount(UKUI_FRAMEWORK_SERVICE,
                                UKUI_FRAMEWORK_TOUCHPAD_PATH,
                                UKUI_FRAMEWORK_TOUCHPAD_INTERFACE,
                                "GetTouchpadCount");
}

void WindowManagerInterface::updateTouchState()
{
    bool currentState = !(m_mouseAvailable || m_touchpadAvailable);
    if (m_isTouchState == currentState) {
        return;
    }
    m_isTouchState = currentState;
    emit touchStateChanged(m_isTouchState);
}

void WindowManagerInterface::asyncGetGenericPointerCount(const QString &destination, const QString &path,
                                                         const QString &interface, const QString &method)
{
    QDBusMessage message = QDBusMessage::createMethodCall(destination, path, interface, method);
    QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
    QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall);
    QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [method, this](QDBusPendingCallWatcher *w) {
        QDBusPendingReply<int> reply = *w;
        w->deleteLater();
        if (!reply.isValid()) {
            qDebug() << "Error:" << reply.error().message();
            return;
        }

        if (method.contains("Mouse") && this->m_flagNeedGetMouse) {
            this->onMouseCountChanged(reply.value());
        } else if (method.contains("Touchpad") && this->m_flagNeedGetTouchpad) {
            this->onTouchpadCountChanged(reply.value());
        }
    });
}

void WindowManagerInterface::onScreenSizeChanged()
{
    emit screenSizeChanged();
    updateScreenRectList();
}

// 屏幕增加时，先更新屏幕列表，再发送信号screenCountChanged；屏幕移除时，反之
// 否则qml读取screenRect时会报错，如：MultitaskView.qml:84: TypeError: Cannot read property 'height' of undefined
void WindowManagerInterface::onScreenAdd(Screen *screen)
{
    if (screen != nullptr) {
        connect(screen, &Screen::geometryChanged, this, &WindowManagerInterface::onScreenSizeChanged);
    }
    updateScreenRectList();
    emit screenCountChanged();
}

void WindowManagerInterface::onScreenRemove()
{
    emit screenCountChanged();
    updateScreenRectList();
}
