/*
 * 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, 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 <http://www.gnu.org/licenses/>.
 *
**/
#include "autoboot.h"

#include <QThread>
#include <QSignalMapper>
#include <QDebug>
#include <QFont>
#include <QMouseEvent>
#include <QPushButton>
#include <QGSettings>
#include <QToolButton>
#include <QMenu>
#include <QFileDialog>
#include <QMessageBox>
#include "rmenu.h"
#include "imageutil.h"
#include "mthread.h"

/* qt会将glib里的signals成员识别为宏，所以取消该宏
 * 后面如果用到signals时，使用Q_SIGNALS代替即可
 **/
#ifdef signals
#undef signals
#endif

#include <glib.h>
#include <glib/gstdio.h>
#include <gio/gio.h>
#include <gio/gdesktopappinfo.h>


#include "kswitchbutton.h"
using namespace kdk;

#define ITEMWIDTH 522
#define ITEMHEIGHT 62
#define HEADHEIGHT 38

#define THEME_QT_SCHEMA  "org.ukui.style"
#define THEME_GTK_SCHEMA "org.mate.interface"

#define UKCC_SCHEMA        "org.ukui.control-center"
#define UKCC_AUTOAPP_EKY   "autoapp-list"

#define ICON_QT_KEY      "icon-theme-name"
#define ICON_GTK_KEY     "icon-theme"

#define LOCAL_CONFIG_DIR           "/.config/autostart/"
#define SYSTEM_CONFIG_DIR          "/etc/xdg/autostart/"
#define USR_CONFIG_DIR             "/usr/share/applications/"

class ukFileDialog : public QFileDialog
{
public:
    explicit ukFileDialog(QWidget *parent = nullptr,
                         const QString &caption = QString(),
                         const QString &directory = QString(),
                         const QString &filter = QString())
        : QFileDialog(parent, caption, directory, filter)
    {
        blacklist.append(QString("%1%2").arg(USR_CONFIG_DIR).arg( "gparted.desktop")); //需要root权限才能打开，session无法打开
        mAutobootDBus = new QDBusInterface("org.ukui.ukcc.session",
                                           "/Autoboot",
                                           "org.ukui.ukcc.session.Autoboot",
                                           QDBusConnection::sessionBus(), this);
    }
protected:
   void accept() override;

private:
   QStringList blacklist;
   QDBusInterface *mAutobootDBus;

};

AutoBoot::AutoBoot() : mFirstLoad(true)
{
    pluginName = tr("Auto Boot");
    pluginType = APPLICATION;
}

AutoBoot::~AutoBoot()
{
    if (!mFirstLoad) {
        if (mAutobootDBus->isValid())
            mAutobootDBus->call("setApplist", mAutoAppList);
    }
}

QString AutoBoot::plugini18nName()
{
    return pluginName;
}

int AutoBoot::pluginTypes()
{
    return pluginType;
}

QWidget *AutoBoot::pluginUi()
{
    if (mFirstLoad) {
        mFirstLoad = false;
        mAutoWidget = new AutoBootUi;
        mAutobootDBus = new QDBusInterface("org.ukui.ukcc.session",
                                           "/Autoboot",
                                           "org.ukui.ukcc.session.Autoboot",
                                           QDBusConnection::sessionBus(), this);
        if (!mAutobootDBus->isValid()) {
            qCritical() << "org.ukui.ukcc.session.Autoboot DBus error:" << mAutobootDBus->lastError();
        } else {
            QDBusConnection::sessionBus().connect("org.ukui.ukcc.session",
                                                  "/Autoboot",
                                                  "org.ukui.ukcc.session.Autoboot",
                                                  "changed",
                                                  this,
                                                  SLOT(keyChangedSlot(QString)));
            initConfig();
            initAutoUI();
            connectToServer();
        }
    }
    return mAutoWidget;
}

const QString AutoBoot::name() const
{
    return QStringLiteral("Autoboot");
}

bool AutoBoot::isShowOnHomePage() const
{
    return true;
}

QIcon AutoBoot::icon() const
{
    return QIcon::fromTheme("ukui-poweron-symbolic");
}

bool AutoBoot::isEnable() const
{
    return true;
}

void AutoBoot::initAutoUI()
{
    appgroupMultiMaps.clear();
    checkSignalMapper = new QSignalMapper(this);
    mAutoAppList = mAutobootDBus->property("appList").toStringList();
    qDebug()<<mAutoAppList;

    QMap <QString, QVariant> map = mAutobootDBus->property("statusMap").toMap();

    for (QMap<QString, QVariant>::iterator it = map.begin(); it != map.end(); it++) {
        AutoApp info;
        const QDBusArgument &dbusArgs = it.value().value<QDBusArgument>();
        dbusArgs >> info;
        statusMaps.insert(it.key(), info);
    }

    // 构建每个启动项
    QMap<QString, AutoApp>::iterator iter;
    for (QMap<QString, AutoApp>::iterator it = statusMaps.begin(); it != statusMaps.end(); it++) {
        if (!mAutoAppList.contains(it.value().bname))
            mAutoAppList.append(it.value().bname);
    }

    for (QString str : mAutoAppList) {
        iter = statusMaps.find(str);
        if (iter != statusMaps.end()) {
            initItem(iter.value());
        } else {
            mAutoAppList.removeOne(str);
        }
    }
    mAutoWidget->getAutobootWidget()->addWidget(mAutoWidget->getAddWidget());
    mAutobootDBus->call("setApplist", mAutoAppList);
    connect(checkSignalMapper, SIGNAL(mapped(QString)), this, SLOT(checkboxChangedSlot(QString)));
    connect(mAutoWidget->getAddBtn(), &AddButton::clicked, this, &AutoBoot::addAppSlot);
}

void AutoBoot::initItem(AutoApp &it)
{
    QString bname = it.bname;
    QString appName = it.name;

    SwitchWidget *baseWidget = new SwitchWidget(appName);

    QLabel *iconLabel = new QLabel(baseWidget);
    iconLabel->setFixedSize(32, 32);
    QPixmap pixmap;
    setAutoPixmap(pixmap, it.icon);
    iconLabel->setPixmap(pixmap);
    mIconLabelMap.insert(iconLabel, it.icon);
    baseWidget->insertWidget(0, iconLabel);

    baseWidget->setChecked(!it.hidden);
    checkSignalMapper->setMapping(baseWidget, bname);
    connect(baseWidget, SIGNAL(stateChanged(bool)), checkSignalMapper, SLOT(map()));
    appgroupMultiMaps.insert(bname, baseWidget);

    if (it.position == Pos::LOCALPOS) {
        QToolButton *deBtn = new QToolButton(baseWidget);
        deBtn->setStyleSheet("QToolButton:!checked{background-color: palette(base)}");
        deBtn->setProperty("useButtonPalette", true);
        deBtn->setPopupMode(QToolButton::InstantPopup);
        deBtn->setIcon(QIcon::fromTheme("view-more-horizontal-symbolic"));

        RMenu *pMenu = new RMenu(deBtn);

        deBtn->setMenu(pMenu);
        QAction* mDel = new QAction(tr("Delete"),this);
        pMenu->addAction(mDel);
        connect(mDel, &QAction::triggered, this, [=](){
            UkccCommon::buriedSettings(name(), "autoboot item " + bname, QString("settings"), "delete from list");
            QMap<QString, AutoApp>::iterator iter = statusMaps.find(bname);
            if (iter == statusMaps.end()) {
                qDebug() << "AutoBoot Data Error";
                return;
            }
            mAutobootDBus->call("deleteLocalFile", bname);
            appgroupMultiMaps.erase(appgroupMultiMaps.find(bname));
            statusMaps.erase(iter);
            mAutoAppList.removeOne(bname);
            mAutobootDBus->call("setApplist", mAutoAppList);
            mIconLabelMap.erase(mIconLabelMap.find(iconLabel));
            baseWidget->close();
        });
        baseWidget->insertWidget(2, deBtn, 1, Qt::AlignRight);
    }
    mAutoWidget->getAutobootWidget()->addWidget(baseWidget);
}

void AutoBoot::addItem(const QString &file)
{
    QDBusReply<bool> reply = mAutobootDBus->call("addAutobootApp", file);
    if (!reply.value())
        return;
    statusMaps.clear();
    QMap <QString, QVariant> map = mAutobootDBus->property("statusMap").toMap();

    for (QMap<QString, QVariant>::iterator it = map.begin(); it != map.end(); it++) {
        AutoApp info;
        const QDBusArgument &dbusArgs = it.value().value<QDBusArgument>();
        dbusArgs >> info;
        statusMaps.insert(it.key(), info);
    }
    QFileInfo fileinfo(file);
    AutoApp app = statusMaps[fileinfo.fileName()];
    if (app.bname.isEmpty())
        return;
    mAutoAppList.append(app.bname);
    mAutobootDBus->call("setApplist", mAutoAppList);
    mAutoWidget->getAutobootWidget()->removeWidget(mAutoWidget->getAddWidget());
    initItem(app);
    mAutoWidget->getAutobootWidget()->addWidget(mAutoWidget->getAddWidget());
}

void AutoBoot::setAutoPixmap(QPixmap &pixmap, const QString &icon)
{
    QFileInfo iconfile(QString("/usr/share/pixmaps/"+ icon + ".png"));
    QIcon currenticon = QIcon::fromTheme(icon);
    if (!currenticon.isNull()) {
        pixmap = currenticon.pixmap(QSize(32, 32));
    } else if (iconfile.exists()) {
        pixmap = QPixmap(iconfile.filePath()).scaled(32, 32);
    } else {
        pixmap = QPixmap(QString(":/img/plugins/autoboot/desktop.png"));
    }
}

void AutoBoot::addAppSlot()
{
    QString filters = tr("Desktop files(*.desktop)");
    ukFileDialog *fd = new ukFileDialog(mAutoWidget);
    fd->setDirectory(USR_CONFIG_DIR);
    fd->setModal(true);
    fd->setAcceptMode(QFileDialog::AcceptOpen);
    fd->setViewMode(QFileDialog::List);
    fd->setNameFilter(filters);
    fd->setFileMode(QFileDialog::ExistingFile);
    fd->setWindowTitle(tr("select autoboot desktop"));
    fd->setLabelText(QFileDialog::Accept, tr("Select"));
    fd->setLabelText(QFileDialog::Reject, tr("Cancel"));
    if (fd->exec() != QDialog::Accepted)
        return;

    QString selectedfile;
    selectedfile = fd->selectedFiles().first();
    addItem(selectedfile);
    UkccCommon::buriedSettings(QStringLiteral("Autoboot"), "add to autoboot list", QString("settings"), selectedfile);
}

void AutoBoot::checkboxChangedSlot(QString bname)
{
    foreach (QString key, appgroupMultiMaps.keys()) {
        if (key == bname) {
            UkccCommon::buriedSettings(name(), "whether " + bname + " auto startup", QString("settings"), ((KSwitchButton *)appgroupMultiMaps.value(key))->isChecked() ? "true" : "false");
            mAutobootDBus->call("saveAppStatus", bname, ((SwitchWidget *)appgroupMultiMaps.value(key))->isChecked());
        }
    }
}

void AutoBoot::keyChangedSlot(const QString &key)
{
    if (key == "boot") {
        mAutoWidget->resetUi();
        initAutoUI();
    } else if (key == "iconThemeName") {
        for (QMap<QLabel*, QString>::iterator it = mIconLabelMap.begin(); it != mIconLabelMap.end(); it++) {
            QPixmap pixmap;
            setAutoPixmap(pixmap, it.value());
            it.key()->setPixmap(pixmap);
        }
    }
}

void AutoBoot::connectToServer()
{
    QThread *NetThread = new QThread;
    MThread *NetWorker = new MThread;
    NetWorker->moveToThread(NetThread);
    connect(NetThread, &QThread::started, NetWorker, &MThread::run);
    connect(NetWorker,&MThread::keychangedsignal,this,&AutoBoot::keyChangedSlot);
    connect(NetThread, &QThread::finished, NetWorker, &MThread::deleteLater);
    NetThread->start();
}

bool AutoBoot::initConfig()
{
    QDir localdir(QString(QDir::homePath()+LOCAL_CONFIG_DIR).toUtf8());
    if(localdir.exists()) {
      return true;
    } else {
       return localdir.mkdir(QDir::homePath()+LOCAL_CONFIG_DIR);
    }
}


void ukFileDialog::accept()
{
    QString selectedfile;
    selectedfile = this->selectedFiles().first();
    if (!mAutobootDBus->isValid()) {
        QFileDialog::accept();
        return;
    }

    QDBusReply<bool> reply = mAutobootDBus->call("getDisplayStatus", selectedfile);

    if (reply.value() || blacklist.contains(selectedfile)) {
        QMessageBox msg(qApp->activeWindow());
        msg.setIcon(QMessageBox::Warning);
        msg.setText(QObject::tr("Programs are not allowed to be added."));
        msg.exec();
    } else {
        QFileDialog::accept();
    }
}

const QDBusArgument &operator<<(QDBusArgument &argument, const AutoApp &app)
{
    argument.beginStructure();
    argument << app.bname;
    argument << app.icon;
    argument << app.name;
    argument << app.hidden;
    argument << app.position;
    argument.endStructure();
    return argument;
}

const QDBusArgument &operator>>(const QDBusArgument &argument, AutoApp &app)
{
    argument.beginStructure();
    argument >> app.bname;
    argument >> app.icon;
    argument >> app.name;
    argument >> app.hidden;
    argument >> app.position;
    argument.endStructure();
    return argument;
}
