// SPDX-License-Identifier: GPL-3.0-or-later

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-plugins.
*
* nymea-plugins 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 of the License, or
* (at your option) any later version.
*
* nymea-plugins 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 nymea-plugins. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "bluetoothmanager.h"

#include <QDBusObjectPath>
#include <QDBusArgument>
#include <QDBusMetaType>

BluetoothManager::BluetoothManager(QObject *parent) :
    QObject(parent),
    m_available(false)
{
    qDBusRegisterMetaType<InterfaceList>();
    qDBusRegisterMetaType<ManagedObjectList>();

    // Check DBus connection
    if (!QDBusConnection::systemBus().isConnected()) {
        qCWarning(dcBluez()) << "System DBus not connected.";
        return;
    }

    // Get notification when bluez appears/disappears on DBus
    m_serviceWatcher = new QDBusServiceWatcher(orgBluez, QDBusConnection::systemBus(), QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration, this);
    connect(m_serviceWatcher, &QDBusServiceWatcher::serviceRegistered, this, &BluetoothManager::serviceRegistered);
    connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &BluetoothManager::serviceUnregistered);

    m_objectManagerInterface = new QDBusInterface(orgBluez, "/", orgFreedesktopDBusObjectManager, QDBusConnection::systemBus(), this);
    if (!m_objectManagerInterface->isValid()) {
        qCWarning(dcBluez()) << "Invalid DBus ObjectManager interface.";
        return;
    }

    QDBusConnection::systemBus().connect(orgBluez, "/", orgFreedesktopDBusObjectManager, "InterfacesAdded", this, SLOT(onInterfaceAdded(QDBusObjectPath,InterfaceList)));
    QDBusConnection::systemBus().connect(orgBluez, "/", orgFreedesktopDBusObjectManager, "InterfacesRemoved", this, SLOT(onInterfaceRemoved(QDBusObjectPath,QStringList)));

    init();
}

QList<BluetoothAdapter *> BluetoothManager::adapters() const
{
    return m_adapters;
}

bool BluetoothManager::isAvailable() const
{
    return m_available;
}

void BluetoothManager::init()
{
    // Get current object from org.bluez
    QDBusMessage query = m_objectManagerInterface->call("GetManagedObjects");
    if(query.type() != QDBusMessage::ReplyMessage) {
        qCWarning(dcBluez()) << "Could not initialize BluetoothManager:" << query.errorName() << query.errorMessage();
        return;
    }

    const QDBusArgument &argument = query.arguments().at(0).value<QDBusArgument>();
    ManagedObjectList objectList = qdbus_cast<ManagedObjectList>(argument);
    processObjectList(objectList);

    if (!m_adapters.isEmpty())
        setAvailable(true);

    qCDebug(dcBluez()) << "BluetoothManager initialized successfully.";
}

void BluetoothManager::clean()
{
    // Delete all adapter objects
    // Note: devices, services and characteristic objects will be removed throug parent relation
    foreach (BluetoothAdapter *adapter, m_adapters) {
        m_adapters.removeOne(adapter);
        emit adapterRemoved(adapter);
        adapter->deleteLater();
    }

    m_adapters.clear();

    setAvailable(false);
}

void BluetoothManager::setAvailable(const bool &available)
{
    if (m_available != available) {
        m_available = available;
        emit availableChanged(m_available);
    }
}

void BluetoothManager::processObjectList(const ManagedObjectList &objectList)
{
    foreach (const QDBusObjectPath &objectPath, objectList.keys()) {
        InterfaceList interfaceList = objectList.value(objectPath);
        processInterfaceList(objectPath, interfaceList);
    }
}

void BluetoothManager::processInterfaceList(const QDBusObjectPath &objectPath, const InterfaceList &interfaceList)
{
    // Note: object hierarchy: first add adapters, than devices, services, characteristics and finally descriptors

    // Adapter interface
    foreach (const QString &interface, interfaceList.keys()) {
        if (interface == orgBluezAdapter1) {
            QVariantMap properties = interfaceList.value(interface);
            // Check if this adapter already added
            if (!adapterAlreadyAdded(objectPath)) {
                BluetoothAdapter *adapter = new BluetoothAdapter(objectPath, properties, this);
                m_adapters.append(adapter);
                emit adapterAdded(adapter);
                qCDebug(dcBluez()) << "[+]" << adapter;
            }
        }
    }

    // Device interface
    foreach (const QString &interface, interfaceList.keys()) {
        if (interface == orgBluezDevice1) {
            QVariantMap properties = interfaceList.value(interface);
            // Find adapter for this thing and add the thing internally
            if (properties.contains("Adapter")) {
                QDBusObjectPath adapterObjectPath = qvariant_cast<QDBusObjectPath>(properties.value("Adapter"));
                BluetoothAdapter *adapter = findAdapter(adapterObjectPath);
                if (adapter)
                    adapter->addDeviceInternally(objectPath, properties);

            }
        }
    }

    // GATT Service interface
    foreach (const QString &interface, interfaceList.keys()) {
        if (interface == orgBluezGattService1) {
            QVariantMap properties = interfaceList.value(interface);
            // Find thing for this service and add the service internally
            if (properties.contains("Device")) {
                QDBusObjectPath deviceObjectPath = qvariant_cast<QDBusObjectPath>(properties.value("Device"));
                BluetoothDevice *thing = findDevice(deviceObjectPath);
                if (thing)
                    thing->addServiceInternally(objectPath, properties);

            }
        }
    }

    // GATT Characteristic interface
    foreach (const QString &interface, interfaceList.keys()) {
        if (interface == orgBluezGattCharacteristic1) {
            QVariantMap properties = interfaceList.value(interface);
            // Find service for this characteristic
            if (properties.contains("Service")) {
                QDBusObjectPath serviceObjectPath = qvariant_cast<QDBusObjectPath>(properties.value("Service"));
                BluetoothGattService *service = findService(serviceObjectPath);
                if (service) {
                    qCDebug(dcBluez()) << "Add characteristic" << serviceObjectPath.path() << properties << service;
                    service->addCharacteristicInternally(objectPath, properties);
                }
            }
        }
    }

    // GATT Descriptor interface
    foreach (const QString &interface, interfaceList.keys()) {
        if (interface == orgBluezGattDescriptor1) {
            QVariantMap properties = interfaceList.value(interface);
            // Find characteristic for this desciptor
            if (properties.contains("Characteristic")) {
                QDBusObjectPath characterisitcObjectPath = qvariant_cast<QDBusObjectPath>(properties.value("Characteristic"));
                BluetoothGattCharacteristic *characteristic = findCharacteristic(characterisitcObjectPath);
                if (characteristic) {
                    qCDebug(dcBluez()) << "Add descriptor" << characterisitcObjectPath.path() << properties << characteristic;
                    characteristic->addDescriptorInternally(objectPath, properties);
                }
            }
        }
    }

}

bool BluetoothManager::adapterAlreadyAdded(const QDBusObjectPath &objectPath)
{
    foreach (BluetoothAdapter *existingAdapter, m_adapters) {
        if (existingAdapter->m_path == objectPath) {
            return true;
        }
    }

    return false;
}

BluetoothAdapter *BluetoothManager::findAdapter(const QDBusObjectPath &objectPath)
{
    foreach (BluetoothAdapter *adapter, m_adapters) {
        if (adapter->m_path == objectPath) {
            return adapter;
        }
    }

    return nullptr;
}

BluetoothDevice *BluetoothManager::findDevice(const QDBusObjectPath &objectPath)
{
    foreach (BluetoothAdapter *adapter, m_adapters) {
        foreach (BluetoothDevice *thing, adapter->devices()) {
            if (thing->m_path == objectPath) {
                return thing;
            }
        }
    }

    return nullptr;
}

BluetoothGattService *BluetoothManager::findService(const QDBusObjectPath &objectPath)
{
    foreach (BluetoothAdapter *adapter, m_adapters) {
        foreach (BluetoothDevice *thing, adapter->devices()) {
            if (thing->hasService(objectPath)) {
                return thing->getService(objectPath);
            }
        }
    }

    return nullptr;
}

BluetoothGattCharacteristic *BluetoothManager::findCharacteristic(const QDBusObjectPath &objectPath)
{
    foreach (BluetoothAdapter *adapter, m_adapters) {
        foreach (BluetoothDevice *thing, adapter->devices()) {
            foreach (BluetoothGattService *service, thing->services()) {
                if (service->hasCharacteristic(objectPath)) {
                    return service->getCharacteristic(objectPath);
                }
            }
        }
    }

    return nullptr;
}

void BluetoothManager::serviceRegistered(const QString &serviceName)
{
    qCDebug(dcBluez()) << "BluetoothManager: service registered" << serviceName;
    init();
}

void BluetoothManager::serviceUnregistered(const QString &serviceName)
{
    qCDebug(dcBluez()) << "BluetoothManager: service unregistered" << serviceName;
    if (serviceName == orgBluez)
        clean();
}

void BluetoothManager::onInterfaceAdded(const QDBusObjectPath &objectPath, const InterfaceList &interfaceList)
{
    //qCDebug(dcBluez()) << "Interface added" << objectPath.path();
    processInterfaceList(objectPath, interfaceList);
}

void BluetoothManager::onInterfaceRemoved(const QDBusObjectPath &objectPath, const QStringList &interfaces)
{
    //qCDebug(dcBluez()) << "Interface removed" << objectPath.path() << interfaces;

    // Adapter removed
    if (interfaces.contains(orgBluezAdapter1)) {
        BluetoothAdapter *adapter = findAdapter(objectPath);
        qCDebug(dcBluez()) << "[-]" << adapter;
        if (adapter) {
            m_adapters.removeOne(adapter);
            emit adapterRemoved(adapter);
            adapter->deleteLater();
        }
    }

    // Device removed
    if (interfaces.contains(orgBluezDevice1)) {
        // Find adapter for this thing
        foreach (BluetoothAdapter *adapter, m_adapters) {
            if (adapter->hasDevice(objectPath)) {
                adapter->removeDeviceInternally(objectPath);
            }
        }
    }

}
