/* BEGIN_COMMON_COPYRIGHT_HEADER
 * (c)LGPL2+
 * Copyright: Copyright (C) Copyright 2021 KylinSoft Co., Ltd.
 *            2012 Razor team
 * Authors:
 *   Christian Surlykke <christian@surlykke.dk>
 *
 * This program or library is free software; you can redistribute it
 * and/or modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA
 *
 * END_COMMON_COPYRIGHT_HEADER */
#include "powerprovider.h"

#include <QDBusInterface>
#include <QDBusMetaType>
#include <QDebug>

#include <sys/types.h>
#include <unistd.h>

#include <QMainWindow>
#include <QMessageBox>
#include <QPushButton>
#include <QObject>
#include <QDBusReply>
#include <QSharedPointer>
// #include "loginedusers.h"

#define LIGHTDM_SERVICE "org.freedesktop.DisplayManager"
#define LIGTHDM_INTERFACE "org.freedesktop.DisplayManager.Seat"

#define SYSTEMD_SERVICE "org.freedesktop.login1"
#define SYSTEMD_PATH "/org/freedesktop/login1"
#define SYSTEMD_INTERFACE "org.freedesktop.login1.Manager"

#define UKUI_SERVICE "org.gnome.SessionManager"
#define UKUI_PATH "/org/gnome/SessionManager"
#define UKUI_INTERFACE "org.gnome.SessionManager"

#define UKUI_SCREENSAVER_SERVICE "org.ukui.ScreenSaver"
#define UKUI_SCREENSAVER_PATH "/"
#define UKUI_SCREENSAVER_INTERFACE "org.ukui.ScreenSaver"

#define PROPERTIES_INTERFACE "org.freedesktop.DBus.Properties"

bool messageBoxCheck()
{
    QMessageBox msgBox;
    //    msgBox.setWindowTitle(QObject::tr("conform"));
    msgBox.setIcon(QMessageBox::Warning);
    msgBox.setWindowFlags(Qt::WindowStaysOnTopHint);
    //    msgBox.setModal(false);
    msgBox.setText(QObject::tr("some applications are running and they don't want you to do this."));
    QPushButton *stillButton = msgBox.addButton(QObject::tr("Still to do!"), QMessageBox::ActionRole);
    QPushButton *giveupButton = msgBox.addButton(QObject::tr("give up"), QMessageBox::RejectRole);

    //    QStringList usrlist = getLoginedUsers();
    //    QList<QString>::Iterator it = usrlist.begin(),itend = usrlist.end();
    //    for(;it != itend;it++){
    //        qDebug()<<*it;
    //    }

    msgBox.exec();

    if (msgBox.clickedButton() == stillButton) {
        qDebug() << "Still to do!";
        return true;
    } else if (msgBox.clickedButton() == giveupButton) {
        qDebug() << "give up";
        return false;
    } else {
        return false;
    }
}

static bool dbusCall(
    const QString &service,
    const QString &path,
    const QString &interface,
    const QDBusConnection &connection,
    const QString &method)
{
    QDBusInterface dbus(service, path, interface, connection);
    if (!dbus.isValid()) {
        qWarning() << "dbusCall: QDBusInterface is invalid" << service << path << interface << method;
        return false;
    }

    QDBusMessage msg = dbus.call(method);

    if (!msg.errorName().isEmpty()) {
        qWarning() << "Dbus error: " << msg;
    }

    return msg.arguments().isEmpty() || msg.arguments().constFirst().isNull() || msg.arguments().constFirst().toBool();
}

static bool dbusCallSystemd(
    const QString &service,
    const QString &path,
    const QString &interface,
    const QDBusConnection &connection,
    const QString &method,
    bool needBoolArg)
{
    QDBusInterface dbus(service, path, interface, connection);
    if (!dbus.isValid()) {
        qWarning() << "dbusCall: QDBusInterface is invalid" << service << path << interface << method;
        return false;
    }

    // QDBusMessage msg = dbus.call(method, needBoolArg ? QVariant(true) : QVariant());
    // 在华为相关的机器上，如果调用的method没有参数，但仍传入一个空值，会调用失败
    QDBusMessage msg;
    if (needBoolArg) {
        msg = dbus.call(method, QVariant(true));
    } else {
        msg = dbus.call(method);
    }

    if (!msg.errorName().isEmpty()) {
        qWarning() << "Debus error: " << msg;
    }

    if (msg.arguments().isEmpty() || msg.arguments().constFirst().isNull()) {
        return false;
    }

    QString response = msg.arguments().constFirst().toString();
    qDebug() << "systemd:" << method << "=" << response;

    // this need to be resolved:
    // while canReboot and canPoweroff return challenge,users click Reboot or shutdown,
    // we call logout ,then Reboot(true) and Poweroff(true) is called,
    // if user click cancel in the polkit messagebox,then Reboot or Poweroff is canceled,
    // but logout has been executed.
    return response == QLatin1String("yes") || response == QLatin1String("challenge");
}

bool dbusGetProperty(
    const QString &service,
    const QString &path,
    const QString &interface,
    const QDBusConnection &connection,
    const QString &property)
{
    QDBusInterface dbus(service, path, interface, connection);
    if (!dbus.isValid()) {
        qWarning() << "dbusGetProperty: QDBusinterface is invalid" << service << path << interface << property;
        return false;
    }

    //    QDBusMessage msg = dbus.call("SwitchToGreeter");//QLatin1String("Get"), dbus.interface(),property

    //    if (!msg.errorName().isEmpty()) {
    //        qWarning() << "Dbus error: " << msg;
    //    }

    //    return !msg.arguments().isEmpty() &&
    //            msg.arguments().constFirst().value<QDBusVariant>().variant().toBool();

    QVariant canswitch = dbus.property("CanSwitch");
    qDebug() << property << "=" << canswitch.toString();
    return canswitch.toBool();
}

PowerProvider::PowerProvider(QObject *parent) : QObject(parent) {}

PowerProvider::~PowerProvider() {}

/************************************************
  SystemdProvider

  http://www.freedesktop.org/wiki/Software/systemd/logind
 ************************************************/

SystemdProvider::SystemdProvider(QObject *parent) : PowerProvider(parent) {}

SystemdProvider::~SystemdProvider() {}

bool SystemdProvider::canSwitchUser() const
{
    QString property = "CanSwitch";
    QString xdg_seat_path = qgetenv("XDG_SEAT_PATH");
    if (xdg_seat_path.isEmpty()) {
        qWarning() << "XDG_SEAT_PATH dose not exist , make canSwitchUser return false";
        return false;
    }
    return dbusGetProperty(
        QLatin1String(LIGHTDM_SERVICE),
        xdg_seat_path,
        QLatin1String(LIGTHDM_INTERFACE),
        QDBusConnection::systemBus(),
        property);
}

bool SystemdProvider::canAction(UkuiPower::Action action) const
{
    QString command;

    switch (action) {
        case UkuiPower::PowerSwitchUser:
            return canSwitchUser();
        case UkuiPower::PowerReboot:
            command = QLatin1String("CanReboot");
            break;

        case UkuiPower::PowerShutdown:
            command = QLatin1String("CanPowerOff");
            break;

        case UkuiPower::PowerSuspend:
            command = QLatin1String("CanSuspend");
            break;

        case UkuiPower::PowerHibernate:
            command = QLatin1String("CanHibernate");
            break;

        default:
            return false;
    }

    // canAction should be always silent because it can freeze
    // g_main_context_iteration Qt event loop in QMessageBox
    // on panel startup if there is no DBUS running.
    return dbusCallSystemd(
        QLatin1String(SYSTEMD_SERVICE),
        QLatin1String(SYSTEMD_PATH),
        QLatin1String(SYSTEMD_INTERFACE),
        QDBusConnection::systemBus(),
        command,
        false);
}

bool SystemdProvider::doSwitchUser()
{
    bool isinhibited = false;
    QDBusInterface *interface = new QDBusInterface(
        "org.gnome.SessionManager",
        "/org/gnome/SessionManager",
        "org.gnome.SessionManager",
        QDBusConnection::sessionBus());
    quint32 inhibit_switchuser = 2;
    QDBusReply<bool> reply = interface->call("IsInhibited", inhibit_switchuser);
    if (reply.isValid()) {
        // use the returned value
        qDebug() << "Is inhibit by someone: " << reply.value();
        isinhibited = reply.value();
    } else {
        qDebug() << reply.value();
    }

    if (isinhibited == true) {
        isinhibited = !messageBoxCheck();
    }

    if (isinhibited == false) {

        //        QDBusInterface dbus("org.ukui.KWin", "/Compositor", "org.ukui.kwin.Compositing",
        //        QDBusConnection::sessionBus());

        //        if (!dbus.isValid()) {
        //            qWarning() << "dbusCall: QDBusInterface is invalid";
        //            return false;
        //        }

        //        dbus.call("suspend");
        bool useScreensaver = dbusCall(
            QLatin1String(UKUI_SCREENSAVER_SERVICE),
            UKUI_SCREENSAVER_PATH,
            QLatin1String(UKUI_SCREENSAVER_INTERFACE),
            QDBusConnection::sessionBus(),
            "CheckAppVersion");
        if (useScreensaver) {
            return dbusCall(
                QLatin1String(UKUI_SCREENSAVER_SERVICE),
                UKUI_SCREENSAVER_PATH,
                QLatin1String(UKUI_SCREENSAVER_INTERFACE),
                QDBusConnection::sessionBus(),
                "SwitchUser");
        }
        QString command = "SwitchToGreeter";
        QString xdg_seat_path = qgetenv("XDG_SEAT_PATH");
        return dbusCall(
            QLatin1String(LIGHTDM_SERVICE),
            xdg_seat_path,
            QLatin1String(LIGTHDM_INTERFACE),
            QDBusConnection::systemBus(),
            command);
    }
    return false;
}

bool SystemdProvider::doAction(UkuiPower::Action action)
{

    qDebug() << "SystemdProvider::doAction:" << action;
    QString command;

    switch (action) {
        case UkuiPower::PowerSwitchUser:
            return doSwitchUser();
        case UkuiPower::PowerReboot:
            command = QLatin1String("Reboot");
            break;
        case UkuiPower::PowerShutdown:
            command = QLatin1String("PowerOff");
            break;
        case UkuiPower::PowerSuspend:
            command = QLatin1String("Suspend");
            break;
        case UkuiPower::PowerHibernate:
            command = QLatin1String("Hibernate");
            break;
        default:
            return false;
    }

    return dbusCallSystemd(
        QLatin1String(SYSTEMD_SERVICE),
        QLatin1String(SYSTEMD_PATH),
        QLatin1String(SYSTEMD_INTERFACE),
        QDBusConnection::systemBus(),
        command,
        true);
}

UKUIProvider::UKUIProvider(QObject *parent) : PowerProvider(parent) {}

UKUIProvider::~UKUIProvider() {}

bool UKUIProvider::canAction(UkuiPower::Action action) const
{
    // 这里是调用ukui-session注册的d-bus检测是否有inhibitor存在
    bool isinhibited = false;
    QDBusInterface *interface = new QDBusInterface(
        "org.gnome.SessionManager",
        "/org/gnome/SessionManager",
        "org.gnome.SessionManager",
        QDBusConnection::sessionBus());
    quint32 inhibit_logout = 1;
    QDBusReply<bool> reply = interface->call("IsInhibited", inhibit_logout);
    if (reply.isValid()) {
        // use the returned value
        qDebug() << "Is inhibit by someone: " << reply.value();
        isinhibited = reply.value();
    } else {
        qDebug() << reply.value();
    }

    if (isinhibited == true) {
        isinhibited = !messageBoxCheck();
    }

    return !isinhibited;
}

bool UKUIProvider::doAction(UkuiPower::Action action)
{
    QString command;
    switch (action) {
        case UkuiPower::PowerLogout:
            command = QLatin1String("logout");
            break;
        case UkuiPower::PowerReboot:
            command = QLatin1String("reboot");
            break;
        case UkuiPower::PowerShutdown:
            command = QLatin1String("powerOff");
            break;
        default:
            return false;
    }

    qDebug() << "ukuipowerprovider call D-Bus session";
    return dbusCall(
        QLatin1String(UKUI_SERVICE),
        QLatin1String(UKUI_PATH),
        QLatin1String(UKUI_INTERFACE),
        QDBusConnection::sessionBus(),
        command);
}

bool UKUIProvider::doLSAction(UkuiPower::Action action, QString param /* = ""*/)
{
    qDebug() << "call LockScreen D-Bus";
    QString SS_DBUS_SERVICE = "org.ukui.ScreenSaver";
    QString SS_DBUS_PATH = "/";
    QString SS_DBUS_INTERFACE = "org.ukui.ScreenSaver";
    QString displayNum = QString(qgetenv("DISPLAY")).replace(":", "").replace(".", "_");
    QString sessionDbus = QString("%1%2").arg(SS_DBUS_SERVICE).arg(displayNum);
    QSharedPointer<QDBusInterface> screensaverInterface(
        new QDBusInterface(sessionDbus, SS_DBUS_PATH, SS_DBUS_INTERFACE));

    if (!screensaverInterface->isValid()) {
        screensaverInterface
            = QSharedPointer<QDBusInterface>(new QDBusInterface(SS_DBUS_SERVICE, SS_DBUS_PATH, SS_DBUS_INTERFACE));
    }

    bool m_flag = false;
    QDBusReply<bool> reply = screensaverInterface->call("CheckAppVersion");
    if (reply.isValid()) {
        // use the returned value
        m_flag = reply.value();
        qDebug() << "CheckAppVersion is " << m_flag;
    } else {
        qDebug() << "CheckAppVersion function is not existed";
    }
    if (m_flag) {
        switch (action) {
            case UkuiPower::PowerLogout:
                screensaverInterface->asyncCall("Logout");
                break;
            case UkuiPower::PowerReboot:
                screensaverInterface->asyncCall("Reboot");
                break;
            case UkuiPower::PowerShutdown:
                screensaverInterface->asyncCall("PowerOff");
                break;
            case UkuiPower::PowerHibernate:
                screensaverInterface->asyncCall("Hibernate");
                break;
            case UkuiPower::PowerSuspend:
                screensaverInterface->asyncCall("Suspend");
                break;
            case UkuiPower::PowerSwitchUser:
                screensaverInterface->asyncCall("SwitchUser");
                break;
            case UkuiPower::PowerSwitchToUser:
                screensaverInterface->asyncCall("SwitchToUser", param);
                break;
            default:
                return false;
        }
        return true;
    } else {
        return false;
    }
}
