/*
 * 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: iaom <zhangpengfei@kylinos.cn>
 */

#include "notification-client.h"
#include "notification-client-private.h"
#include <QDBusConnection>
#include <QQmlEngine>
#include "notificationclientadaptor.h"

using namespace UkuiNotification;

NotificationClientPrivate::NotificationClientPrivate(NotificationClient *q) : QObject(q), q(q)
{
}

NotificationClientPrivate::~NotificationClientPrivate()
{
    unregisterClient();
}

bool NotificationClientPrivate::init()
{
    if(m_registered) {
        return false;
    }
    m_registered = true;
    new NotificationClientAdaptor(this);
    QDBusConnection conn = QDBusConnection::sessionBus();

    if(!conn.registerObject(clientServicePath(), clientServiceInterface(), this)) {
        qWarning() << "Failed to register NotificationClient DBus object!" << conn.lastError();
        return false;
    }
    m_watcher = new QDBusServiceWatcher(QStringLiteral("org.freedesktop.Notifications"),
                                                           conn,
                                                           QDBusServiceWatcher::WatchForOwnerChange,
                                                           this);
    connect(m_watcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &NotificationClientPrivate::serviceChange);
    return registerClient();
}

bool NotificationClientPrivate::registerClient()
{
    QDBusConnection conn = QDBusConnection::sessionBus();
    if(!m_notificationsInterface) {
        m_notificationsInterface = new OrgFreedesktopNotificationsInterface(QStringLiteral("org.freedesktop.Notifications"),
                                                                            QStringLiteral("/org/freedesktop/Notifications"),
                                                                            conn);

        connect(m_notificationsInterface, &OrgFreedesktopNotificationsInterface::NotificationClosed,
                this, &NotificationClientPrivate::notificationClosed);
    }

    if(!m_serverInterface) {
        m_serverInterface = new QDBusInterface(QStringLiteral("org.freedesktop.Notifications"),
                                               QStringLiteral("/org/freedesktop/Notifications"),
                                               QStringLiteral("org.ukui.NotificationServer"),
                                               conn,
                                               this);
        if(!m_serverInterface->isValid()) {
            qWarning() << "Failed to create notification server interface! " << conn.lastError();
            return false;
        }
    }
    QDBusMessage reply = m_serverInterface->call(QStringLiteral("RegisterClient"));
    if (reply.type() == QDBusMessage::ErrorMessage || reply.type() == QDBusMessage::InvalidMessage) {
        qWarning() << "Failed to call RegisterClient!" << conn.lastError() << reply.type();
        return false;
    }
    return true;
}

void NotificationClientPrivate::unregisterClient()
{
    if(m_notificationsInterface) {
        disconnect(m_notificationsInterface, &OrgFreedesktopNotificationsInterface::NotificationClosed,
                   this, &NotificationClientPrivate::notificationClosed);
        delete m_notificationsInterface;
        m_notificationsInterface = nullptr;
    }
    QDBusConnection conn = QDBusConnection::sessionBus();
    if(m_serverInterface) {
        QDBusMessage reply = m_serverInterface->call(QStringLiteral("UnRegisterClient"));
        if (reply.type() == QDBusMessage::ErrorMessage || reply.type() == QDBusMessage::InvalidMessage) {
            qWarning() << "Failed to call UnRegisterClient!" << conn.lastError() << reply.type();
            return;
        }
    }
    conn.unregisterObject(clientServicePath(), QDBusConnection::UnregisterTree);

    m_registered = false;
}

QString NotificationClientPrivate::clientServicePath()
{
    return QStringLiteral("/NotificationClient");
}

QString NotificationClientPrivate::clientServiceInterface()
{
    return QStringLiteral("org.ukui.NotificationClient");
}

void NotificationClientPrivate::Notify(const QString &app_name, uint replaces_id, const QString &app_icon,
                                       const QString &summary, const QString &body, const QStringList &actions,
                                       const QVariantMap &hints, int timeout)
{
    PopupNotification notification(replaces_id);
    notification.setSummary(summary);
    notification.setBody(body);
    notification.setApplicationName(app_name);
    notification.setApplicationIconName(app_icon);
    notification.setActions(actions);
    notification.setTimeout(timeout);
    notification.setHints(hints);
    Q_EMIT q->newNotification(notification);
}

void NotificationClientPrivate::notificationClosed(uint id, uint reason)
{
    auto closeReason = NotificationCloseReason::CloseReason::Undefined;
    switch (reason) {
        case 1:
            closeReason = NotificationCloseReason::CloseReason::Expired;
            break;
        case 2:
            closeReason = NotificationCloseReason::CloseReason::DismissedByUser;
            break;
        case 3:
            closeReason = NotificationCloseReason::CloseReason::Revoked;
            break;
    }
    Q_EMIT q->notificationClosed(id, closeReason);
}

bool NotificationClientPrivate::closeNotification(uint id, NotificationCloseReason::CloseReason reason)
{
    if(!m_serverInterface) {
        return false;
    }
    QDBusMessage reply = m_serverInterface->callWithArgumentList(QDBus::BlockWithGui, QStringLiteral("CloseNotification"), {id, static_cast<uint32_t>(reason)});
    if (reply.type() == QDBusMessage::ErrorMessage) {
        qWarning() << "Failed to call CloseNotification!" << QDBusConnection::sessionBus().lastError();
        return false;
    }
    return true;
}

void NotificationClientPrivate::serviceChange(const QString &service, const QString &oldOwner, const QString &newOwner)
{
    qDebug() << "Notification Service" << service << "status change, old owner:" << oldOwner << "new:" << newOwner;
    if(oldOwner.isEmpty() && m_registered) {
        registerClient();
    }
}

bool NotificationClientPrivate::invokeAction(uint id, const QString &action_key)
{
    if(!m_serverInterface) {
        return false;
    }
    qDebug() << "invokeAction" << id << action_key;
    QDBusMessage reply = m_serverInterface->callWithArgumentList(QDBus::NoBlock,
                                                                        QStringLiteral("InvokeAction"),
                                                                        {id, action_key});
    if (reply.type() == QDBusMessage::ErrorMessage) {
        qWarning() << "Failed to call InvokedAction!" << QDBusConnection::sessionBus().lastError();
        return false;
    }
    return true;
}

NotificationClient::NotificationClient(QObject *parent) : QObject(parent), d(new NotificationClientPrivate(this))
{
    qRegisterMetaType<PopupNotification>("PopupNotification");
    qRegisterMetaType<UkuiNotification::PopupNotification>("const UkuiNotification::PopupNotification&");
    qRegisterMetaType<UkuiNotification::ActionList>("ActionList");
    qmlRegisterUncreatableType<UkuiNotification::NotificationCloseReason>("org.ukui.notification.client", 1, 0, "NotificationCloseReason", "");
}

NotificationClient::~NotificationClient() = default;

bool NotificationClient::registerClient()
{
    return d->init();
}

bool NotificationClient::closeNotification(uint id, NotificationCloseReason::CloseReason reason)
{
    return d->closeNotification(id, reason);
}

bool NotificationClient::invokeAction(uint id, const QString &action_key)
{
    return d->invokeAction(id, action_key);
}

void NotificationClient::unregisterClient()
{
    d->unregisterClient();
}
