/*
 * Copyright (C) 2023, KylinSoft Co., Ltd.
 *               2014  Hong Jen Yee (PCMan) <pcman.tw@gmail.com>.
 *
 * This 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
 *
 */
#include "sessionmanagercontext.h"
#include "sessiondbusadaptor.h"
#include <QDBusMetaType>
#include <QDBusAbstractAdaptor>
#include <QDebug>

SessionManagerDBusContext::SessionManagerDBusContext(ModuleManager *manager, QObject *parent)
    : QObject(parent)
    , mManager(manager)
    , minhibit(new usminhibit())
    , m_systemdProvider(new SystemdProvider())
    , m_ukuiProvider(new UKUIProvider())
    , m_serviceWatcher(new QDBusServiceWatcher(this))
{
    new SessionDBusAdaptor(this);

    connect(mManager, &ModuleManager::moduleStateChanged, this , &SessionManagerDBusContext::moduleStateChanged);
    connect(mManager, &ModuleManager::finished, this, &SessionManagerDBusContext::emitPrepareForPhase2);
    connect(minhibit, &usminhibit::inhibitRemove, this, &SessionManagerDBusContext::simulateUserActivity);
    connect(minhibit, &usminhibit::inhibitAdd, this, &SessionManagerDBusContext::simulateUserActivity);

    m_serviceWatcher->setConnection(QDBusConnection::sessionBus());
    m_serviceWatcher->setWatchMode(QDBusServiceWatcher::WatchForUnregistration);
    connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &SessionManagerDBusContext::on_serviceUnregistered);

    //注册类型
    qDBusRegisterMetaType<InhibitInfo::InhibitorInfo>();
    qDBusRegisterMetaType<QVector<InhibitInfo::InhibitorInfo>>();
    qDBusRegisterMetaType<SystemdInhibitor::Inhibitor>();
}

SessionManagerDBusContext::~SessionManagerDBusContext()
{
}


Q_NOREPLY void SessionManagerDBusContext::startupfinished(const QString &appName ,const QString &string)
{
    return mManager->startupfinished(appName, string);
}

bool SessionManagerDBusContext::canSwitch()
{
    if (Ukuilockinfo::getCfgValue(Ukuilockinfo::buttonType::switchuserBtn) && m_systemdProvider->canAction(UkuiPower::PowerSwitchUser)) {
        return true;
    }

    return false;
}

bool SessionManagerDBusContext::canLockscreen()
{
    if (Ukuilockinfo::getCfgValue(Ukuilockinfo::buttonType::lockscreenBtn)) {
        return true;
    }

    return false;
}

bool SessionManagerDBusContext::canHibernate()
{
    if (Ukuilockinfo::getCfgValue(Ukuilockinfo::buttonType::hibernateBtn) && m_systemdProvider->canAction(UkuiPower::PowerHibernate)) {
        return true;
    }

    return false;
}

bool SessionManagerDBusContext::canSuspend()
{
    if (Ukuilockinfo::getCfgValue(Ukuilockinfo::buttonType::suspendBtn) && m_systemdProvider->canAction(UkuiPower::PowerSuspend)) {
        return true;
    }

    return false;
}


bool SessionManagerDBusContext::canLogout()
{
    //暂时都返回true
    if (Ukuilockinfo::getCfgValue(Ukuilockinfo::buttonType::logoutBtn)) {
        return true;
    }

    return false;
}

bool SessionManagerDBusContext::canReboot()
{
    if (Ukuilockinfo::getCfgValue(Ukuilockinfo::buttonType::rebootBtn) && m_systemdProvider->canAction(UkuiPower::PowerReboot)) {
        return true;
    }

    return false;
}

bool SessionManagerDBusContext::canPowerOff()
{
    if (Ukuilockinfo::getCfgValue(Ukuilockinfo::buttonType::shutdownBtn) && m_systemdProvider->canAction(UkuiPower::PowerShutdown)) {
        return true;
    }

    return false;
}

Q_NOREPLY void SessionManagerDBusContext::switchUser()
{
    m_systemdProvider->doAction(UkuiPower::PowerSwitchUser);
}

Q_NOREPLY void SessionManagerDBusContext::hibernate()
{
    m_systemdProvider->doAction(UkuiPower::PowerHibernate);
}

Q_NOREPLY void SessionManagerDBusContext::suspend()
{
    m_systemdProvider->doAction(UkuiPower::PowerSuspend);
}

Q_NOREPLY void SessionManagerDBusContext::logout()
{
    qDebug() << "ukui-session logout inteface called";
    if (mManager->getIsShutingDown()) {
        qDebug() << "already performing logout";
        return;
    }

    mManager->setIsShutingDown(true);

    QDBusInterface ukuismserverFace("org.ukui.ukuismserver",
                                    "/UKUISMServer",
                                    "org.ukui.ukuismserver",
                                    QDBusConnection::sessionBus());

    if (!ukuismserverFace.isValid()) {
        qDebug() << "ukuisminterface not valid";
        return;
    }

//    QDBusReply<bool> isCloseSession = ukuismserverFace.call("isCloseSession");
//    if (isCloseSession.value()) {
//        qDebug() << "already performing close session";
//        return;
//    }

//    OrgKdeKSMServerInterfaceInterface ksmserverIface(QStringLiteral("org.kde.ksmserver"), QStringLiteral("/KSMServer"), QDBusConnection::sessionBus());
    //QDBusAbstractInterface有超时机制，默认是25秒，25秒后没有返回值，会给出一个带有错误信息的返回值，供调用方判断做出下一步动作，也正是因此KDE桌面不会在某个应用的提醒保存页面一直
    //停留，而是会继续往下执行注销操作，可以通过setTimerout设置超时时间，实验证明确实如此
    ukuismserverFace.setTimeout(15000);
    QList<QVariant> argumentList;
    QDBusPendingReply<bool> closeSessionReply = ukuismserverFace.asyncCallWithArgumentList(QStringLiteral("closeSession"), argumentList);
    auto watcher = new QDBusPendingCallWatcher(closeSessionReply, this);
    connect(watcher, &QDBusPendingCallWatcher::finished, this, [closeSessionReply, watcher, this]() {
        watcher->deleteLater();
        if (closeSessionReply.isError()) {
            qDebug() << "ksmserver failed to complete close session";
        }
        if (closeSessionReply.value()) {
            qDebug() << "ukuismserver close session completed";
            logoutComplete();
        } else {
            mManager->setIsShutingDown(false);
            qDebug() << "logout cancelled";
        }
    });
}

Q_NOREPLY void SessionManagerDBusContext::reboot()
{
    qDebug() << "ukui-session reboot inteface called";
    if (mManager->getIsShutingDown()) {
        qDebug() << "already performing reboot";
        return;
    }

    mManager->setIsShutingDown(true);

    QDBusInterface ukuismserverFace("org.ukui.ukuismserver",
                                    "/UKUISMServer",
                                    "org.ukui.ukuismserver",
                                    QDBusConnection::sessionBus());

    if (!ukuismserverFace.isValid()) {
        qDebug() << "ukuisminterface not valid";
        return;
    }

//    OrgKdeKSMServerInterfaceInterface ksmserverIface(QStringLiteral("org.kde.ksmserver"), QStringLiteral("/KSMServer"), QDBusConnection::sessionBus());
    //QDBusAbstractInterface有超时机制，默认是25秒，25秒后没有返回值，会给出一个带有错误信息的返回值，供调用方判断做出下一步动作，也正是因此KDE桌面不会在某个应用的提醒保存页面一直
    //停留，而是会继续往下执行注销操作，可以通过setTimerout设置超时时间，实验证明确实如此
    ukuismserverFace.setTimeout(15000);
    QList<QVariant> argumentList;
    QDBusPendingReply<bool> closeSessionReply = ukuismserverFace.asyncCallWithArgumentList(QStringLiteral("closeSession"), argumentList);
    auto watcher = new QDBusPendingCallWatcher(closeSessionReply, this);
    connect(watcher, &QDBusPendingCallWatcher::finished, this, [closeSessionReply, watcher, this]() {
        watcher->deleteLater();
        if (closeSessionReply.isError()) {
            qDebug() << "ksmserver failed to complete close session";
        }
        if (closeSessionReply.value()) {
            qDebug() << "ukuismserver close session completed";
            rebootComplete();
        } else {
            mManager->setIsShutingDown(false);
            qDebug() << "logout cancelled";
        }
    });
}

Q_NOREPLY void SessionManagerDBusContext::powerOff()
{
    qDebug() << "ukui-session reboot inteface called";
    if (mManager->getIsShutingDown()) {
        qDebug() << "already performing poweroff";
        return;
    }

    mManager->setIsShutingDown(true);

    QDBusInterface ukuismserverFace("org.ukui.ukuismserver",
                                    "/UKUISMServer",
                                    "org.ukui.ukuismserver",
                                    QDBusConnection::sessionBus());

    if (!ukuismserverFace.isValid()) {
        qDebug() << "ukuisminterface not valid";
        return;
    }

//    OrgKdeKSMServerInterfaceInterface ksmserverIface(QStringLiteral("org.kde.ksmserver"), QStringLiteral("/KSMServer"), QDBusConnection::sessionBus());
    //QDBusAbstractInterface有超时机制，默认是25秒，25秒后没有返回值，会给出一个带有错误信息的返回值，供调用方判断做出下一步动作，也正是因此KDE桌面不会在某个应用的提醒保存页面一直
    //停留，而是会继续往下执行注销操作，可以通过setTimerout设置超时时间，实验证明确实如此
    ukuismserverFace.setTimeout(15000);
    QList<QVariant> argumentList;
    QDBusPendingReply<bool> closeSessionReply = ukuismserverFace.asyncCallWithArgumentList(QStringLiteral("closeSession"), argumentList);
    auto watcher = new QDBusPendingCallWatcher(closeSessionReply, this);
    connect(watcher, &QDBusPendingCallWatcher::finished, this, [closeSessionReply, watcher, this]() {
        watcher->deleteLater();
        if (closeSessionReply.isError()) {
            qDebug() << "ksmserver failed to complete close session";
        }
        if (closeSessionReply.value()) {
            qDebug() << "ukuismserver close session completed";
            shutdownComplete();
        } else {
            mManager->setIsShutingDown(false);
            qDebug() << "logout cancelled";
        }
    });
}

Q_NOREPLY void SessionManagerDBusContext::startModule(const QString& name)
{
    mManager->startProcess(name, true);
}

Q_NOREPLY void SessionManagerDBusContext::stopModule(const QString& name)
{
    mManager->stopProcess(name);
}

bool SessionManagerDBusContext::startApp(const QString &name, const QStringList &args)
{
    return mManager->startProcess(name, args);
}

uint SessionManagerDBusContext::Inhibit(QString app_id, quint32 toplevel_xid, QString reason, quint32 flags)
{
    uint result = minhibit->addInhibit(app_id, toplevel_xid, reason, flags);
    if (result < 0) {
        return 0;
    }

    QString dbusService = message().service();
    qDebug() << "SessionManagerDBusContext message().service():" << dbusService;

    QStringList keys = m_hashInhibitionServices.keys();
    if (!keys.contains(dbusService)) {
        QList<quint32> cookies;
        cookies.append(result);
        m_hashInhibitionServices.insert(dbusService, cookies);
        m_serviceWatcher->addWatchedService(dbusService);
        qDebug() << "m_serviceWatcher services:..." << m_serviceWatcher->watchedServices();
    } else {
        for (auto iter = m_hashInhibitionServices.begin(); iter != m_hashInhibitionServices.end(); iter++) {
            qDebug() << "Inhibit iter.key()" << iter.key() << dbusService;

            if (iter.key() == dbusService) {
                QList<quint32> cookies = iter.value();
                if (!cookies.contains(result)) {
                    cookies.append(result);
                    m_hashInhibitionServices[dbusService] = cookies;
                    break;
                }
            }
        }
    }
    qDebug() << "Inhibit m_hashInhibitionServices..." << m_hashInhibitionServices;
    emit this->inhibitadded(flags);
    return result;
}

Q_NOREPLY void SessionManagerDBusContext::Uninhibit(uint cookie)
{
    uint result = minhibit->unInhibit(cookie);
    if (result > 0) {
        emit inhibitremove(result);
    }
    for (auto iter = m_hashInhibitionServices.begin(); iter != m_hashInhibitionServices.end(); iter++) {
        QList<quint32> cookies = iter.value();
        for (int i = 0; i < cookies.length(); i++) {
            if (cookie == cookies[i]) {
                qDebug() << "Uninhibit cookies:" << cookies << "cookie:" << cookie;
                if (cookies.length() > 1) {
                    cookies.removeAt(i);
                    m_hashInhibitionServices[iter.key()] = cookies;
                } else {
                    m_serviceWatcher->removeWatchedService(iter.key());
                    m_hashInhibitionServices.remove(iter.key());
                    qDebug() << "Uninhibit m_hashInhibitionServices...:" << m_hashInhibitionServices;
                }
                qDebug() << "Uninhibit m_hashInhibitionServices:" << m_hashInhibitionServices;
                return;
            }
        }
    }
}

QStringList SessionManagerDBusContext::GetInhibitors()
{
    return minhibit->getInhibitor();
}

QVector<InhibitInfo::InhibitorInfo> SessionManagerDBusContext::ListInhibitor(QString type)
{
    QVector<InhibitInfo::InhibitorInfo> result;

    if (type == QString("logout")) {
        result = Ukuilockinfo::listInhibitorInfo(Ukuilockinfo::InhibitorType::logout);
    } else if (type == QString("sleep")) {
        result = Ukuilockinfo::listInhibitorInfo(Ukuilockinfo::InhibitorType::suspend);
    } else if (type == QString("shutdown")) {
        result = Ukuilockinfo::listInhibitorInfo(Ukuilockinfo::InhibitorType::shutdown);
    }

    return result;
}

bool SessionManagerDBusContext::IsSessionRunning()
{
    QString xdg_session_desktop = qgetenv("XDG_SESSION_DESKTOP").toLower();
    if (xdg_session_desktop == "ukui" || xdg_session_desktop == "ukui-wayland") {
        return true;
    }
    return false;
}

QString SessionManagerDBusContext::GetSessionName()
{
    QString xdg_session_desktop = qgetenv("XDG_SESSION_DESKTOP").toLower();
    if (xdg_session_desktop == "ukui" || xdg_session_desktop == "ukui-wayland") {
        return "ukui-session";
    }

    return "error";
}

bool SessionManagerDBusContext::IsInhibited(quint32 flags)
{
    return minhibit->isInhibited(flags);
}

void SessionManagerDBusContext::setSessionEnv(const QString &key, const QString &value)
{
    qDebug() << "set env " << key << " as " << value;
    qputenv(key.toLatin1(), value.toLatin1());
}

Q_NOREPLY void SessionManagerDBusContext::emitStartLogout()
{
    qDebug() << "emit  StartLogout";
    emit StartLogout();
}

Q_NOREPLY void SessionManagerDBusContext::emitPrepareForSwitchuser()
{
    qDebug() << "emit  PrepareForSwitchuser";
    emit PrepareForSwitchuser();
}

Q_NOREPLY void SessionManagerDBusContext::emitPrepareForPhase2()
{
    qDebug() << "emit  PrepareForPhase2";
    emit PrepareForPhase2();
}

Q_NOREPLY void SessionManagerDBusContext::simulateUserActivity()
{
    qDebug() << "simulate User Activity";
    KIdleTime::instance()->simulateUserActivity();
}

void SessionManagerDBusContext::on_serviceUnregistered(const QString &serviceName)
{
    qDebug() << "onServiceUnregistered..." << serviceName;

    for (auto iter = m_hashInhibitionServices.begin(); iter != m_hashInhibitionServices.end(); iter++) {
        if (iter.key() == serviceName) {
            QList<quint32> cookies = iter.value();
            for (auto i = 0; i < cookies.length(); i++) {
                quint32 cookie = cookies[i];
                qDebug() << "on_serviceUnregistered cookie:" << cookie;
                Uninhibit(cookie);
            }
            return;
        }
    }
    qDebug() << "on_serviceUnregistered unfind serviceName:" << serviceName;
}

void SessionManagerDBusContext::logoutComplete()
{
    //注销前执行指定目录脚本
    QStringList list = listFileList();
    if (list.isEmpty()) {
        qDebug() << "logouting file list is empty or dir is not exits.";
    } else {
        execPro(list);
    }

    //调用systemd的注销
    QDBusInterface face("org.freedesktop.login1",
                        "/org/freedesktop/login1/session/self",
                        "org.freedesktop.login1.Session",
                        QDBusConnection::systemBus());

    qDebug() << "call sytemd login1 teminate session";
    face.call("Terminate");
}

void SessionManagerDBusContext::rebootComplete()
{
    //注销前执行指定目录脚本
    QStringList list = listFileList();
    if (list.isEmpty()) {
        qDebug() << "logouting file list is empty or dir is not exits.";
    } else {
        execPro(list);
    }

    //调用systemd的reboot
    qDebug() << "complete xsmp logout, call Reboot";
    this->m_systemdProvider->doAction(UkuiPower::PowerReboot);
}

void SessionManagerDBusContext::shutdownComplete()
{
    //注销前执行指定目录脚本
    QStringList list = listFileList();
    if (list.isEmpty()) {
        qDebug() << "logouting file list is empty or dir is not exits.";
    } else {
        execPro(list);
    }

    //调用systemd的shutdown
    qDebug() << "complete xsmp logout, call powerOff";
    this->m_systemdProvider->doAction(UkuiPower::PowerShutdown);
}

QStringList SessionManagerDBusContext::listFileList()
{
    QDir dir("/etc/ukui/ukui-session/logout/");
    if (!dir.exists()) {
          qWarning("Cannot find the example directory");
          return QStringList();
    }
    dir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks);
    dir.setSorting(QDir::Size | QDir::Reversed);

    QFileInfoList list = dir.entryInfoList();
    QStringList filelist;

    for (int i = list.size() - 1 ; i >= 0 ; --i) {
        QFileInfo fileInfo = list.at(i);
        filelist<<fileInfo.fileName();
    }
    return filelist;
}

void SessionManagerDBusContext::execPro(QStringList proList)
{
    for (int i = 0 ; i < proList.size(); ++i){
        qDebug() << "script name is " << proList.at(i);
        QString pro = "/etc/ukui/ukui-session/logout/" + proList.at(i);
        QProcess::startDetached(pro ,QStringList());
    }
}
