#include "servicemanager.h"

#include <QDebug>
#include <QDBusArgument>
#include <QDBusMessage>
#include <QDBusMetaType>
#include <QDBusObjectPath>
#include <QDBusReply>
#include <glib.h>

#define SYSTEMD_SERVICE "org.freedesktop.systemd1"
#define SYSTEMD_PATH "/org/freedesktop/systemd1"
#define SYSTEMD_MANAGER_INTERFACE "org.freedesktop.systemd1.Manager"

static const QStringList AVAHI_UNITS = {
    QStringLiteral("avahi-daemon.service"),
    QStringLiteral("avahi-daemon.socket"),
    QStringLiteral("avahi-dnsconfd.service")
};

struct Change
{
    QString action;
    QString unit;
    QString target;
};
typedef QList<Change> ChangeList;
Q_DECLARE_METATYPE(Change)
Q_DECLARE_METATYPE(ChangeList)

QDBusArgument &operator<<(QDBusArgument &argument, const Change &change)
{
    argument.beginStructure();
    argument << change.action << change.unit << change.target;
    argument.endStructure();

    return argument;
}

const QDBusArgument &operator>>(const QDBusArgument &argument, Change &change)
{
    argument.beginStructure();
    argument >> change.action >> change.unit >> change.target;
    argument.endStructure();

    return argument;
}

QDebug operator<<(QDebug debug, const Change& change)
{
    QDebugStateSaver saver(debug);
    debug.nospace() << '(' << change.action << ", " << change.unit << ", " << change.target << ')';
    return debug;
}

ServiceManager::ServiceManager(QObject *parent)
    : QObject(parent),
      m_systemd(SYSTEMD_SERVICE,
                SYSTEMD_PATH,
                SYSTEMD_MANAGER_INTERFACE,
                QDBusConnection::systemBus())
{
    qDBusRegisterMetaType<Change>();
    qDBusRegisterMetaType<ChangeList>();
}

ServiceManager::~ServiceManager()
{
}

bool ServiceManager::isSupported()
{
    const auto distro_id = std::string(g_get_os_info("ID"));
    const auto distro_variant = std::string(g_get_os_info("VARIANT_ID"));
    return (distro_id == "ubuntu" && distro_variant == "touch");
}

void ServiceManager::enableAvahi()
{
    auto msg = QDBusMessage::createMethodCall(SYSTEMD_SERVICE,
                                              SYSTEMD_PATH,
                                              SYSTEMD_MANAGER_INTERFACE,
                                              "EnableUnitFiles");
    msg.setArguments({AVAHI_UNITS /*files*/, false /*runtime*/, false /*force*/});
    msg.setInteractiveAuthorizationAllowed(true);

    QDBusReply<bool> reply = QDBusConnection::systemBus().call(msg);
    if (!reply.isValid()) {
        qWarning() << Q_FUNC_INFO << "DBus error (EnableUnitFiles):" << reply.error().message();
        return;
    } else {
        qDebug() << reply.value();
    }

    m_systemd.call("Reload");

    for (const auto& unit : AVAHI_UNITS) {
        QDBusReply<QDBusObjectPath> reply = m_systemd.call("StartUnit", unit, "replace");
        if (!reply.isValid() || reply.value().path().isEmpty()) {
            qWarning() << Q_FUNC_INFO << "DBus error (StartUnit):" << reply.error().message();
            return;
        }
    }
}

void ServiceManager::disableAvahi()
{
    auto msg = QDBusMessage::createMethodCall(SYSTEMD_SERVICE,
                                              SYSTEMD_PATH,
                                              SYSTEMD_MANAGER_INTERFACE,
                                              "DisableUnitFiles");
    msg.setArguments({AVAHI_UNITS /*files*/, false /*runtime*/});
    msg.setInteractiveAuthorizationAllowed(true);

    QDBusReply<ChangeList> reply = QDBusConnection::systemBus().call(msg);

    if (!reply.isValid()) {
        qWarning() << Q_FUNC_INFO << "DBus error (DisableUnitFiles):" << reply.error().message();
        return;
    } else {
        qDebug() << reply.value();
    }

    m_systemd.call("Reload");

    for (const auto& unit : AVAHI_UNITS) {
        QDBusReply<QDBusObjectPath> reply = m_systemd.call("StopUnit", unit, "replace");
        if (!reply.isValid() || reply.value().path().isEmpty()) {
            qWarning() << Q_FUNC_INFO << "DBus error (StopUnit):" << reply.error().message();
            return;
        }
    }
}

bool ServiceManager::isAvahiEnabled()
{
    bool ret = false;

    for (const auto& unit : AVAHI_UNITS) {
        QDBusReply<QString> reply = m_systemd.call("GetUnitFileState", unit);
        if (!reply.isValid()) {
            qWarning() << Q_FUNC_INFO << "DBus error (GetUnitFileState):" << reply.error().message();
            return false;
        }

        const auto state = reply.value();
        if (state != QStringLiteral("masked") && state != QStringLiteral("disabled"))
            ret = true;
    }

    return ret;
}
