/***************************************************************************
 *                                                                         *
 *   Copyright (C) 2009 Marco Martin <notmart@gmail.com>                   *
 *                                                                         *
 *   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 2 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, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .        *
 ***************************************************************************/

#include "dbussystemtraytask.h"
#include "debug.h"
#include "host.h"

#include "dbussystemtrayprotocol.h"

#include <QtCore/QMetaEnum>
#include <QtCore/QDir>
#include <QtCore/QCoreApplication>
#include <QtWidgets/QMenu>
#include <QtGui/QIcon>
#include <QLoggingCategory>
#include <QQuickWindow>

#include <KJob>
#include <KIconLoader>
#include <KIconEngine>

#include <Plasma/ServiceJob>
#include <Plasma/Applet>


namespace SystemTray
{

DBusSystemTrayTask::DBusSystemTrayTask(const QString &serviceName, Plasma::DataEngine *dataEngine, QObject *parent)
    : Task(parent),
      m_serviceName(serviceName),
      m_taskId(serviceName),
      m_customIconLoader(0),
      m_dataEngine(dataEngine),
      m_service(dataEngine->serviceForSource(serviceName)),
      m_isMenu(false),
      m_valid(false)
{
    //qCDebug(SYSTEMTRAY);
    m_service->setParent(this);

    m_dataEngine->connectSource(serviceName, this);
}

DBusSystemTrayTask::~DBusSystemTrayTask()
{
}

bool DBusSystemTrayTask::isValid() const
{
    return m_valid;
}

bool DBusSystemTrayTask::isEmbeddable() const
{
    return false; // this task cannot be embed because it only provides information to GUI part
}

bool DBusSystemTrayTask::isWidget() const
{
    return false; // isn't a widget
}

QString DBusSystemTrayTask::taskId() const
{
    return m_taskId;
}

QIcon DBusSystemTrayTask::icon() const
{
    return m_icon;
}

void DBusSystemTrayTask::activate1(int x, int y) const
{
    QVariantMap params;
    if (m_isMenu) {
        params = m_service->operationDescription(QStringLiteral("ContextMenu"));
    } else {
        params = m_service->operationDescription(QStringLiteral("Activate"));
    }
    params[QStringLiteral("x")] = x;
    params[QStringLiteral("y")] = y;
    KJob *job = m_service->startOperationCall(params);
    connect(job, &KJob::result, this, &DBusSystemTrayTask::_onContextMenu);
}

void DBusSystemTrayTask::activate2(int x, int y) const
{
    QVariantMap params = m_service->operationDescription(QStringLiteral("SecondaryActivate"));
    params[QStringLiteral("x")] = x;
    params[QStringLiteral("y")] = y;
    m_service->startOperationCall(params);
}

void DBusSystemTrayTask::activateHorzScroll(int delta) const
{
    _activateScroll(delta, QStringLiteral("Horizontal"));
}

// Copied from kde-runtime/plasma/declarativeimports/core/iconitem.cpp
bool static hasm_svgIcon(QVariant source)
{
    // Set up compat envrionment, afterwards it is 100% copy'n'pastable.
    const QString element = source.toString();
    const QString filename = element.split(QStringLiteral("-")).first();
    Plasma::Svg svgIcon;
    Plasma::Svg *m_svgIcon = &svgIcon;

    //try as a svg toolbar icon
    m_svgIcon->setImagePath("toolbar-icons/" + filename);

    //try as a svg normal icon (like systray)
    if (!m_svgIcon->isValid() || !m_svgIcon->hasElement(element)) {
        m_svgIcon->setImagePath("icons/" + filename);
    }
    m_svgIcon->setContainsMultipleImages(true);

    //success?
    if (m_svgIcon->isValid() && m_svgIcon->hasElement(element)) {
        return true;
    }
    return false;
}

QVariant DBusSystemTrayTask::customIcon(QVariant variant) const
{
    if (variant.canConvert<QString>()) {
        // If we have no icon loader there is nothing to be done with strings.
        if (!m_customIconLoader)
            return variant;

        // If an SVG icon can be found (via Plasma theme) the name needs to be
        // passed along for IconItem to be able to resolve to the theme name as
        // well.
        if (hasm_svgIcon(variant))
            return variant;

        return QVariant(QIcon(new KIconEngine(variant.toString(), m_customIconLoader)));
    } else {
        // Most importantly QIcons. Nothing to do for those.
        return variant;
    }
}

void DBusSystemTrayTask::activateVertScroll(int delta) const
{
    _activateScroll(delta, QStringLiteral("Vertical"));
}

void DBusSystemTrayTask::_activateScroll(int delta, QString direction) const
{
    QVariantMap params = m_service->operationDescription(QStringLiteral("Scroll"));
    params[QStringLiteral("delta")] = delta;
    params[QStringLiteral("direction")] = direction;
    m_service->startOperationCall(params);
}

void DBusSystemTrayTask::activateContextMenu(int x, int y) const
{
    QVariantMap params = m_service->operationDescription(QStringLiteral("ContextMenu"));
    params[QStringLiteral("x")] = x;
    params[QStringLiteral("y")] = y;
    KJob *job = m_service->startOperationCall(params);
    connect(job, &KJob::result, this, &DBusSystemTrayTask::_onContextMenu);
}

void DBusSystemTrayTask::_onContextMenu(KJob *job)
{
    if (QCoreApplication::closingDown()) {
        // apparently an edge case can be triggered due to the async nature of all this
        // see: https://bugs.kde.org/show_bug.cgi?id=251977
        return;
    }

    Plasma::ServiceJob *sjob = qobject_cast<Plasma::ServiceJob *>(job);
    if (!sjob) {
        return;
    }

    QMenu *menu = qobject_cast<QMenu *>(sjob->result().value<QObject *>());

    if (menu) {
        menu->adjustSize();
        int x = sjob->parameters()[QStringLiteral("x")].toInt();
        int y = sjob->parameters()[QStringLiteral("y")].toInt();

        //try tofind the icon screen coordinates, and adjust the position as a poor
        //man's popupPosition
        if (parent()) {
            Host* h = qobject_cast<Host*>(parent()->parent());
            if (h && h->rootItem()) {
                QRect screenItemRect(h->rootItem()->mapToScene(QPointF(0, 0)).toPoint(), QSize(h->rootItem()->width(), h->rootItem()->height()));

                if (h->rootItem()->window()) {
                    screenItemRect.moveTopLeft(h->rootItem()->window()->mapToGlobal(screenItemRect.topLeft()));
                }

                switch (h->rootItem()->property("location").toInt()) {
                case Plasma::Types::LeftEdge:
                    x = screenItemRect.right();
                    break;
                case Plasma::Types::RightEdge:
                    x = screenItemRect.left() - menu->width();
                    break;
                case Plasma::Types::TopEdge:
                    y = screenItemRect.bottom();
                    break;
                case Plasma::Types::BottomEdge:
                    y = screenItemRect.top() - menu->height();
                    break;
                default:
                    if (screenItemRect.top() - menu->height() >= h->rootItem()->window()->screen()->geometry().top()) {
                        y = screenItemRect.top() - menu->height();
                    } else {
                        y = screenItemRect.bottom();
                    }
                }
            }
        }

        menu->popup(QPoint(x, y));
    }
}

void DBusSystemTrayTask::dataUpdated(const QString &taskName, const Plasma::DataEngine::Data &properties)
{
    Q_UNUSED(taskName);

    const QString id = properties[QStringLiteral("Id")].toString();
    bool becomeValid = false;
    if (!id.isEmpty() && id != m_taskId) {
        m_taskId = id;
        m_valid = true;
        becomeValid = true;
        setObjectName(QStringLiteral("SystemTray-%1").arg(m_taskId));
    }

    QString cat = properties[QStringLiteral("Category")].toString();
    if (!cat.isEmpty()) {
        int index = metaObject()->indexOfEnumerator("Category");
        int key = metaObject()->enumerator(index).keyToValue(cat.toLatin1());

        if (key != -1) {
            setCategory((Task::Category)key);
        }
    }

    if (properties[QStringLiteral("TitleChanged")].toBool() || becomeValid) {
        QString title = properties[QStringLiteral("Title")].toString();
        if (!title.isEmpty()) {
            bool is_title_changed = (name() != title);
            setName(title);
            if (is_title_changed) {
                emit changedTitle();
                emit changed(this);
            }
        }
    }

    if (properties[QStringLiteral("IconsChanged")].toBool() || becomeValid) {
        syncIcons(properties);
        emit changedIcons();
    }

    if (properties[QStringLiteral("StatusChanged")].toBool() || becomeValid) {
        syncStatus(properties[QStringLiteral("Status")].toString());
    }

    if (properties[QStringLiteral("ToolTipChanged")].toBool() || becomeValid) {
        syncToolTip(properties[QStringLiteral("ToolTipTitle")].toString(),
                    properties[QStringLiteral("ToolTipSubTitle")].toString(),
                    properties[QStringLiteral("ToolTipIcon")].value<QIcon>());
    }

    bool is_menu = properties[QStringLiteral("ItemIsMenu")].toBool();
    if (is_menu != m_isMenu) {
        m_isMenu = is_menu;
        emit changedIsMenu();
    }

    if (becomeValid) {
        DBusSystemTrayProtocol *protocol = qobject_cast<DBusSystemTrayProtocol*>(parent());
        if (protocol) {
            protocol->initedTask(this);
        }
    }
}

void DBusSystemTrayTask::syncIcons(const Plasma::DataEngine::Data &properties)
{
    m_icon = properties[QStringLiteral("Icon")].value<QIcon>();
    m_attentionIcon = properties[QStringLiteral("AttentionIcon")].value<QIcon>();

    QString icon_name            = properties[QStringLiteral("IconName")].toString();
    QString att_icon_name        = properties[QStringLiteral("AttentionIconName")].toString();
    QString movie_path           = properties[QStringLiteral("AttentionMovieName")].toString();
    QString overlay_icon_name    = properties[QStringLiteral("OverlayIconName")].value<QString>();
    QString icon_theme_path      = properties[QStringLiteral("IconThemePath")].value<QString>();
    bool is_icon_changed                = false;
    bool is_icon_name_changed           = false;
    bool is_att_icon_name_changed       = false;
    bool is_movie_path_changed          = false;
    bool is_overlay_icon_name_changed   = false;

    if (icon_name != m_iconName) {
        m_iconName = icon_name;
        is_icon_name_changed = true;
    }

    if (att_icon_name != m_attentionIconName) {
        m_attentionIconName = att_icon_name;
        is_att_icon_name_changed = true;
    }

    if (!movie_path.isEmpty() && !QDir::isAbsolutePath(movie_path)) {
        movie_path = KIconLoader::global()->moviePath(movie_path, KIconLoader::Panel);
    }

    if (movie_path != m_moviePath) {
        m_moviePath = movie_path;
        is_movie_path_changed = true;
    }

    if (overlay_icon_name != m_overlayIconName) {
        m_overlayIconName = overlay_icon_name;
        is_overlay_icon_name_changed = true;
    }

    if (icon_theme_path != m_iconThemePath) {
        m_iconThemePath = icon_theme_path;

        if (m_customIconLoader) {
            delete m_customIconLoader;
            m_customIconLoader = 0;
        }
        const QString path = m_iconThemePath;
        if (!path.isEmpty()) {
            // FIXME: If last part of path is not "icons", this won't work!
            QStringList tokens = path.split('/', QString::SkipEmptyParts);
            if (tokens.length() >= 3 && tokens.takeLast() == QLatin1String("icons")) {
                QString appName = tokens.takeLast();

                // We use a separate instance of KIconLoader to avoid
                // adding all application dirs to KIconLoader::global(), to
                // avoid potential icon name clashes between application
                // icons
                m_customIconLoader = new KIconLoader(appName, QStringList(), this);
                m_customIconLoader->addAppDir(appName, path);
                is_icon_changed = true;
            } else {
                qWarning() << "Wrong IconThemePath" << path << ": too short or does not end with 'icons'";
            }
        }

        // Trigger updates
        is_icon_name_changed = true;
        is_att_icon_name_changed = true;
        is_overlay_icon_name_changed = true;
    }

    // If we have custom theme path set, it means we're using icons loaded with custom
    // icon loader, which would not be used by IconItem. So we first check if Plasma themed SVG
    // exists for this icon and if not, force IconItem to use the QIcon we provide
    // instead of trying to lookup the icon by name (and fail)
    if (!m_iconThemePath.isEmpty()) {
        if (!hasm_svgIcon(icon_name)) {
            m_iconName = QString();
        }

        is_icon_changed = true;
    }

    if (is_icon_changed) {
        emit changedIcons();
    }

    // emit signals
    if (is_icon_name_changed) {
        emit changedIconName();
    }
    if (is_att_icon_name_changed) {
        emit changedAttIconName();
    }
    if (is_movie_path_changed) {
        emit changedMoviePath();
    }
    if (is_overlay_icon_name_changed) {
        emit changedOverlayIconName();
    }
}


//toolTip

void DBusSystemTrayTask::syncToolTip(const QString &title, const QString &subTitle, const QIcon &toolTipIcon)
{
    if (title.isEmpty() && subTitle.isEmpty()) {
        // if empty tooltip use application name as fallback
        if (m_tooltipTitle != name()) {
            m_tooltipTitle = name();
            emit changedTooltipTitle();
        }

        if (!m_tooltipText.isEmpty()) {
            m_tooltipText.clear();
            emit changedTooltipText();
        }
    } else {
        if (title != m_tooltipTitle) {
            m_tooltipTitle = title;
            emit changedTooltipTitle();
        }

        if (subTitle != m_tooltipText) {
            m_tooltipText = subTitle;
            emit changedTooltipText();
        }
    }

    bool iconNameChanged = false;

    if (toolTipIcon.isNull()) {
        iconNameChanged = m_tooltipIcon.name() != m_icon.name();
        m_tooltipIcon = m_icon;
    } else {
        iconNameChanged = m_tooltipIcon.name() != toolTipIcon.name();
        m_tooltipIcon = toolTipIcon;
        emit changedTooltip();
    }

    emit changedTooltip();
    if (iconNameChanged) {
        emit changedTooltipIconName();
    }
}


//Status

void DBusSystemTrayTask::syncStatus(QString newStatus)
{
    Task::Status status = (Task::Status)metaObject()->enumerator(metaObject()->indexOfEnumerator("Status")).keyToValue(newStatus.toLatin1());

    if (this->status() == status) {
        return;
    }

    setStatus(status);
}

}


