// 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 "bluetoothgattdescriptor.h"

#include <QDBusReply>

QString BluetoothGattDescriptor::name() const
{
    bool ok = false;
    quint16 typeId = m_uuid.toUInt16(&ok);

    if (ok) {
        QBluetoothUuid::DescriptorType type = static_cast<QBluetoothUuid::DescriptorType>(typeId);
        const QString name = QBluetoothUuid::descriptorToString(type);
        if (!name.isEmpty())
            return name;
    }

    return QString("Unknown Descriptor");
}

QBluetoothUuid BluetoothGattDescriptor::uuid() const
{
    return m_uuid;
}

QByteArray BluetoothGattDescriptor::value() const
{
    return m_value;
}

BluetoothGattDescriptor::Properties BluetoothGattDescriptor::properties() const
{
    return m_properties;
}

BluetoothGattDescriptor::BluetoothGattDescriptor(const QDBusObjectPath &path, const QVariantMap &properties, QObject *parent) :
    QObject(parent),
    m_path(path)
{
    m_descriptorInterface = new QDBusInterface(orgBluez, m_path.path(), orgBluezGattDescriptor1, QDBusConnection::systemBus(), this);
    if (!m_descriptorInterface->isValid()) {
        qCWarning(dcBluez()) << "Invalid DBus descriptor interface for" << m_path.path();
        return;
    }

    QDBusConnection::systemBus().connect(orgBluez, m_path.path(), "org.freedesktop.DBus.Properties", "PropertiesChanged", this, SLOT(onPropertiesChanged(QString,QVariantMap,QStringList)));

    processProperties(properties);

    QDBusPendingCall readingCall = m_descriptorInterface->asyncCall("GetAll", QVariantMap());
    QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(readingCall, this);
    connect(watcher, &QDBusPendingCallWatcher::finished, this, [=](){
        qCDebug(dcBluez()) << "Get descriptor properties finished";
    });
}

void BluetoothGattDescriptor::processProperties(const QVariantMap &properties)
{
    qCDebug(dcBluez()) << "Descriptor properties" << properties;
    foreach (const QString &propertyName, properties.keys()) {
        if (propertyName == "UUID") {
            m_uuid = QBluetoothUuid(properties.value(propertyName).toString());
        } else if (propertyName == "Value") {
            setValueInternally(properties.value(propertyName).toByteArray());
        } else if (propertyName == "Flags") {
            m_properties = parsePropertyFlags(properties.value(propertyName).toStringList());
        }
    }
}

void BluetoothGattDescriptor::setValueInternally(const QByteArray &value)
{
    if (m_value != value) {
        m_value = value;
        emit valueChanged(m_value);
    }
}

BluetoothGattDescriptor::Properties BluetoothGattDescriptor::parsePropertyFlags(const QStringList &descriptorProperties)
{
    Properties properties;

    foreach (const QString &propertyString, descriptorProperties) {
        if (propertyString == "read") {
            properties |= Read;
        } else if (propertyString == "write") {
            properties |= Write;
        } else if (propertyString == "encrypt-read") {
            properties |= EncryptRead;
        } else if (propertyString == "encrypt-write") {
            properties |= EncryptWrite;
        } else if (propertyString == "encrypt-authenticated-read") {
            properties |= EncryptAuthenticatedRead;
        } else if (propertyString == "encrypt-authenticated-write") {
            properties |= EncryptAuthenticatedWrite;
        } else if (propertyString == "secure-read") {
            properties |= SecureRead;
        } else if (propertyString == "secure-write") {
            properties |= SecureWrite;
        }
    }

    return properties;
}

void BluetoothGattDescriptor::onPropertiesChanged(const QString &interface, const QVariantMap &changedProperties, const QStringList &invalidatedProperties)
{
    if (interface != orgBluezGattDescriptor1)
        return;

    qCDebug(dcBluez()) << "BluetoothDescriptor:" << m_uuid << "properties changed" << interface << changedProperties << invalidatedProperties;
    processProperties(changedProperties);
}

void BluetoothGattDescriptor::onReadingFinished(QDBusPendingCallWatcher *call)
{
    QDBusPendingReply<QByteArray> reply = *call;
    if (reply.isError()) {
        qCWarning(dcBluez()) << "Could not read descriptor" << m_uuid.toString() << reply.error().name() << reply.error().message();
    } else {
        QByteArray value = reply.argumentAt<0>();
        qCDebug(dcBluez()) << "Async descriptor reading finished for" << m_uuid.toString() << value;
        setValueInternally(value);
        emit readingFinished(value);
    }

    call->deleteLater();
}

void BluetoothGattDescriptor::onWritingFinished(QDBusPendingCallWatcher *call)
{
    QByteArray value = m_asyncWrites.take(call);
    QDBusPendingReply<void> reply = *call;
    if (reply.isError()) {
        qCWarning(dcBluez()) << "Could not write descriptor" << m_uuid.toString() << reply.error().name() << reply.error().message();
    } else {
        qCDebug(dcBluez()) << "Async descriptor writing finished for" << m_uuid.toString() << value;
        emit writingFinished(value);
    }

    call->deleteLater();
}

bool BluetoothGattDescriptor::readValue()
{
    if (!m_descriptorInterface->isValid()) {
        qCWarning(dcBluez()) << "Invalid DBus characteristic interface for" << m_path.path();
        return false;
    }

    QDBusPendingCall readingCall = m_descriptorInterface->asyncCall("ReadValue", QVariantMap());
    QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(readingCall, this);
    connect(watcher, &QDBusPendingCallWatcher::finished, this, &BluetoothGattDescriptor::onReadingFinished);
    return true;
}

bool BluetoothGattDescriptor::writeValue(const QByteArray &value)
{
    if (!m_descriptorInterface->isValid()) {
        qCWarning(dcBluez()) << "Invalid DBus characteristic interface for" << m_path.path();
        return false;
    }

    QDBusPendingCall writingCall = m_descriptorInterface->asyncCall("WriteValue", value, QVariantMap());
    QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(writingCall, this);
    m_asyncWrites.insert(watcher, value);
    connect(watcher, &QDBusPendingCallWatcher::finished, this, &BluetoothGattDescriptor::onWritingFinished);
    return true;
}

QDebug operator<<(QDebug debug, BluetoothGattDescriptor *descriptor)
{
    debug.noquote().nospace() << "GattDescriptor(" << descriptor->name();
    debug.noquote().nospace() << ", " << descriptor->uuid().toString();
    debug.noquote().nospace() << ", Properties: ";

    if (descriptor->properties().testFlag(BluetoothGattDescriptor::Read))
        debug.noquote().nospace() << " R ";

    if (descriptor->properties().testFlag(BluetoothGattDescriptor::Write))
        debug.noquote().nospace() << " W";

    if (descriptor->properties().testFlag(BluetoothGattDescriptor::EncryptRead))
        debug.noquote().nospace() << " ER";

    if (descriptor->properties().testFlag(BluetoothGattDescriptor::EncryptWrite))
        debug.noquote().nospace() << " EW";

    if (descriptor->properties().testFlag(BluetoothGattDescriptor::EncryptAuthenticatedRead))
        debug.noquote().nospace() << " EAR";

    if (descriptor->properties().testFlag(BluetoothGattDescriptor::EncryptAuthenticatedWrite))
        debug.noquote().nospace() << " EAW";


    if (descriptor->properties().testFlag(BluetoothGattDescriptor::SecureRead))
        debug.noquote().nospace() << " SR";

    if (descriptor->properties().testFlag(BluetoothGattDescriptor::SecureWrite))
        debug.noquote().nospace() << " SW";

    debug.noquote().nospace() << ", value: " << descriptor->value();
    debug.noquote().nospace() << ") ";
    return debug;
}
