/*
 *
 *  * Copyright (C) 2023, 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: Nicole <buxiaoqing@kylinos.cn>
 *
 */

#include <gio/gdesktopappinfo.h>
#include <KWindowSystem>
#include "ukuitaskbutton.h"
#include "../panel/common/common.h"
#include "../panel/customstyle.h"

UKUITaskButton::UKUITaskButton(const WindowId window, const QString desktopFile, QWidget *parent) :
    QToolButton(nullptr),
    m_timer(new QTimer(this)),
    m_windowId(window),
    m_desktopFileName(desktopFile),
    m_parent(parent)
{
    this->setObjectName("UKUITaskButton-" + desktopFile);
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    setMinimumWidth(1);
    setMinimumHeight(1);
    setToolButtonStyle(Qt::ToolButtonIconOnly);
    setAcceptDrops(false);
    setProperty("useButtonPalette",true);
    setAutoRaise(true);
    setSystemStyle();
    const QByteArray styleId(ORG_UKUI_STYLE);
    if (QGSettings::isSchemaInstalled(styleId)) {
        m_styleGsettings.reset(new QGSettings(styleId));
        connect(m_styleGsettings.get(), &QGSettings::changed, this, [=] (const QString &key) {
            if (key == STYLE_NAME) {
                setSystemStyle();
            }
            if (key == SYSTEM_FONT_SIZE) {
                updateCaption();
            }
        });
    }

    const QByteArray id(PANEL_SETTINGS);
    if (QGSettings::isSchemaInstalled(id)) {
        m_gsettings.reset(new QGSettings(id));
        m_gsettingKeys = m_gsettings->keys();
        if (m_gsettingKeys.contains(ICON_SIZE_KEY))
            m_iconSize = m_gsettings->get(ICON_SIZE_KEY).toInt();
        if (m_gsettingKeys.contains(GROUPING_ENABLE))
            m_isGrouping = m_gsettings->get(GROUPING_ENABLE).toBool();
        if (m_gsettingKeys.contains(PANEL_POSITION_KEY))
            m_panelPosition = m_gsettings->get(PANEL_POSITION_KEY).toInt();
        if (m_gsettingKeys.contains(PANEL_SIZE_KEY))
            m_panelSize = m_gsettings->get(PANEL_SIZE_KEY).toInt();

        setFixedSize(m_panelSize, m_panelSize);
        connect(m_gsettings.get(), &QGSettings::changed, this, [&] (const QString &key) {
            if (key == ICON_SIZE_KEY) {
                m_iconSize = m_gsettings->get(ICON_SIZE_KEY).toInt();
                updateIcon();
            }
            if (key == TASKBAR_BTN_SPAN) {
                updateCaption();
            }
            if (key == GROUPING_ENABLE) {
                m_isGrouping = m_gsettings->get(GROUPING_ENABLE).toBool();
            }
            if (key == PANEL_POSITION_KEY) {
                m_panelPosition = m_gsettings->get(PANEL_POSITION_KEY).toInt();
            }
            if (key == PANEL_SIZE_KEY) {
                m_panelSize = m_gsettings->get(PANEL_SIZE_KEY).toInt();
            }
        });
    }

    updateCaption();
    updateIcon();

    m_timer->setTimerType(Qt::PreciseTimer);
    connect(m_timer, &QTimer::timeout, this, &UKUITaskButton::timeToEmit);

    connect(KWindowSystem::self(), static_cast<void (KWindowSystem::*)(WId, NET::Properties, NET::Properties2)>(&KWindowSystem::windowChanged), this, &UKUITaskButton::onWindowChanged);

    m_hightlightAnimation = new QPropertyAnimation(this, "opacity");
    m_hightlightAnimation->setDuration(2000);
    m_hightlightAnimation->setKeyValueAt(0, 255 * 0.6);
    m_hightlightAnimation->setKeyValueAt(0.5, 255 * 0.25);
    m_hightlightAnimation->setKeyValueAt(1,255 * 0.6);
    m_hightlightAnimation->setLoopCount(3);
    m_opacityStyle = new CustomStyle("attentionbutton",true);
}

UKUITaskButton::~UKUITaskButton()
{
    if (m_gsettings) {
        m_gsettings.reset(nullptr);
    }
    if (m_act) {
        m_act.reset(nullptr);
    }
    if (m_styleGsettings) {
        m_styleGsettings.reset(nullptr);
    }
    if (m_hightlightAnimation) {
        delete m_hightlightAnimation;
        m_hightlightAnimation = nullptr;
    }
    if (m_opacityStyle) {
        delete m_opacityStyle;
        m_opacityStyle = nullptr;
    }
}

void UKUITaskButton::onWindowChanged(WId id, NET::Properties properties, NET::Properties2 properties2) {
    if (m_windowId.toUInt() != id || id == 0) {
        return;
    }
    if (properties.testFlag(NET::WMVisibleName) || properties.testFlag(NET::WMName)) {
        updateCaption();
    }
    if (properties.testFlag(NET::WMIcon)) {
        updateIcon();
    }

    KWindowInfo info(id, NET::WMState);
    if (info.state() & NET::DemandsAttention  && !m_isDemandingAttention) {
        this->setStyle(m_opacityStyle);
        m_hightlightAnimation->start();
        m_isDemandingAttention = true;

    } else if (info.state() & NET::Focused) {
        if(m_isDemandingAttention) {
            if(m_hightlightAnimation->state() & QAbstractAnimation::Running) {
                m_hightlightAnimation->stop();
            }
            this->setStyle(new CustomStyle("taskbutton"));
            m_isDemandingAttention = false;
        }
    }
}

int UKUITaskButton::opacity() const
{
    return m_alpha;
}

void UKUITaskButton::setOpacity(int alpha)
{
    m_alpha = alpha;
    m_opacityStyle->setOpacity(alpha);
    this->update();
}

WindowId UKUITaskButton::windowId() const
{
    return m_windowId;
}

QString UKUITaskButton::desktopFileName()
{
    return m_desktopFileName;
}

void UKUITaskButton::setDesktopFileName(QString desktopFileName)
{
    m_desktopFileName = desktopFileName;
}

void UKUITaskButton::updateIcon()
{
    if (m_windowId != 0) {
        m_icon = kdk::WindowManager::getWindowIcon(m_windowId);

        if (m_icon.isNull()) {
            qDebug() << "Window's icon is NULL. Get icon from desktop";
            XdgDesktopFile xdg;
            if (xdg.load(m_desktopFileName)) {
                m_icon = QIcon::fromTheme(xdg.iconName());
            } else {
                m_icon = QIcon::fromTheme("application-x-desktop");
            }
        }
        setIcon(m_icon);
        setIconSize(QSize(m_iconSize, m_iconSize));
    } else if (m_isPinned) {
        quickLaunchAction();
    }
}

void UKUITaskButton::updateCaption()
{
    m_caption = kdk::WindowManager::getWindowTitle(m_windowId);
    int btnspan = 1;
    int panelsize = 46;
    if (m_gsettingKeys.contains(TASKBAR_BTN_SPAN) && m_gsettingKeys.contains(PANEL_SIZE_KEY)) {
        btnspan = m_gsettings->get(TASKBAR_BTN_SPAN).toInt();
        panelsize = m_gsettings->get(PANEL_SIZE_KEY).toInt();
    }
    QString formatAppName = this->fontMetrics().elidedText(m_caption, Qt::ElideRight, panelsize * (btnspan - 1));
    this->setText(formatAppName);
}

bool UKUITaskButton::isLeaderWindow()
{
    return this->isActiveWindow();
}

bool UKUITaskButton::isActiveWindow()
{
    return m_windowId == kdk::WindowManager::currentActiveWindow();
}

bool UKUITaskButton::isOnCurrentDesktop()
{
    return kdk::WindowManager::isOnCurrentDesktop(m_windowId);
}

void UKUITaskButton::refreshIconGeometry()
{
    QString platform = QGuiApplication::platformName();
    if (platform.startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) {
        return;
    }

    float scale = qApp->devicePixelRatio();
    QRect rect = geometry();
    rect.moveTo(mapToGlobal(QPoint(0, 0)).x() * scale, mapToGlobal(QPoint(0, 0)).y() * scale);

    NETWinInfo info(QX11Info::connection(),
                    windowId().toInt(),
                    (WId) QX11Info::appRootWindow(),
                    NET::WMIconGeometry,
                    0);
    NETRect const curr = info.iconGeometry();
    if (curr.pos.x != rect.x() || curr.pos.y != rect.y()
            || curr.size.width != rect.width() || curr.size.height != rect.height()) {
        NETRect nrect;
        nrect.pos.x = rect.x();
        nrect.pos.y = rect.y();
        nrect.size.height = rect.height();
        nrect.size.width = rect.width();
        info.setIconGeometry(nrect);
    }
}

void UKUITaskButton::activeWindow()
{
    if (this->isActiveWindow()) {
        minimizeWindow();
    } else {
        kdk::WindowManager::activateWindow(m_windowId);
        setUrgencyHint(false);
    }
}

void UKUITaskButton::minimizeWindow()
{
    kdk::WindowManager::minimizeWindow(m_windowId);
}

void UKUITaskButton::closeWindow()
{
    kdk::WindowManager::closeWindow(m_windowId);
}

void UKUITaskButton::quickLaunchAction()
{
    XdgDesktopFile xdg;
    if (xdg.load(m_desktopFileName)) {
        QString appName = xdg.localizedValue("Name[" + QLocale::system().name() + "]").toString();
        if (appName.isEmpty()) {
            appName = xdg.localizedValue("Name").toString();
        }
        QIcon appIcon = QIcon::fromTheme(xdg.iconName());

        if (appIcon.isNull()) {
            appIcon = xdg.icon();
        }
        if (appIcon.isNull()) {
            qDebug() << "Can't get icon from desktop";
            appIcon = QIcon::fromTheme("application-x-desktop");;
        }
        m_act.reset(new QAction());
        m_act->setText(appName);
        m_act->setIcon(appIcon);
        m_act->setData(xdg.fileName());
        if (this->icon().isNull()) {
            this->setIcon(appIcon);
        }
        this->setIconSize(QSize(m_iconSize, m_iconSize));
        connect(m_act.get(), &QAction::triggered, this, [this](){
            execAction();
        });
    }
}

void UKUITaskButton::execAction(QString additionalAction)
{
    XdgDesktopFile xdg;
    if (xdg.load(m_desktopFileName)) {
        if (additionalAction.isEmpty()) {
            QDBusInterface iface("com.kylin.ProcessManager",
                                 "/com/kylin/ProcessManager/AppLaunche",
                                 "com.kylin.ProcessManager.AppLauncher",
                                 QDBusConnection::sessionBus());
            QDBusReply<bool> reply;
            if (iface.isValid()) {
                reply = iface.call("LaunchApp", m_desktopFileName);
            }
            if (!iface.isValid() || !reply.isValid() || !reply) {
                qDebug() << "AppManager Interface is Not Valid! Use GIO Interface instead.";
                GDesktopAppInfo *appinfo = g_desktop_app_info_new_from_filename(xdg.fileName().toStdString().data());
                if (!g_app_info_launch_uris(G_APP_INFO(appinfo), nullptr, nullptr, nullptr)) {
                    qWarning() << "XdgDesktopFile" << m_desktopFileName << "is not valid!";
                }
                g_object_unref(appinfo);
            }
        } else {
            if (!xdg.actionActivate(additionalAction, QStringList{})) {
                qDebug() << "Can't activate additionalAction:" << additionalAction;
            }
        }
    }
}

void UKUITaskButton::getAdditionalActions()
{
    for (QAction *act : qAsConst(m_additionalActionsList)) {
        delete act;
        act = nullptr;
    }
    m_additionalActionsList.clear();

    XdgDesktopFile xdg;
    if (xdg.load(m_desktopFileName)) {
        if (xdg.actions().isEmpty()) {
            return;
        }

        for (auto const & action : const_cast<const QStringList &&>(xdg.actions())) {
            QAction *act = new QAction(xdg.actionIcon(action), xdg.actionName(action), this);
            if (m_act->icon().isNull()) {
                m_act->setIcon(act->icon());
            }
            act->setData(action);
            connect(act, &QAction::triggered, [this, act](){
                execAction(act->data().toString());
            });

            m_additionalActionsList.push_back(act);
        }
    }
}

void UKUITaskButton::onButtonsCountChanged(int buttonsCount)
{
    m_buttonsCount = buttonsCount;
    repaint();
}

void UKUITaskButton::onButtonsStatusChanged(bool isPinned)
{
    m_isPinned = isPinned;
}

void UKUITaskButton::paintEvent(QPaintEvent *event)
{
    QToolButton::paintEvent(event);

    QStyleOption option;
    option.initFrom(this);
    QPainter painter(this);
    if (m_gsettingKeys.contains(GROUPING_ENABLE))
        m_isGrouping = m_gsettings->get(GROUPING_ENABLE).toBool();

    if (m_isGrouping) {
        if (m_buttonsCount > 1) {
            painter.setRenderHint(QPainter::Antialiasing, true);
            painter.setPen(QPen(option.palette.color(QPalette::Highlight), 4, Qt::SolidLine, Qt::RoundCap));
            painter.drawLine(QPoint(option.rect.center().x() - 6, option.rect.bottomLeft().y() - 3),
                             QPoint(option.rect.center().x() + 6, option.rect.bottomLeft().y() - 3));
        } else if (m_buttonsCount == 1) {
            painter.setRenderHint(QPainter::Antialiasing, true);
            painter.setPen(QPen(option.palette.color(QPalette::Highlight), 4, Qt::SolidLine, Qt::RoundCap));
            painter.drawLine(QPoint(option.rect.center().x() - 2, option.rect.bottomLeft().y() - 3),
                             QPoint(option.rect.center().x() + 2, option.rect.bottomLeft().y() - 3));
        }
    } else {
        if (m_buttonsCount > 0) {
            QColor color = palette().color(QPalette::BrightText);
            color.setAlphaF(0.15);
            QBrush brush = QBrush(color);
            painter.setPen(QPen(brush, 1, Qt::SolidLine, Qt::RoundCap));
            painter.drawRoundedRect(option.rect.adjusted(2, 2, -2,- 2), 6, 6);
        }
    }
}

void UKUITaskButton::mouseReleaseEvent(QMouseEvent* event)
{
    if (event->button() == Qt::LeftButton) {
        if (m_buttonsCount == 0) {
            this->execAction();
        } else {
            if (m_buttonsCount == 1 || !m_isGrouping) {
                refreshIconGeometry();
                if (isActiveWindow()) {
                    minimizeWindow();
                } else {
                    activeWindow();
                }
            } else {
                // TODO: show thumbnails
            }
        }
    }
    QToolButton::mouseReleaseEvent(event);
}

void UKUITaskButton::mousePressEvent(QMouseEvent *event)
{
    event->setAccepted(false);
}

void UKUITaskButton::enterEvent(QEvent *e)
{
    if (m_isGrouping) {
        e->setAccepted(false);
        return;
    }
    m_taskBtnEvent = ENTEREVENT;
    if (m_timer->isActive()) {
        m_timer->stop();
    }
    m_timer->start(400);

    QToolButton::enterEvent(e);
}

void UKUITaskButton::leaveEvent(QEvent *e)
{
    if (m_isGrouping) {
        e->setAccepted(false);
        return;
    }
    m_taskBtnEvent = LEAVEEVENT;
    if (m_timer->isActive()) {
        m_timer->stop();
    }
    m_timer->start(400);

    QToolButton::leaveEvent(e);
}

void UKUITaskButton::contextMenuEvent(QContextMenuEvent *e)
{
    QMenu *rightMenu = new QMenu(this);
    rightMenu->setAttribute(Qt::WA_DeleteOnClose);

    //desktop文件无法解析,不存在或者NoDisplay值为true，右键菜单仅显示退出
    XdgDesktopFile xdg;
    if (!xdg.load(m_desktopFileName) || m_desktopFileName.isEmpty()) {
        qDebug() << "Can't load desktop:" << m_desktopFileName;
        rightMenuCloseAction(rightMenu, e->pos());
        return;
    }
    if (xdg.localizedValue("NoDisplay").toBool()) {
            qDebug() << "NoDisplay:" << xdg.localizedValue("NoDisplay").toBool();
            rightMenuCloseAction(rightMenu, e->pos());
            return;
    }
    quickLaunchAction();

    rightMenu->addAction(m_act.get());
    getAdditionalActions();
    if (m_additionalActionsList.size() > 0) {
        rightMenu->addActions(m_additionalActionsList);
    }
    rightMenu->addSeparator();

    if (m_isPinned) {
        QAction *unpinAct = rightMenu->addAction(QIcon::fromTheme("ukui-unfixed-symbolic"), tr("Unpin from taskbar"));
        connect(unpinAct, &QAction::triggered, [this](){ emit unPinFromTaskbar(m_desktopFileName);});
    } else {
        QAction *pinAct = rightMenu->addAction(QIcon::fromTheme("ukui-fixed-symbolic"), tr("Pin to taskbar"));
        connect(pinAct, &QAction::triggered, [this](){ emit pinToTaskbar(m_desktopFileName);});
    }
    rightMenuCloseAction(rightMenu, e->pos());
    QObject::connect(rightMenu, &QMenu::destroyed, this, [&](){
        this->setAttribute(Qt::WA_UnderMouse, false);
        this->setDown(false);
        this->update();
    });

}

void UKUITaskButton::timeToEmit()
{
    if (m_timer->isActive()) {
        m_timer->stop();
    }

    QList<WindowId> winIdList;
    winIdList.append(m_windowId);
    QPoint abs = mapToGlobal(QPoint(0, 0));

    switch (m_taskBtnEvent) {
    case ENTEREVENT:
        if (isHorizontalPanel()) {
            int buttonCenterPositionX = abs.x() + this->width() / 2;
            emit enterButton(winIdList, "", buttonCenterPositionX, 0);
        } else {
            int buttonCenterPositionY = abs.y() + this->height() / 2;
            emit enterButton(winIdList, "", 0, buttonCenterPositionY);
        }
        break;

    case LEAVEEVENT:
        if (isHorizontalPanel()) {
            int buttonCenterPositionX = abs.x() + this->width() / 2;
            emit leaveButton(winIdList, "", buttonCenterPositionX, 0);
        } else {
            int buttonCenterPositionY = abs.y() + this->height() / 2;
            emit leaveButton(winIdList, "", 0, buttonCenterPositionY);
        }
        break;

    case OTHEREVENT:
    default:
        break;
    }
}

void UKUITaskButton::rightMenuCloseAction(QMenu *menu, QPoint pos)
{
    if (m_buttonsCount > 0) {
        menu->addSeparator();
        QAction *closeAct = menu->addAction(QIcon::fromTheme("application-exit-symbolic"), tr("close"));
        connect(closeAct, &QAction::triggered, [this](){ emit closeGroup();});
    }
    menu->setGeometry(caculateMenuPosition(mapToGlobal(pos), menu->sizeHint()));
    menu->show();

}

QRect UKUITaskButton::caculateMenuPosition(const QPoint &absolutePos, const QSize &windowSize)
{
    int x = absolutePos.x(), y = absolutePos.y();
    QRect screen = QApplication::desktop()->screenGeometry(this);

    switch (m_panelPosition)
    {
    case PanelPosition::Top:
        y = m_panelSize;
        break;
    case PanelPosition::Bottom:
        y = screen.height() - m_panelSize - windowSize.height();
        break;
    case PanelPosition::Left:
        x = m_panelSize;
        break;
    case PanelPosition::Right:
        x = screen.width() - m_panelSize - windowSize.width();
        break;
    }

    QRect res(QPoint(x, y), windowSize);
    if (res.right() > screen.right())
        res.moveRight(screen.right());

    if (res.bottom() > screen.bottom())
        res.moveBottom(screen.bottom());

    if (res.left() < screen.left())
        res.moveLeft(screen.left());

    if (res.top() < screen.top())
        res.moveTop(screen.top());

    return res;
}

bool UKUITaskButton::isHorizontalPanel()
{
    return m_panelPosition == PanelPosition::Bottom || m_panelPosition == PanelPosition::Top;
}

void UKUITaskButton::setSystemStyle()
{
    QPalette pal = this->palette();
    QColor col = pal.color(QPalette::Active, QPalette::BrightText);
    col.setAlphaF(0.13);
    pal.setColor(QPalette::Button, col);
    this->setPalette(pal);

}

void UKUITaskButton::setUrgencyHint(bool set) {
    if (m_urgencyHint == set) {
        return;
    }

    if (!set) {
        qWarning() << "qApp->platformName()" << qApp->platformName();
        if (qApp->platformName().contains("xcb"))
            KWindowSystem::demandAttention(this->m_windowId.toUInt(), false);
        else
            qWarning() << "wayland not support now";
    }

    m_urgencyHint = set;
    setProperty("urgent", set);
    style()->unpolish(this);
    style()->polish(this);
    update();
}
