#include "bluetoothradio.h"
#include "common/strutil.h"

#ifdef FF_HAVE_DBUS
    #include "common/dbus.h"
    #include "common/io.h"

/* Example dbus reply:
array [
    dict entry(
        string "Address"
        variant string "XX:XX:XX:XX:XX:XX"
    )
    dict entry(
        string "Name"
        variant string "xxxxxxxx"
    )
    dict entry(
        string "Powered"
        variant boolean true
    )
    dict entry(
        string "PowerState"
        variant string "on"
    )
    dict entry(
        string "Manufacturer"
        variant uint16 2
    )
    dict entry(
        string "Version"
        variant byte 12
    )
]
*/

static const char* detectBluetoothProperty(FFBluetoothRadioResult* device, FFDBusData* dbus, DBusMessageIter* iter) {
    if (dbus->lib->ffdbus_message_iter_get_arg_type(iter) != DBUS_TYPE_DICT_ENTRY) {
        return "Expected dict entry";
    }

    DBusMessageIter dictIter;
    dbus->lib->ffdbus_message_iter_recurse(iter, &dictIter);

    if (dbus->lib->ffdbus_message_iter_get_arg_type(&dictIter) != DBUS_TYPE_STRING) {
        return "Expected dict entry key to be a string";
    }

    const char* deviceProperty;
    dbus->lib->ffdbus_message_iter_get_basic(&dictIter, &deviceProperty);

    dbus->lib->ffdbus_message_iter_next(&dictIter);

    if (ffStrEquals(deviceProperty, "Address")) {
        ffDBusGetString(dbus, &dictIter, &device->address);
    } else if (ffStrEquals(deviceProperty, "Alias")) {
        ffDBusGetString(dbus, &dictIter, &device->name);
    } else if (ffStrEquals(deviceProperty, "Manufacturer")) {
        uint64_t vendorId;
        if (ffDBusGetUint(dbus, &dictIter, &vendorId)) {
            ffStrbufSetStatic(&device->vendor, ffBluetoothRadioGetVendor((uint32_t) vendorId));
        }
    } else if (ffStrEquals(deviceProperty, "Version")) {
        uint64_t version;
        if (ffDBusGetUint(dbus, &dictIter, &version)) {
            device->lmpVersion = (int32_t) version;
        }
    } else if (ffStrEquals(deviceProperty, "Powered")) {
        ffDBusGetBool(dbus, &dictIter, &device->enabled);
    } else if (ffStrEquals(deviceProperty, "Discoverable")) {
        ffDBusGetBool(dbus, &dictIter, &device->discoverable);
    } else if (ffStrEquals(deviceProperty, "Pairable")) {
        ffDBusGetBool(dbus, &dictIter, &device->connectable);
    }

    return nullptr;
}

static const char* detectBluetoothRoot(FFBluetoothRadioResult* device, const char* hciName, FFDBusData* dbus) {
    char objPath[300];
    snprintf(objPath, sizeof(objPath), "/org/bluez/%s", hciName);

    DBusMessage* properties = ffDBusGetMethodReply(dbus, "org.bluez", objPath, "org.freedesktop.DBus.Properties", "GetAll", "org.bluez.Adapter1", nullptr);
    if (!properties) {
        return "Failed to call org.freedesktop.DBus.Properties.GetAll";
    }

    DBusMessageIter rootIter;
    if (!dbus->lib->ffdbus_message_iter_init(properties, &rootIter)) {
        dbus->lib->ffdbus_message_unref(properties);
        return "Failed to get root iterator of org.freedesktop.DBus.Properties.GetAll";
    }

    if (dbus->lib->ffdbus_message_iter_get_arg_type(&rootIter) != DBUS_TYPE_ARRAY) {
        dbus->lib->ffdbus_message_unref(properties);
        return "Expected array";
    }

    DBusMessageIter arrayIter;
    dbus->lib->ffdbus_message_iter_recurse(&rootIter, &arrayIter);

    do {
        detectBluetoothProperty(device, dbus, &arrayIter);
    } while (dbus->lib->ffdbus_message_iter_next(&arrayIter));

    dbus->lib->ffdbus_message_unref(properties);
    return nullptr;
}

static const char* detectBluetooth(FFlist* devices) {
    FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/bluetooth");
    if (dirp == nullptr) {
        return "Failed to open /sys/class/bluetooth";
    }

    FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {};
    const char* error = ffDBusLoadData(DBUS_BUS_SYSTEM, &dbus);
    if (error) {
        return error;
    }

    struct dirent* entry;
    while ((entry = readdir(dirp)) != nullptr) {
        if (entry->d_name[0] == '.') {
            continue;
        }

        if (strchr(entry->d_name, ':') != nullptr) { // ignore connected devices
            continue;
        }

        FFBluetoothRadioResult* device = FF_LIST_ADD(FFBluetoothRadioResult, *devices);
        ffStrbufInit(&device->name);
        ffStrbufInit(&device->address);
        ffStrbufInitStatic(&device->vendor, "Unknown");
        device->lmpVersion = INT_MIN;
        device->lmpSubversion = INT_MIN;
        device->enabled = false;
        detectBluetoothRoot(device, entry->d_name, &dbus);
    }
    return nullptr;
}

#endif

const char* ffDetectBluetoothRadio(FFlist* devices /* FFBluetoothRadioResult */) {
#ifdef FF_HAVE_DBUS
    return detectBluetooth(devices);
#else
    FF_UNUSED(devices)
    return "Fastfetch was compiled without DBus support";
#endif
}
