#include "deepinabilitymanager.h"
#include "deepinlauncher.h"
#include "deepincalendar.h"
#include "deepinnotification.h"
#include "deepincontrolcenter.h"
#include "osinfo.h"

#include <DDesktopEntry>
#include <DSysInfo>

#include <QApplication>
#include <QDBusVariant>
#include <QDateTime>
#include <QDesktopServices>
#include <QDBusPendingReply>
#include <QDBusReply>
#include <QFile>
#include <QFileInfo>
#include <QJsonDocument>
#include <QJsonArray>
#include <QUrl>
#include <QProcess>
#include <QThread>
#include <QDebug>

#include <math.h>

DCORE_USE_NAMESPACE

static QString formatCap(qulonglong cap, const int size = 1024, quint8 precision = 1)
{
    QStringList type { " B", " KB", " MB", " GB", " TB" };

    qulonglong lc = cap;
    double dc = cap;
    double ds = size;

    for (int p = 0; p < type.size(); ++p) {
        if (cap < pow(size, p + 1) || p == (type.size() - 1)) {
            if (!precision) {
                //! 内存总大小只能是整数所以当内存大小有小数时，就需要向上取整
                int mem = static_cast<int>(ceil(lc / pow(size, p)));
#ifdef __sw_64__
                return QString::number(mem) + type[p];
#else
                //! 如果向上取整后内存大小不为偶数，就向下取整
                if (mem % 2 > 0)
                    mem = static_cast<int>(floor(lc / pow(size, p)));
                return QString::number(mem) + type[p];
#endif
            }

            return QString::number(dc / pow(ds, p), 'f', precision) + type[p];
        }
    }

    return "";
}

UOSAbilityManager::UOSAbilityManager(QObject *parent)
    : QObject{parent}
{
    loadErrMap();
    //Get application paths
    initDesktopPaths();
    //Need load before init proxys
    loadApp2Desktop();

    initUosProxys();
}

OSCallContext UOSAbilityManager::doBluetoothConfig(bool on)
{
    QStringList adpters;
    {
        QVariant adptersResult;
        if (!osCtrCallDbus(osCallDbusBtService, osCallDbusBtPath, osCallDbusBtInterface,
                           QString("GetAdapters"), adptersResult)) {
            return ctxByError(OSCallContext::NonError);
        }
        QJsonDocument doc = QJsonDocument::fromJson(adptersResult.toString().toUtf8());
        QJsonArray arr = doc.array();
        for (int index = 0; index < arr.size(); index++)
            adpters.append(arr[index].toObject()["Path"].toString());
    }

    if (adpters.isEmpty()) {
        qInfo() << "there is no bluetooth adpater.";
        return ctxByError(OSCallContext::NonService);
    }

    for (auto adpter : adpters) {
        QDBusObjectPath dPath(adpter);
        QVariantList args;
        args << QVariant::fromValue(dPath)
             << QVariant::fromValue(on);
        osCtrCallDbusNoResult(osCallDbusBtService, osCallDbusBtPath, osCallDbusBtInterface, QString("SetAdapterPowered"), args);
    }

    if (on) {
        m_uosControlCenterProxy->ShowPage("bluetooth");
    } else {
        osCtrCallDbusNoResult(osCallDbusBtService, osCallDbusBtPath, osCallDbusBtInterface, QString("ClearUnpairedDevice"));
    }

    return ctxByError(OSCallContext::NonError);
}

OSCallContext UOSAbilityManager::doScreenMirroring(bool state)
{
    Q_UNUSED(state);

    // display:Casting
    // com.deepin.dde.ControlCenter
    // /com/deepin/dde/ControlCenter
    // com.deepin.dde.ControlCenter.ShowPage 'display' 'Casting'

    OSCallContext ctx;
    int errCode;

#ifdef COMPILE_ON_V23
    errCode = m_uosControlCenterProxy->ShowPage("display/Casting");
#else
    errCode = m_uosControlCenterProxy->ShowPage("display", "Casting");
#endif

    ctx.error = OSCallContext::CallError(errCode);
    ctx.errorInfo = m_errMap[ctx.error];
    return ctx;
}

/**
 * @brief UOSAbilityManager::doNoDisturb
 *   Ref:
 *   dde-control-center/
 *   src/frame/window/modules/notification/systemnotifywidget.cpp
 *   typedef enum {
 *        DNDMODE,  //bool
 *        LOCKSCREENOPENDNDMODE,    //bool
 *        OPENBYTIMEINTERVAL,   //bool
 *        STARTTIME,    //time.toString("hh:mm")
 *       ENDTIME        //time.toString("hh:mm")
 *    } SystemConfigurationItem;
 * @return
 */
OSCallContext UOSAbilityManager::doNoDisturb(bool state)
{
    OSCallContext ctx;

    typedef enum {
        DNDMODE,  //bool
        LOCKSCREENOPENDNDMODE,//bool
        OPENBYTIMEINTERVAL,   //bool
        STARTTIME,    //time.toString("hh:mm")
        ENDTIME        //time.toString("hh:mm")
    } SystemConfigurationItem;

    int errCode = m_uosNotificationProxy->SetSystemInfo(
                      SystemConfigurationItem::DNDMODE, state);

    ctx.error = OSCallContext::CallError(errCode);
    ctx.errorInfo = m_errMap[ctx.error];

    return ctx;
}

OSCallContext UOSAbilityManager::doWallpaperSwitch()
{    
    auto covertUrlToLocalPath = [](const QString &url) {
        if (url.startsWith("/"))
            return url;
        else
            return QUrl(QUrl::fromPercentEncoding(url.toUtf8())).toLocalFile();
    };

    QVariantList listArg {QVariant::fromValue(QString("background"))};
    QVariant list;
    if (!osCtrCallDbus(osCallDbusAppearanceService, osCallDbusAppearancePath, osCallDbusAppearanceInterface,
                       QString("List"), list, listArg)) {
        return ctxByError(OSCallContext::NonService);
    }

    QVariant screen;
    if (!propertiesGet(osCallDbusDisplayService, osCallDbusDisplayPath, osCallDbusDisplayInterface, QString("Primary"), screen)) {
        return ctxByError(OSCallContext::NonService);
    }

    QVariantList resultArg {screen};
    QVariant result;
    if (!osCtrCallDbus(osCallDbusWmService, osCallDbusWmPath, osCallDbusWmInterface,
                       "GetCurrentWorkspaceBackgroundForMonitor", result, resultArg)) {
        return ctxByError(OSCallContext::NonService);
    }
    QString current = covertUrlToLocalPath(result.toString());

    QList<QString> wallpapers;
    QJsonDocument doc = QJsonDocument::fromJson(list.toString().toUtf8());
    if (doc.isArray()) {
        QJsonArray arr = doc.array();
        foreach (QJsonValue val, arr) {
            QJsonObject obj = val.toObject();
            QString id = covertUrlToLocalPath(obj["Id"].toString());
            wallpapers.append(id);
        }
    }

    if (wallpapers.isEmpty() || screen.isNull()) {
        return ctxByError(OSCallContext::NonService);
    } else {
        int idx = wallpapers.indexOf(current);
        if (idx < 0 || idx >= wallpapers.size() - 1)
            idx = 0;
        else
            idx++;

        QString next = wallpapers.at(idx);
        QList<QVariant> argumentList;
        argumentList << screen << QVariant::fromValue(next);
        if (!osCtrCallDbusNoResult(osCallDbusAppearanceService, osCallDbusAppearancePath, osCallDbusAppearanceInterface,
                           "SetMonitorBackground", argumentList)) {
            return ctxByError(OSCallContext::NonService);
        }
    }

    return ctxByError(OSCallContext::NonError);
}

OSCallContext UOSAbilityManager::doDesktopOrganize(bool state)
{
    OSCallContext ctx;
    ctx.error = OSCallContext::NonError;
    ctx.errorInfo = m_errMap[ctx.error];

    QProcess process;
    QStringList args;
    args << "--set" << "-a" << "org.deepin.dde.file-manager"
         << "-r" << "org.deepin.dde.file-manager.desktop.organizer"
         << "-k" << "enableOrganizer" << "-v";
    int exitCode = 0;
    if (state) {
        // 先开
        {
            args << QString("1");
            process.start("dde-dconfig", args);
            process.waitForFinished();
            exitCode = process.exitCode();
        }

        QStringList queryArgs;
        queryArgs << "--get" << "-a" << "org.deepin.dde.file-manager"
             << "-r" << "org.deepin.dde.file-manager.desktop.organizer"
             << "-k" << "organizeAction";

        QStringList setArgs;
        setArgs << "--set" << "-a" << "org.deepin.dde.file-manager"
             << "-r" << "org.deepin.dde.file-manager.desktop.organizer"
             << "-k" << "organizeAction" << "-v";
        if (exitCode == 0) {
            //查询集合action
            process.start("dde-dconfig", queryArgs);
            process.waitForFinished();
            exitCode = process.exitCode();
            if (exitCode == 0) {
               QString out = QString::fromUtf8(process.readAllStandardOutput());
               out = out.replace("\n", "");
               out = out.replace("\"","");
               int action = out.toInt();
               if (action == 0) {
                   QStringList setArgsTmp = setArgs;
                   setArgsTmp << QString("1");
                   process.start("dde-dconfig", setArgsTmp);
                   process.waitForFinished();
                   exitCode = process.exitCode();

                   QThread::sleep(1);

                   setArgsTmp = setArgs;
                   setArgsTmp << QString("0");
                   process.start("dde-dconfig", setArgsTmp);
                   process.waitForFinished();
                   exitCode = process.exitCode();
               }
            }
        }
    } else {
        args << QString("0");

        process.start("dde-dconfig", args);

        // Wait for the process to finish
        process.waitForFinished();

        // Get the exit code of the process
        exitCode = process.exitCode();
    }

    if (exitCode != 0) {
        if (process.error() == QProcess::ProcessError::FailedToStart) {
            ctx.error = OSCallContext::NotImpl;
            ctx.errorInfo = m_errMap[ctx.error];
        } else {
            ctx.error = OSCallContext::NonService;
            ctx.errorInfo = m_errMap[ctx.error];
        }

        qWarning() << "Failed to execute dde-dconfig: state=" << state
                   << " exitCode=" << exitCode
                   << " stdout=" << QString::fromLocal8Bit(process.readAllStandardOutput())
                   << " stderr=" << QString::fromLocal8Bit(process.readAllStandardError());
    }

    return ctx;
}

OSCallContext UOSAbilityManager::doDockModeSwitch(int mode)
{
    if (mode < 0 || mode >= 2) {
        return ctxByError(OSCallContext::InvalidArgs);
    }

    if (!propertiesSet(osCallDbusDockService, osCallDbusDockPath, osCallDbusDockInterface,
                       QString("DisplayMode"), QVariant::fromValue(mode))) {
        return ctxByError(OSCallContext::NonService);
    }

    return ctxByError(OSCallContext::NonError);
}

OSCallContext UOSAbilityManager::doSystemThemeSwitch(int theme)
{
    OSCallContext ctx;
    ctx.error = OSCallContext::NonError;
    ctx.errorInfo = m_errMap[ctx.error];

    if (theme < 0 || theme >= 3) {
        return ctxByError(OSCallContext::InvalidArgs);
    }

    QVariantList args;
    args << QVariant::fromValue(QString("gtk"));

    switch (theme) {
    case 0:
        args << QVariant::fromValue(QString("deepin"));
        break;
    case 1:
        args << QVariant::fromValue(QString("deepin-dark"));
        break;
    case 2:
        args << QVariant::fromValue(QString("deepin-auto"));
        break;
    default:
        break;
    }

    if (!osCtrCallDbusNoResult(osCallDbusAppearanceService, osCallDbusAppearancePath, osCallDbusAppearanceInterface, QString("Set"), args)) {
        return ctxByError(OSCallContext::NonService);
    }

    return ctxByError(OSCallContext::NonError);
}

OSCallContext UOSAbilityManager::doDiplayEyesProtection(bool on)
{
    //Eyes Protection
    //TODO:
    //   Brightness:70%
    //   Temperatrue: pos [0-100] 20%
    //      int kelvin = pos > 50 ? (6500 - (pos - 50) * 100) : (6500 + (50 - pos) * 300);
    //   May need adjust other display parameter.
    QVariant result;
    if (!osCtrCallDbus(osCallDbusDisplayService, osCallDbusDisplayPath, osCallDbusDisplayInterface, QString("SupportSetColorTemperature"), result)) {
        return ctxByError(OSCallContext::NonService);
    }

    bool isSupEyesProtection = result.toBool();
    if (!isSupEyesProtection) {
        return ctxByError(OSCallContext::NonService);
    }

    if (on) {
        QVariantList argCCT {QVariant::fromValue(2)};
        if (!osCtrCallDbusNoResult(osCallDbusDisplayService, osCallDbusDisplayPath, osCallDbusDisplayInterface,
                                   QString("SetMethodAdjustCCT"), argCCT)) {
            return ctxByError(OSCallContext::NonService);
        }

        int pos = 80;
        int kelvin = pos > 50 ? (6500 - (pos - 50) * 100) : (6500 + (50 - pos) * 300);
        QVariantList argTem {QVariant::fromValue(kelvin)};
        if (!osCtrCallDbusNoResult(osCallDbusDisplayService, osCallDbusDisplayPath, osCallDbusDisplayInterface,
                                   QString("SetColorTemperature"), argTem)) {
            return ctxByError(OSCallContext::NonService);
        }
    } else {
        //Close the eyes protection mode
        // Brightness:80%
        // Temperatrue: disable
        QVariantList argCCT {QVariant::fromValue(0)};
        if (!osCtrCallDbusNoResult(osCallDbusDisplayService, osCallDbusDisplayPath, osCallDbusDisplayInterface,
                                   QString("SetMethodAdjustCCT"), argCCT)) {
            return ctxByError(OSCallContext::NonService);
        }
    }

    return ctxByError(OSCallContext::NonError);
}

OSCallContext UOSAbilityManager::doDiplayBrightness(int value)
{    
    if (value < 0 || value > 100) {
        return ctxByError(OSCallContext::InvalidArgs);
    }

    QVariant screen;
    if (!propertiesGet(osCallDbusDisplayService, osCallDbusDisplayPath, osCallDbusDisplayInterface, QString("Primary"), screen)) {
        return ctxByError(OSCallContext::NonService);
    }

    QVariantList args;
    args << screen
        << QVariant::fromValue(value / 100.0);
    if (!osCtrCallDbusNoResult(osCallDbusDisplayService, osCallDbusDisplayPath, osCallDbusDisplayInterface,
                               QString("SetAndSaveBrightness"), args)) {
        return ctxByError(OSCallContext::NonService);
    }

    return ctxByError(OSCallContext::NonError);;
}

OSCallContext UOSAbilityManager::doAppLaunch(const QString &appId, bool on)
{
    qInfo() << "UOSAbilityManager::doAppLaunch->" << appId;

    OSCallContext ctx;
    ctx.error = OSCallContext::NonError;

    if (on) {
        //TODO:
        //    Mail, browser open the default application.
        if ("mail" == appId) {
            int defRet = m_uosAppLauncher->launchDefault("x-scheme-handler/mailto");

            ctx.error = OSCallContext::CallError(defRet);
        } else if ("browser" == appId) {
            //Try to get the default app by MIME service
            //this maybe failed for deepin MIME service
            //in some cases.
            int defRet = m_uosAppLauncher->launchDefault("x-scheme-handler/https");

            if (OSCallContext::NonError != defRet) {
                //if GetDefaultApp failed and there are other applications
                //try use Qt xdg-open to open the default appliction.
                int defApps = m_uosAppLauncher->listApps("x-scheme-handler/https");

                if (defApps != 0) {
                    if (!QDesktopServices::openUrl(QUrl("https://"))) {
                        ctx.error = OSCallContext::AppStartFailed;
                    }
                } else {
                    ctx.error = OSCallContext::AppStartFailed;
                }
            }
        } else {
            //Handle other app start except mail,browser
            auto iter = m_app2Desktop.find(appId);

            if (iter != m_app2Desktop.end()) {
                //Try to find the desktop file path.
                QString appDesktopFile;
                foreach (auto path, m_defaultDesktopPaths) {
                    foreach (auto d, iter->desktopFiles) {
                        QFileInfo f(path + "/" + d);

                        if (f.isFile() && f.exists()) {
                            appDesktopFile = f.filePath();
                            goto FIND_DESKTOP_END;
                        }
                    }
                }

                //Find the proper desktop file end, or not find
            FIND_DESKTOP_END:

                qInfo() << "UOSAbilityManager::doAppLaunch->" << appDesktopFile;
                int retCode = m_uosAppLauncher->launchDesktop(appDesktopFile);
                ctx.error = OSCallContext::CallError(retCode);

            } else {
                ctx.error = OSCallContext::AppNotFound;
            }
        }
    } else { //off
        ctx.error = OSCallContext::NotImpl;
    }

    ctx.errorInfo = m_errMap[ctx.error];

    return ctx;
}

OSCallContext UOSAbilityManager::doCreateSchedule(const QString &title,
                                                  const QString &startTime,
                                                  const QString &endTime)
{
    OSCallContext ctx;

    auto st = QDateTime::fromString(startTime, "yyyy-MM-ddThh:mm:ss");
    auto et = QDateTime::fromString(endTime, "yyyy-MM-ddThh:mm:ss");

    //TODO:
    //  Ai may reply invalid time format.So check if the times
    //are valid format.
    if (st.isValid() && et.isValid()) {
        QJsonObject schedObj;
        //Use the default title if title is missed
        schedObj["Title"] = title.isEmpty() ? QString("AI会议日程") : title;
        schedObj["Description"] = "Uos Ai " + title;
        schedObj["AllDay"] = false;
        schedObj["Type"] = 1;
        schedObj["Start"] = startTime;
        schedObj["End"] = endTime;
        schedObj["Remind"] = "15";

        qInfo() << "UOSAbilityManager::doCreateSchedule->" << schedObj;

        if (0 == m_uosCalendarScheduler->createSchedule(schedObj)) {
            ctx.error = OSCallContext::NonError;
        } else {
            ctx.error = OSCallContext::NonService;
        }
    } else {
        ctx.error = OSCallContext::InvalidArgs;

        qWarning() << "UOSAbilityManager::doCreateSchedule->"
                   << " title=" << title
                   << " start=" << startTime
                   << " endTime" << endTime;
    }

    ctx.errorInfo = m_errMap[ctx.error];

    return ctx;
}

OSCallContext UOSAbilityManager::switchWifi(bool on)
{
    QStringList adpters;
    {
        QVariant deviceRes;
        propertiesGet(osCallDbusNetworkService, osCallDbusNetworkPath, osCallDbusNetworkInterface, QString("Devices"), deviceRes);
        QString reply = deviceRes.toString();
        QJsonDocument doc = QJsonDocument::fromJson(reply.toUtf8());
        QJsonArray arr = doc["wireless"].toArray();
        for (int index = 0; index < arr.size(); index++)
            adpters.append(arr[index].toObject()["Path"].toString());
    }

    if (adpters.isEmpty()) {
        return ctxByError(OSCallContext::NonService);
    }

    for (auto adpter : adpters) {
        QDBusObjectPath dPath(adpter);
        QVariantList args;
        args << QVariant::fromValue(dPath)
             << QVariant::fromValue(on);
        osCtrCallDbusNoResult(osCallDbusNetworkService, osCallDbusNetworkPath, osCallDbusNetworkInterface, QString("EnableDevice"), args);
    }

    int errCode = 0;
    if (on) {
#ifdef COMPILE_ON_V23
#ifdef COMPILE_ON_V25
        QString firstAdpterNum = adpters.at(0).section('/', -1);
        errCode = m_uosControlCenterProxy->ShowPage(QString("network/wireless%1").arg(firstAdpterNum));
#else
        errCode = m_uosControlCenterProxy->ShowPage(QString("network/WirelessPage"));
#endif
#else
        errCode = m_uosControlCenterProxy->ShowPage("network", "WirelessPage");
#endif
    }

    OSCallContext ctx;
    ctx.error = OSCallContext::CallError(errCode);

    if (errCode == 0)
        ctx.output = textForCommnand();
    else
        ctx.errorInfo = m_errMap[ctx.error];

    return ctx;
}

OSCallContext UOSAbilityManager::getSystemMemory()
{
    OSCallContext ctx;
    ctx.error = OSCallContext::NonError;
    qint64 mem = DSysInfo::memoryInstalledSize();

    if (mem < 1) {
        ctx.error = OSCallContext::InvalidArgs;
        ctx.errorInfo = m_errMap[ctx.error];
    } else {
        QString strMem = formatCap(mem, 1024, 0);
        ctx.output = tr("Your system memory is %0.") .arg(strMem);
    }

    return ctx;
}

OSCallContext UOSAbilityManager::doSystemLanguageSetting()
{
    OSCallContext ctx;
    int errCode = 0;

#ifdef COMPILE_ON_V23
    errCode = m_uosControlCenterProxy->ShowPage("keyboard/keyboardLanguage");
#else
    errCode = m_uosControlCenterProxy->ShowPage("keyboard", "System Language");
#endif
    ctx.error = OSCallContext::CallError(errCode);

    if (errCode == 0)
        ctx.output = tr("The language setting interface has been opened. Please set it in this interface.");
    else
        ctx.errorInfo = m_errMap[ctx.error];

    return ctx;
}

OSCallContext UOSAbilityManager::doPerformanceModeSwitch(const QString &mode, bool isOpen)
{
    const QString performanceStr = "performance";
    const QString balanceStr = "balance";
    const QString powersaveStr = "powersave";

    auto conn = QDBusConnection::systemBus();
    QDBusMessage msg = QDBusMessage::createMethodCall(osCallDbusPowerService, osCallDbusPowerPath, QString("org.freedesktop.DBus.Properties"),
                                                      QString("Get"));
    QVariantList args;
    args << QVariant::fromValue(QString(osCallDbusPowerInterface))
         << QVariant::fromValue(QString("Mode"));
    msg.setArguments(args);
    QDBusReply<QVariant> reply = conn.call(msg);
    QString currentMode = reply.value().toString();

    // set mode
    auto setMode = [](const QString &mode){
        auto conn = QDBusConnection::systemBus();
        QDBusMessage setMsg = QDBusMessage::createMethodCall(osCallDbusPowerService, osCallDbusPowerPath, osCallDbusPowerInterface,
                                                          QString("SetMode"));
        QVariantList setArgs {QVariant::fromValue(mode)};
        setMsg.setArguments(setArgs);
        QDBusMessage setReply = conn.call(setMsg);
        if (setReply.type() != QDBusMessage::ReplyMessage) {
            return false;
        }

        return true;
    };

    if (isOpen) {
        // 切换
        if (mode == currentMode) {
            OSCallContext ctx;
            ctx.output = tr("The current mode is already %1 mode.").arg(mode);
            return ctx;
        }

        bool res = setMode(mode);
        if (!res)
            return ctxByError(OSCallContext::NonService);
    } else {
        // 关闭
        if (mode != currentMode) {
            OSCallContext ctx;
            ctx.output = tr("Unable to close because the current mode %1 does not match the target mode.").arg(mode);
            return ctx;
        }

        if (mode == performanceStr || mode == powersaveStr) {
            bool res = setMode(balanceStr);
            if (!res)
                return ctxByError(OSCallContext::NonService);
        } else if (mode == balanceStr) {
            OSCallContext ctx;
            ctx.output = tr("Balance mode cannot be turned off.");
            return ctx;
        }
    }

    return ctxByError(OSCallContext::NonError);
}

OSCallContext UOSAbilityManager::openShutdownFront()
{
    if (!osCtrCallDbusNoResult(osCallDbusShutDownService, osCallDbusShutDownPath, osCallDbusShutDownInterface, QString("Show"))) {
        return ctxByError(OSCallContext::NonService);
    }

    OSCallContext ctx;
    ctx.output = tr("The lock screen has been opened for you");
    return ctx;
}
OSCallContext UOSAbilityManager::openScreenShot()
{
    if (!osCtrCallDbusNoResult(osCallDbusScreenshotService, osCallDbusScreenshotPath, osCallDbusScreenshotInterface, QString("StartScreenshot"))) {
        return ctxByError(OSCallContext::NonService);
    }

    OSCallContext ctx;
    ctx.output = tr("Screen shotting or recording has been completed");
    return ctx;
}

OSCallContext UOSAbilityManager::doDisplayModeSwitch(int mode)
{
    QVariant result;
    if (!osCtrCallDbus(osCallDbusDisplayService, osCallDbusDisplayPath, osCallDbusDisplayInterface, QString("ListOutputNames"), result)) {
        return ctxByError(OSCallContext::NonService);
    }
    QStringList screenList = result.toStringList();

    if (screenList.count() <= 1) {
        OSCallContext ctx;
        ctx.output = tr("Only one screen, can't switch screen mode.");
        return ctx;
    }

    QVariant currentMode;
    if (!propertiesGet(osCallDbusDisplayService, osCallDbusDisplayPath, osCallDbusDisplayInterface, QString("DisplayMode"), currentMode)) {
        return ctxByError(OSCallContext::NonService);
    }
    if (mode == currentMode.toInt()) {
        OSCallContext ctx;
        ctx.output = tr("It is the same as the current display mode. Please try again.");
        return ctx;
    }

    QVariant primary;
    if (!propertiesGet(osCallDbusDisplayService, osCallDbusDisplayPath, osCallDbusDisplayInterface, QString("Primary"), primary)) {
        return ctxByError(OSCallContext::NonService);
    }

    QVariantList args;
    args << QVariant::fromValue(mode)
         << primary;
    if (!osCtrCallDbusNoResult(osCallDbusDisplayService, osCallDbusDisplayPath, osCallDbusDisplayInterface, QString("SwitchMode"), args)) {
        return ctxByError(OSCallContext::NonService);
    }

    return ctxByError(OSCallContext::NonError);
}

OSCallContext UOSAbilityManager::openGrandSearch()
{
    OSCallContext ctx;
    ctx.error = OSCallContext::NonError;
    ctx.errorInfo = m_errMap[ctx.error];

    QDBusInterface *grandSearch = new QDBusInterface(
                "com.deepin.dde.GrandSearch",
                "/com/deepin/dde/GrandSearch",
                "com.deepin.dde.GrandSearch",
                QDBusConnection::sessionBus(), this);

    if (!grandSearch->isValid()) {
        ctx.error = OSCallContext::NonService;
        ctx.errorInfo = m_errMap[ctx.error];
    }

    return ctx;
}

OSCallContext UOSAbilityManager::switchScreen()
{
    uchar singleMode = 3;
    QVariant currentMode;
    if (!propertiesGet(osCallDbusDisplayService, osCallDbusDisplayPath, osCallDbusDisplayInterface, QString("DisplayMode"), currentMode)) {
        return ctxByError(OSCallContext::NonService);
    }

    QVariant primaryScreen;
    if (!propertiesGet(osCallDbusDisplayService, osCallDbusDisplayPath, osCallDbusDisplayInterface, QString("Primary"), primaryScreen)) {
        return ctxByError(OSCallContext::NonService);
    }
    QString primary = primaryScreen.toString();

    QVariant screenList;
    if (!osCtrCallDbus(osCallDbusDisplayService, osCallDbusDisplayPath, osCallDbusDisplayInterface, QString("ListOutputNames"), screenList)) {
        return ctxByError(OSCallContext::NonService);
    }
    QStringList screens = screenList.toStringList();

    if (screens.length() <= 1) {
        OSCallContext ctx;
        ctx.output = tr("Only one screen, can't switch screen.");
        return ctx;
    }

    QVariantList switchArgs;
    switchArgs << QVariant::fromValue(singleMode);
    if (singleMode == currentMode) {
        int screenIdx = screens.indexOf(primary);
        if (screens.endsWith(primary))
            switchArgs << QVariant::fromValue(screens.first());
        else
            switchArgs << QVariant::fromValue(screens[screenIdx + 1]);
    }
    else
        switchArgs << primaryScreen;

    if (!osCtrCallDbusNoResult(osCallDbusDisplayService, osCallDbusDisplayPath, osCallDbusDisplayInterface, QString("SwitchMode"), switchArgs)) {
        return ctxByError(OSCallContext::NonService);
    }

    return ctxByError(OSCallContext::NonError);
}

OSCallContext UOSAbilityManager::volumeAdjustment(const QJsonObject &argsObj)
{
    QVariant audioSink;
    if (!propertiesGet(osCallDbusAudioService, osCallDbusAudioPath, osCallDbusAudioInterface, QString("DefaultSink"), audioSink)) {
        return ctxByError(OSCallContext::NonService);
    }
    QString audioPath = audioSink.value<QDBusObjectPath>().path();

    if (audioPath.isEmpty())
        return ctxByError(OSCallContext::NonService);

    if (argsObj.contains("mute")) {
        // 静音
        bool isMute = argsObj.value("mute").toBool();
        QVariantList muteArgs;
        muteArgs << QVariant::fromValue(isMute);
        if (!osCtrCallDbusNoResult(osCallDbusAudioService, audioPath, osCallDbusAudioInterface + QString(".Sink"), QString("SetMute"), muteArgs)) {
            return ctxByError(OSCallContext::NonService);
        }

        return ctxByError(OSCallContext::NonError);
    }

    if (argsObj.contains("approximate")) {
        // 模糊调节
        QString approximate = argsObj.value("approximate").toString();
        QVariant volVariant;
        if (!propertiesGet(osCallDbusAudioService, audioPath, osCallDbusAudioInterface + QString(".Sink"), QString("Volume"), volVariant)) {
            return ctxByError(OSCallContext::NonService);
        }
        double vol = volVariant.toDouble();
        if (approximate == "Add") {
            vol += 0.1;
        } else if (approximate == "Reduce") {
            vol -= 0.1;
        } else {
            return ctxByError(OSCallContext::InvalidArgs);
        }
        vol = qBound(0.0, vol, 1.0);
        QVariantList volArgs;
        volArgs << QVariant::fromValue(vol);
        volArgs << QVariant::fromValue(true);
        if (!osCtrCallDbusNoResult(osCallDbusAudioService, audioPath, osCallDbusAudioInterface + QString(".Sink"), QString("SetVolume"), volArgs)) {
            return ctxByError(OSCallContext::NonService);
        }

        return ctxByError(OSCallContext::NonError);
    }

    if (argsObj.contains("volume")) {
        double vol = argsObj.value("volume").toInt() / 100.0;
        if (vol < 0 || vol > 100) {
            return ctxByError(OSCallContext::InvalidArgs);
        }

        QVariantList volArgs;
        volArgs << QVariant::fromValue(vol);
        volArgs << QVariant::fromValue(true);
        if (!osCtrCallDbusNoResult(osCallDbusAudioService, audioPath, osCallDbusAudioInterface + QString(".Sink"), QString("SetVolume"), volArgs)) {
            return ctxByError(OSCallContext::NonService);
        }
    }

    return ctxByError(OSCallContext::NonError);
}

QString UOSAbilityManager::textForCommnand()
{
    return tr("Your command has been issued.");
}

QStringList UOSAbilityManager::getAppsDesc()
{
    if (m_app2Desktop.isEmpty())
        return {};

    QStringList appsDesc;
    for (auto iter = m_app2Desktop.begin(); iter != m_app2Desktop.end(); iter++) {
        appsDesc << iter.value().appDesc;
    }

    return appsDesc;
}

void UOSAbilityManager::loadErrMap()
{
    m_errMap[OSCallContext::NonError] = "";
    m_errMap[OSCallContext::NotImpl]
        = QCoreApplication::translate(
              "UOSAbility"
              , "I haven't implemented this feature yet.");
    m_errMap[OSCallContext::NonService]
        = QCoreApplication::translate(
              "UOSAbility"
              , "service is not available!");

    m_errMap[OSCallContext::InvalidArgs]
        = QCoreApplication::translate(
              "UOSAbility"
              , "Invalid parameter!");

    m_errMap[OSCallContext::AppNotFound]
        = QCoreApplication::translate(
              "UOSAbility"
              , "This app cannot be found!");

    m_errMap[OSCallContext::AppStartFailed]
        = QCoreApplication::translate(
              "UOSAbility"
              , "Failed to start application!");

}

void UOSAbilityManager::loadApp2Desktop()
{
    // 读取JSON文件
    QFile file(":/assets/app/deepin-app-infos.json");

    if (!file.open(QIODevice::ReadOnly)) {
        qWarning() << "Failed to load uos app infos.";
        return;
    }

    QByteArray jsonData = file.readAll();
    file.close();

    QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData);

    if (!jsonDoc.isObject()) {
        qWarning() << "Uos app infos file format error!";
        return;
    }

    QJsonObject appConfigObject = jsonDoc.object();

    //Only use config application paths if system don't
    //set.
    if (m_defaultDesktopPaths.isEmpty()) {
        QJsonArray desktopPaths = appConfigObject["desktopPaths"].toArray();
        foreach (auto path, desktopPaths) {
            QString deskPath = path.toString();

            if (!deskPath.isEmpty()) {
                m_defaultDesktopPaths << deskPath;
            }
        }
    }

    QJsonArray appInfosArray = appConfigObject["appInfos"].toArray();

    foreach (const QJsonValue &value, appInfosArray) {
        if (value.isObject()) {
            UosAppInfo appInfo;

            QJsonObject app = value.toObject();

            appInfo.appId = app["appId"].toString();
            appInfo.appName = app["appName"].toString();
            appInfo.appIcon = app["appIcon"].toString();
            appInfo.appDesc = app["appDesc"].toString();

            foreach (auto desktopFile, app["desktopFile"].toArray()) {
                appInfo.desktopFiles << desktopFile.toString();
            }

            m_app2Desktop.insert(appInfo.appId, appInfo);
        }
    }

    qInfo() << "loadApp2Desktop->" << m_app2Desktop.size() << m_defaultDesktopPaths;
}

void UOSAbilityManager::initUosProxys()
{
    m_uosControlCenterProxy.reset(new DeepinControlCenter(this));
    m_uosNotificationProxy.reset(new DeepinNotification(this));
    m_uosAppLauncher.reset(new DeepinLauncher(m_defaultDesktopPaths, this));
    m_uosCalendarScheduler.reset(new DeepinCalendar(this));
}

void UOSAbilityManager::initDesktopPaths()
{
    QString systemDataDirs = UosInfo()->pureEnvironment().value("XDG_DATA_DIRS");

#if QT_VERSION < QT_VERSION_CHECK(5,14,0)
    QStringList systemDataPaths = systemDataDirs.split(":", QString::SkipEmptyParts);
#else
    QStringList systemDataPaths = systemDataDirs.split(":", Qt::SkipEmptyParts);
#endif

    foreach (auto p, systemDataPaths) {
        if (p.endsWith('/')) {
            p.chop(1);
        }
        m_defaultDesktopPaths << (p + QString("/applications"));
    }
}

bool UOSAbilityManager::osCtrCallDbus(const QString &service, const QString &path, const QString &interface, const QString &method,
                                 QVariant &result, const QVariantList &arguments)
{
    auto conn = QDBusConnection::sessionBus();
    QDBusMessage msg = QDBusMessage::createMethodCall(service, path, interface,  method);

    if (!arguments.isEmpty())
        msg.setArguments(arguments);

    // sync
    QDBusMessage reply = conn.call(msg);

    if (reply.type() != QDBusMessage::ReplyMessage) {
        qWarning() << QString("dbus error: service:%0, path:%1, interface:%2, method:%3").arg(service, path, interface, method);
        qWarning() << reply.errorMessage();
        return false;
    }

    if (reply.arguments().isEmpty())
        return false;

    result = reply.arguments().at(0);
    return true;
}

bool UOSAbilityManager::osCtrCallDbusNoResult(const QString &service, const QString &path, const QString &interface, const QString &method, const QVariantList &arguments)
{
    auto conn = QDBusConnection::sessionBus();
    QDBusMessage msg = QDBusMessage::createMethodCall(service, path, interface,  method);

    if (!arguments.isEmpty())
        msg.setArguments(arguments);

    // sync
    QDBusMessage reply = conn.call(msg);

    if (reply.type() != QDBusMessage::ReplyMessage) {
        qWarning() << QString("dbus error: service:%0, path:%1, interface:%2, method:%3").arg(service, path, interface, method);
        qWarning() << reply.errorMessage();
        return false;
    }

    return true;
}

bool UOSAbilityManager::propertiesGet(const QString &service, const QString &path, const QString &interface, const QString &propertyName, QVariant &value)
{
    auto conn = QDBusConnection::sessionBus();
    QDBusMessage msg = QDBusMessage::createMethodCall(service, path, QString("org.freedesktop.DBus.Properties"), QString("Get"));

    QVariantList args;
    args << QVariant::fromValue(interface)
         << QVariant::fromValue(propertyName);
    msg.setArguments(args);

    QDBusMessage reply = conn.call(msg);
    if (reply.type() != QDBusMessage::ReplyMessage) {
        qWarning() << reply.errorMessage();
        return false;
    }

    if (reply.arguments().isEmpty())
        return false;

    if (reply.arguments().at(0).canConvert<QDBusVariant>()) {
        value = QDBusReply<QVariant>(reply);
        return true;
    }

    return false;
}

bool UOSAbilityManager::propertiesGetAll(const QString &interface, QVariantMap &values)
{
    //TODO
    return true;
}

bool UOSAbilityManager::propertiesSet(const QString &service, const QString &path, const QString &interface, const QString &propertyName, const QVariant &value)
{
    auto conn = QDBusConnection::sessionBus();
    QDBusMessage msg = QDBusMessage::createMethodCall(service, path, QString("org.freedesktop.DBus.Properties"), QString("Set"));

    QVariantList args;
    args << QVariant::fromValue(interface)
         << QVariant::fromValue(propertyName)
         << QVariant::fromValue(QDBusVariant(QVariant::fromValue(value)));
    msg.setArguments(args);

    QDBusMessage reply = conn.call(msg);

    if (reply.type() != QDBusMessage::ReplyMessage) {
        qWarning() << reply.errorMessage();
        return false;
    }

    return true;
}
