/*
 * Copyright 2014  Rohan Garg <rohan@kde.org>
 * Copyright 2026  Erich Eickmeyer <eeickmeyer@ubuntu.com>
 *
 * 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 2 of
 * the License or (at your option) version 3 or any later version
 * accepted by the membership of KDE e.V. (or its successor approved
 * by the membership of KDE e.V.), which shall act as a proxy
 * defined in Section 14 of version 3 of the license.
 *
 * 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 "driverevent.h"
#include <drivermanager_interface.h>

#include <QDBusConnection>
#include <QDebug>
#include <QProcess>
#include <QStandardPaths>

#include <KIO/CommandLauncherJob>
#include <KConfig>
#include <KConfigGroup>

DriverEvent::DriverEvent(QObject *parent, QString name)
    : Event(parent, name)
    , m_showNotification(false)
{
    qDBusRegisterMetaType<DeviceList>();
}

bool DriverEvent::isPackageInstalled(const QString &packageName)
{
    QProcess dpkg;
    dpkg.start(QStringLiteral("dpkg-query"),
               {QStringLiteral("-W"), QStringLiteral("-f=${Status}"), packageName});
    dpkg.waitForFinished(5000);
    // dpkg-query returns 0 if the package is known, output is "install ok installed"
    return (dpkg.exitCode() == 0 &&
            dpkg.readAllStandardOutput().trimmed() == "install ok installed");
}

void DriverEvent::show()
{
    m_manager = new OrgKubuntuDriverManagerInterface(
        QStringLiteral("org.kubuntu.DriverManager"),
        QStringLiteral("/DriverManager"),
        QDBusConnection::sessionBus());

    // Force no dbus timeout.
    m_manager->setTimeout(INT_MAX);

    QDBusPendingReply<DeviceList> reply = m_manager->devices();
    QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
    connect(watcher, &QDBusPendingCallWatcher::finished,
            this, &DriverEvent::onDevicesReady);
}

void DriverEvent::onDevicesReady(QDBusPendingCallWatcher *call)
{
    QDBusPendingReply<DeviceList> reply = *call;

    if (reply.isError()) {
        qDebug() << "got dbus error; abort";
        return;
    }

    DeviceList devices = reply.value();
    call->deleteLater(); // deep copy, delete caller

    qDebug() << "data " << devices;

    KConfig driver_manager(QStringLiteral("kcmdrivermanagerrc"));
    KConfigGroup pciGroup( &driver_manager, QStringLiteral("PCI") );

    for (const Device &device : std::as_const(devices)) {
        if (pciGroup.readEntry(device.id) != QLatin1String("true")) {
            // Not seen before, check whether we have recommended drivers.
            for (int i = 0; i < device.drivers.length(); ++i) {
                // Supposedly Driver is not a pod due to ctor, so it can't
                // be fully used by QList :'<
                // Manually iter instead.
                Driver driver = device.drivers.at(i);
                // If there is only one driver listed, we consider it an option.
                // This works around an issue with virtualbox where the one and
                // only driver is not marked as recommended. However, from
                // a notification point of view it makes sense to notify about
                // single-option drivers all the same as even if not considered
                // recommended they probably should be looked at by the user.
                if (driver.recommended || device.drivers.length() == 1) {
                    if (!isPackageInstalled(driver.packageName)) {
                        m_showNotification = true;
                        break;
                    }
                }
            }
        } else {
            qDebug() << device.id << "has already been processed by the KCM";
        }
    }

    if (m_showNotification) {
        QString icon = QStringLiteral("hwinfo");
        QString text(i18nc("Notification when additional packages are required for activating proprietary hardware",
                           "Proprietary drivers might be required to enable additional features"));
        QStringList actions;
        actions << i18nc("Launches KDE Control Module to manage drivers", "Manage Drivers");
        actions << i18nc("Button to dismiss this notification once", "Ignore for now");
        actions << i18nc("Button to make this notification never show up again",
                         "Never show again");
        Event::show(icon, text, actions);
    }
}

void DriverEvent::run()
{
    // Try distro-specific driver managers first, fall back to software-properties-qt
    const QStringList candidates = {
        QStringLiteral("kubuntu-driver-manager"),
        QStringLiteral("ubuntustudio-driver-manager"),
    };
    for (const QString &cmd : candidates) {
        if (!QStandardPaths::findExecutable(cmd).isEmpty()) {
            auto *job = new KIO::CommandLauncherJob(cmd);
            job->start();
            Event::run();
            return;
        }
    }
    // Ultimate fallback: the drivers tab in software-properties-qt
    auto *job = new KIO::CommandLauncherJob(QStringLiteral("software-properties-qt"),
                                            QStringList() << QStringLiteral("--open-tab=4"));
    job->start();
    Event::run();
}

