/*
 * 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 General Public License as published by
 * the Free Software Foundation, either version 3 of the License, 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 General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * Authors: zy-yuan1 <zhangyuanyuan1@kylinos.cn>
 *
 */
#include "appwindowlistmodel.h"
#include "windowmanagerinterface.h"
#include <QMetaEnum>
#include <QDebug>


AppWindowListModel::AppWindowListModel(QObject *parent) : QAbstractListModel(parent)
{
    connectSignals();
    loadWindows();
}

void AppWindowListModel::connectSignals()
{
    QObject::connect(WindowManagerInterface::getInstance(), &WindowManagerInterface::appWindowRemoved,
                     this, &AppWindowListModel::onAppWindowRemoved);

    QObject::connect(WindowManagerInterface::getInstance(), &WindowManagerInterface::appWindowDesktopChanged,
                     this, &AppWindowListModel::onAppWindowDesktopChanged);

    QObject::connect(WindowManagerInterface::getInstance(), &WindowManagerInterface::desktopMoved,
                     this, &AppWindowListModel::onDesktopMoved);

    QObject::connect(WindowManagerInterface::getInstance(), &WindowManagerInterface::desktopRemoved,
                     this, &AppWindowListModel::onDesktopRemoved);
    QObject::connect(WindowManagerInterface::getInstance(), &WindowManagerInterface::appWindowAdd,
                     this, &AppWindowListModel::onAppWindowAdd);
    QObject::connect(WindowManagerInterface::getInstance(), &WindowManagerInterface::appWindowActivated,
                     this, &AppWindowListModel::onAppWindowActived);
    QObject::connect(WindowManagerInterface::getInstance(), &WindowManagerInterface::appWindowUpdated,
                     this, &AppWindowListModel::onAppWindowUpdated);
}

int AppWindowListModel::rowCount(const QModelIndex &parent) const
{
    return m_windowList.count();
}

QVariant AppWindowListModel::data(const QModelIndex &index, int role) const
{
    int i = index.row();
    if (i < 0 || i >= m_windowList.count()) {
        return QVariant();
    }
    Window window = m_windowList.at(i);
    switch (static_cast<Role>(role)) {
    case Role::Id:
        return window.id();
    case Role::Uuid:
        return window.uuid();
    case Role::Caption:
        return window.caption();
    case Role::X:
        return window.x();
    case Role::Y:
        return window.y();
    case Role::Width:
        return window.width();
    case Role::Height:
        return window.height();
    case Role::Minimized:
        return window.minimized();
    case Role::DesktopsId:
        return QVariant::fromValue<QList<int> >(window.desktopsId());
    case Role::ScreenId:
        return window.screenId();
    case Role::Icon:
        return window.icon();
    default:
        return {};
    }
}

bool AppWindowListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (!index.isValid()) {
        return false;
    }

    if (role == Role::DesktopsId) {
        m_windowList[index.row()].setDesktopsId(value.value<QList<int> >());
        Q_EMIT dataChanged(index, index, QVector<int>{Role::DesktopsId});
        return true;
    } else if (role == Role::ScreenId) {
        m_windowList[index.row()].setScreenId(Desktop::DesktopType(value.toInt()));
        Q_EMIT dataChanged(index, index, QVector<int>{Role::ScreenId});
        return true;
    }

    return false;
}

QHash<int, QByteArray> AppWindowListModel::roleNames() const
{
    QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
    QMetaEnum e = metaObject()->enumerator(metaObject()->indexOfEnumerator("Role"));

    for (int i = 0; i < e.keyCount(); ++i) {
        roles.insert(e.value(i), e.key(i));
    }
    return roles;
}

void AppWindowListModel::loadWindows()
{
    m_windowList.clear();
    beginResetModel();
    for (const Window &appwindow : getAllAppWindowList()) {
        m_windowList.append(appwindow);
    }
    endResetModel();
}

void AppWindowListModel::addItem(Window window)
{
    beginInsertRows(QModelIndex(), 0, 0);
    m_windowList.prepend(window);
    endInsertRows();
}

void AppWindowListModel::onAppWindowRemoved(QString windowId)
{
    for (int index = 0; index < m_windowList.size(); ++index) {
        auto item = m_windowList.at(index);
        if (item.uuid() == windowId) {
            beginRemoveRows(QModelIndex(), index, index);
            m_windowList.removeAt(index);
            endRemoveRows();
            break;
        }
    }
}

void AppWindowListModel::onAppWindowDesktopChanged(QString windowId, int oldDesktopIndex, int newDesktopIndex)
{
    for (int i = 0; i < m_windowList.size(); ++i) {
        auto &item = m_windowList.at(i);
        if (item.uuid() == windowId) {
            m_windowList[i].setDesktopsId(QList<int>{ newDesktopIndex });
            Q_EMIT dataChanged(index(i, 0), index(i, 0), QVector<int>{Role::DesktopsId});
            break;
        }
    }
}

void AppWindowListModel::onDesktopMoved(int oldDesktopIndex, int newDesktopIndex)
{
#ifdef KWINEFFECT
    //x-kwin would emit signal 'appWindowDesktopChanged', model has changed in function 'onAppWindowDesktopChanged'.
    return;
#endif
    for (int i = 0; i < m_windowList.size(); ++i) {
        auto &item = m_windowList.at(i);
        QList<int> desktops = item.desktopsId();
        if (desktops.contains(oldDesktopIndex)) {
            int ind = desktops.indexOf(oldDesktopIndex);
            desktops.replace(ind, newDesktopIndex);
        } else if (desktops.contains(newDesktopIndex)) {
            int ind = desktops.indexOf(newDesktopIndex);
            desktops.replace(ind, oldDesktopIndex);
        } else {
            continue;
        }

        m_windowList[i].setDesktopsId(desktops);
        Q_EMIT dataChanged(index(i, 0), index(i, 0), QVector<int>{Role::DesktopsId});
    }
}

void AppWindowListModel::onDesktopRemoved(int desktopIndex)
{
    //只需要处理desktopId > desktopIndex的情况，
    //desktopId < desktopIndex时，不影响窗口所在工作区的索引值
    //desktopId = desktopIndex时，在onAppWindowDesktopChanged中处理
    bool changeFlag;
    for (int i = 0; i < m_windowList.size(); ++i) {
        changeFlag = false;
        auto &item = m_windowList.at(i);
        QList<int> desktops = item.desktopsId();
        for (int i = 0; i < desktops.size(); ++i) {
            if (desktops.at(i) > desktopIndex) {
                changeFlag = true;
                desktops[i] -=1;
            }
            #ifdef KWINEFFECT
            //x-kwin not emit signal 'appWindowDesktopChanged' when removeDesktop, handle here.
            if (desktops.at(i) == desktopIndex) {
                changeFlag = true;
                desktops[i] = desktopIndex - 1 > 0 ? desktopIndex - 1 : 1;
            }
            #endif
        }
        if (changeFlag) {
            m_windowList[i].setDesktopsId(desktops);
            Q_EMIT dataChanged(index(i, 0), index(i, 0), QVector<int>{Role::DesktopsId});
        }
    }
}

void AppWindowListModel::onAppWindowAdd(const Window &window)
{
    qDebug() << "onAppWindowAdd window.caption" << window.caption();
    addItem(window);
}

void AppWindowListModel::onAppWindowActived()
{
    loadWindows();
}

void AppWindowListModel::onAppWindowUpdated(const Window &window)
{
    for (int i = 0; i < m_windowList.size(); ++i) {
        auto &item = m_windowList.at(i);
        if (item.uuid() == window.uuid()) {
            m_windowList.replace(i, window);
            Q_EMIT dataChanged(index(i, 0), index(i, 0));
            break;
        }
    }
}

// TODO: remove?
QVariant AppWindowListModel::getItemData(const QModelIndex &index)
{
    int i = index.row();
    if (i >= 0 && i < m_windowList.count()) {
        return QVariant::fromValue(m_windowList.at(i));
    } else {
        return QVariant();
    }
}

QList<Window> AppWindowListModel::getAllAppWindowList() const
{
    QList<Window> allAppWindowList;
    for(int desktopIndex = 1; desktopIndex <= WindowManagerInterface::getInstance()->getNumberOfDesktops(); desktopIndex++) {
        for (int screenIndex = 0; screenIndex < WindowManagerInterface::getInstance()->logicalScreenCount(); screenIndex++) {
            QList<Window> windowList;
            windowList = WindowManagerInterface::getInstance()->createWindowList(desktopIndex, screenIndex);
            for (int i = windowList.size() - 1; i >= 0; --i) {
                for (const auto &wind : allAppWindowList) {
                    if (wind.uuid() == windowList[i].uuid()) {
                        windowList.removeAt(i);
                        break;
                    }
                }
            }
            std::reverse(windowList.begin(), windowList.end());
            allAppWindowList += windowList;
        }
    }
    return allAppWindowList;
}
