#include "bluetoothradio.h"
#include "common/library.h"
#include "common/io.h"
#include "common/windows/unicode.h"

#include <windows.h>
#include <bluetoothapis.h>
#include <winioctl.h>

// #include <bthioctl.h>

#define BTH_IOCTL_BASE              0
#define BTH_CTL(id)                 CTL_CODE(FILE_DEVICE_BLUETOOTH, (id), METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_BTH_GET_LOCAL_INFO    BTH_CTL(BTH_IOCTL_BASE+0x00)
#define LMP_LE_SUPPORTED(x)         ((x >> 38) & 1)

typedef struct _BTH_RADIO_INFO
{
    // Supported LMP features of the radio.  Use LMP_XXX() to extract
    // the desired bits.
    ULONGLONG lmpSupportedFeatures;

    // Manufacturer ID (possibly BTH_MFG_XXX)
    USHORT mfg;

    // LMP subversion
    USHORT lmpSubversion;

    // LMP version
    UCHAR lmpVersion;
} __attribute__((__packed__)) BTH_RADIO_INFO;

typedef struct _BTH_LOCAL_RADIO_INFO
{
    // Local BTH_ADDR, class of device, and radio name
    BTH_DEVICE_INFO         localInfo;

    // Combo of LOCAL_RADIO_XXX values
    ULONG flags;

    // HCI revision, see core spec
    USHORT hciRevision;

    // HCI version, see core spec
    UCHAR hciVersion;

    // More information about the local radio (LMP, MFG)
    BTH_RADIO_INFO radioInfo;
} __attribute__((__packed__)) BTH_LOCAL_RADIO_INFO;
static_assert(sizeof(BTH_LOCAL_RADIO_INFO) == 292, "BTH_LOCAL_RADIO_INFO should be 292 bytes");

#pragma GCC diagnostic ignored "-Wpointer-sign"

const char* ffDetectBluetoothRadio(FFlist* devices /* FFBluetoothRadioResult */)
{
    // Actually bluetoothapis.dll, but it's missing on Windows 7
    FF_LIBRARY_LOAD_MESSAGE(bluetoothapis, "bthprops.cpl", 1)
    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindFirstRadio)
    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindNextRadio)
    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindRadioClose)
    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothIsConnectable)
    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothIsDiscoverable)

    HANDLE hRadio = NULL;
    HBLUETOOTH_DEVICE_FIND hFind = ffBluetoothFindFirstRadio(&(BLUETOOTH_FIND_RADIO_PARAMS) {
        .dwSize = sizeof(BLUETOOTH_FIND_RADIO_PARAMS)
    }, &hRadio);
    if(!hFind)
    {
        if (GetLastError() == ERROR_NO_MORE_ITEMS)
            return "No Bluetooth radios found or service disabled";
        else
            return "BluetoothFindFirstRadio() failed";
    }

    do {
        BTH_LOCAL_RADIO_INFO blri;
        DWORD returned;
        if (!DeviceIoControl(hRadio, IOCTL_BTH_GET_LOCAL_INFO, NULL, 0, &blri, sizeof(blri), &returned, NULL))
            continue;

        FFBluetoothRadioResult* device = ffListAdd(devices);
        ffStrbufInitS(&device->name, blri.localInfo.name);

        BLUETOOTH_ADDRESS_STRUCT addr = { .ullLong = blri.localInfo.address };
        ffStrbufInitF(&device->address, "%02X:%02X:%02X:%02X:%02X:%02X",
            addr.rgBytes[5],
            addr.rgBytes[4],
            addr.rgBytes[3],
            addr.rgBytes[2],
            addr.rgBytes[1],
            addr.rgBytes[0]);

        device->lmpVersion = blri.radioInfo.lmpVersion;
        device->lmpSubversion = blri.radioInfo.lmpSubversion;
        ffStrbufInitStatic(&device->vendor, ffBluetoothRadioGetVendor(blri.radioInfo.mfg));
        device->enabled = true;
        device->connectable = ffBluetoothIsConnectable(hRadio);
        device->discoverable = ffBluetoothIsDiscoverable(hRadio);

        CloseHandle(hRadio);
    } while (ffBluetoothFindNextRadio(hFind, &hRadio));

    ffBluetoothFindRadioClose(hFind);

    return NULL;
}
