/*
 * Copyright (C) 2024, 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 "multitaskviewmodelbycompositehandler.h"
#include <algorithm>

#include <QtDBus>
#include <QGSettings>
#include <QGuiApplication>
#include <QScreen>
#include <QtX11Extras/QX11Info>
#include <QTimer>

MultitaskViewModelByCompositeHandler::MultitaskViewModelByCompositeHandler(QObject *parent) : MultitaskViewAbstractModelHandler{}
{
    connectSignals();
}

void MultitaskViewModelByCompositeHandler::removeAppWindow(QString windowId)
{
    auto* window = KWin::effects->findWindow(windowId);
    if (window == nullptr) {
        return;
    }

    window->closeWindow();
}

void MultitaskViewModelByCompositeHandler::moveAppWindowToDesktop(QString windowId,
                                                int srcDesktopInex,
                                                int destDesktopInex)
{

    auto* window = KWin::effects->findWindow(windowId);
    if (window == nullptr || window->isOnAllDesktops()) {
        return;
    }

    auto desktopIndexList = window->desktops();
    if (!desktopIndexList.contains(srcDesktopInex)) {
        return;
    }

    desktopIndexList.remove(desktopIndexList.indexOf(srcDesktopInex));

    if (desktopIndexList.contains(destDesktopInex)) {
        return;
    }

    desktopIndexList.append(destDesktopInex);

    KWin::effects->windowToDesktops(window, desktopIndexList);
}

void MultitaskViewModelByCompositeHandler::activateAppWindow(QString windowId)
{
    auto* window = KWin::effects->findWindow(windowId);
    if (window == nullptr) {
        return;
    }
    KWin::effects->activateWindow(window);
}

QVariant MultitaskViewModelByCompositeHandler::getWindowIcon(QString windowId)
{
    auto* window = KWin::effects->findWindow(windowId);
    if (window == nullptr) {
        return QIcon("");
    }
    return window->icon();
}

int MultitaskViewModelByCompositeHandler::getNumberOfDesktops()
{
   auto desktopCount = KWin::effects->numberOfDesktops();
   return desktopCount;
}


void MultitaskViewModelByCompositeHandler::removeDesktop(int desktopIndex)
{
    KWin::desktopManager->removeDesktop(desktopIndex);
    emit desktopRemoved(desktopIndex);
}

void MultitaskViewModelByCompositeHandler::moveDesktop(int srcDesktopIndex, int destDesktopIndex)
{
     KWin::desktopManager->moveDesktop(srcDesktopIndex, destDesktopIndex);
     emit desktopMoved(srcDesktopIndex, destDesktopIndex);
     setCurrentDesktop(destDesktopIndex);
}

void MultitaskViewModelByCompositeHandler::appendDesktop()
{
    auto desktopCount = KWin::effects->numberOfDesktops();
    KWin::effects->setNumberOfDesktops(desktopCount + 1);
    emit desktopAppended(desktopCount + 1);
}

int MultitaskViewModelByCompositeHandler::getCurrentDesktop()
{
    return KWin::effects->currentDesktop();
}

bool MultitaskViewModelByCompositeHandler::isCurrentDesktopShowingDesktop()
{
    return KWin::desktopManager->showingDesktop();
}

void MultitaskViewModelByCompositeHandler::setCurrentDesktop(int desktopIndex)
{
    //在990环境下将wayland窗口拖入新工作区后，立刻调用接口切换至对应工作区会导致kwin崩溃
    //setCurrentDesktop与windowToDesktops不在一个事件循环内执行则无此问题
    QTimer::singleShot(0, this, [desktopIndex]() {
        KWin::effects->setCurrentDesktop(desktopIndex);
    });
}

QRect MultitaskViewModelByCompositeHandler::getFullScreenGeometry(int desktopIndex, int screenIndex)
{
    KWin::EffectScreen* screen =KWin::effects->findScreen(screenIndex);
    return KWin::effects->clientArea(KWin::clientAreaOption::FullScreenArea,
                                     screen, desktopIndex);
}

bool MultitaskViewModelByCompositeHandler::hasModalChildWindow(QString windowId)
{
    auto* window = KWin::effects->findWindow(windowId);
    if (window == nullptr) {
        return false;
    }

    return window->findModal() != nullptr;
}

bool MultitaskViewModelByCompositeHandler::isExtensionMode()
{
    for (int i = 0; i < QGuiApplication::screens().size(); i++) {
        if (KWin::effects->virtualScreenGeometry() == QGuiApplication::screens().at(i)->geometry()) {
            return false;
        };
    }

    return true;
}

int MultitaskViewModelByCompositeHandler::logicalScreenCount()
{
    const auto screenCount = KWin::effects->screens().size();
    if (screenCount == 1 || !isExtensionMode()) {
        return 1;
    }

    return screenCount;
}

WId MultitaskViewModelByCompositeHandler::getWindowId(KWin::EffectWindow *window)
{
    return window->parent()->property("windowId").toULongLong();
}

QUuid MultitaskViewModelByCompositeHandler::getUuid(KWin::EffectWindow *window)
{
    return window->parent()->property("internalId").toUuid();
}

QList<Window> MultitaskViewModelByCompositeHandler::createWindowList(int desktopIndex, int screenIndex)
{
    KWin::EffectScreen* screen = KWin::effects->findScreen(screenIndex);
    QList<Window> windowList;
    const auto& effectWindowList = KWin::effects->stackingOrder();
    for (auto iter = effectWindowList.cbegin(); iter != effectWindowList.cend(); iter++) {
        auto* effectWindow = *iter;
        if (!shouldAcceptWindow(effectWindow)) {
            continue;
        }
        if ((!effectWindow->desktops().contains(desktopIndex) && effectWindow->desktops().size())
                || effectWindow->screen() != screen) {
            continue;
        }
        Window window(getWindowId(effectWindow),
                      getUuid(effectWindow).toString(),
                      effectWindow->caption(),
                      effectWindow->pos().x(),
                      effectWindow->pos().y(),
                      effectWindow->frameGeometry().width(),
                      effectWindow->frameGeometry().height(),
                      effectWindow->isMinimized());
        QList<int> list;
        QVector<uint> vec = effectWindow->desktops();
        if (vec.isEmpty()) {
            // If the vec is empty it means the window is on all desktops
            for (int i = 1; i <= getNumberOfDesktops(); ++i)
            list.append(i);
        } else {
            std::transform(vec.begin(), vec.end(),std::back_inserter(list), [](uint id) { return static_cast<int>(id); });
        }
        window.setDesktopsId(list);
        window.setScreenId(screenIndex);
        window.setIcon(QVariant::fromValue(effectWindow->icon()));
        windowList.append(window);
    }
    return windowList;
}

bool MultitaskViewModelByCompositeHandler::shouldAcceptWindow(KWin::EffectWindow* window)
{
    return !window->isDeleted()
            && (window->isNormalWindow() || (window->isDialog() && !window->transientFor()))
            && !window->isSkipSwitcher()
            && window->isOnCurrentActivity()
            && window->acceptsFocus()
            && window->isManaged();
}

QString MultitaskViewModelByCompositeHandler::getDesktopUuid(int desktopIndex)
{
    return "";
}

QString MultitaskViewModelByCompositeHandler::getOutputUuid(int outputIndex)
{
    return "";
}

void MultitaskViewModelByCompositeHandler::setDesktopName(const int desktopIndex, const QString name)
{
    Q_UNUSED(desktopIndex);
    Q_UNUSED(name);
}

void MultitaskViewModelByCompositeHandler::onWindowAdded(KWin::EffectWindow *window)
{
    if (!shouldAcceptWindow(window) && !window->isModal()) {
        return;
    }

    emit appWindowCreated();
}

void MultitaskViewModelByCompositeHandler::onWindowDeleted(KWin::EffectWindow *window)
{
    emit appWindowRemoved(getUuid(window).toString());
}

void MultitaskViewModelByCompositeHandler::onWindowActivated(KWin::EffectWindow* window)
{
    // KWin在发送windowActivated信号时
    // window对象是有可能为空的。对于多任务
    // 视图来说，只关心真正有窗口被激活的清空。
    // 因此，如果window对象为空，则不转发
    // 该信号
    if (window == nullptr) {
        return;
    }
    if (window->isDesktop()) {
        return;
    }

    emit appWindowActivated();
}

void MultitaskViewModelByCompositeHandler::onDesktopPresenceChanged(KWin::EffectWindow *window,
                                                  int oldDesktopIndex, int newDesktopIndex)
{
    if (!shouldAcceptWindow(window)) {
        return;
    }

    emit appWindowDesktopChanged(getUuid(window).toString(), oldDesktopIndex, newDesktopIndex);
}

void MultitaskViewModelByCompositeHandler::onNumberScreensChanged()
{
    emit screenCountChanged();
}

void MultitaskViewModelByCompositeHandler::connectSignals()
{
    QObject::connect(KWin::effects, &KWin::EffectsHandler::windowAdded,
                     this, &MultitaskViewModelByCompositeHandler::onWindowAdded);
    QObject::connect(KWin::effects, &KWin::EffectsHandler::windowDeleted,
                     this, &MultitaskViewModelByCompositeHandler::onWindowDeleted);
    QObject::connect(KWin::effects, &KWin::EffectsHandler::windowActivated,
                     this, &MultitaskViewModelByCompositeHandler::onWindowActivated);
    QObject::connect(KWin::effects, &KWin::EffectsHandler::desktopPresenceChanged,
                     this, &MultitaskViewModelByCompositeHandler::onDesktopPresenceChanged);
    QObject::connect(KWin::effects, SIGNAL(desktopChanged(int, int, KWin::EffectWindow*)),
                     this, SIGNAL(currentDesktopChanged(int, int)));
    QObject::connect(KWin::effects, SIGNAL(numberDesktopsChanged(uint)),
                     this, SIGNAL(desktopNumberChanged()));
    QObject::connect(KWin::effects, &KWin::EffectsHandler::virtualScreenSizeChanged,
                     this, &MultitaskViewModelByCompositeHandler::screenSizeChanged);
}
