#include "setupapi_.h"

FAKE_DEVICE* fake_devices = NULL;

BOOL add_fake_device(const GUID* guid, const WCHAR* path) {
    FAKE_DEVICE* new_device = (FAKE_DEVICE*)malloc(sizeof *new_device);
    if (!new_device) return FALSE;
    new_device->guid = guid;
    new_device->path = path;
    new_device->handle = NULL;
    new_device->next = NULL;
    FAKE_DEVICE** tail = &fake_devices;
    while (*tail) tail = &((*tail)->next);
    *tail = new_device;
    return TRUE;
}

HDEVINFO WINAPI FakeSetupDiGetClassDevsA(const GUID* ClassGuid, PCWSTR Enumerator, HWND hwndParent, DWORD Flags) {
    log_misc(plfSetupAPI, "SetupDiGetClassDevsA(%p, %s, %d, %d)", ClassGuid, Enumerator, hwndParent, Flags);
    HDEVINFO res = TrueSetupDiGetClassDevsA(ClassGuid, Enumerator, hwndParent, Flags);

    if (res != INVALID_HANDLE_VALUE && ClassGuid != NULL) {
        FAKE_DEVICE* device = fake_devices;
        while (device != NULL) {
            if (memcmp(device->guid, ClassGuid, sizeof(*ClassGuid)) == 0) {
                log_misc(plfSetupAPI, "injecting %ls into class devs list", device->path);
                device->handle = res;
            }
            device = device->next;
        }
    } else {
        log_warning(plfSetupAPI, "upstream SetupDiGetClassDevsA failed: %d", GetLastError());
    }

    return res;
}

BOOL WINAPI FakeSetupDiEnumDeviceInterfaces(HDEVINFO DeviceInfoSet, PSP_DEVINFO_DATA DeviceInfoData,
                                            const GUID* InterfaceClassGuid, DWORD MemberIndex,
                                            PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData) {
    log_misc(plfSetupAPI, "SetupDiEnumDeviceInterfaces");
    FAKE_DEVICE* device = fake_devices;
    if (DeviceInterfaceData) {
        while (device != NULL) {
            if (device->handle == DeviceInfoSet) {
                log_info(plfSetupAPI, "hooking fake device: %ls", device->path);
                memcpy(&DeviceInterfaceData->InterfaceClassGuid, device->guid, sizeof *device->guid);
                DeviceInterfaceData->Flags = SPINT_ACTIVE;
                DeviceInterfaceData->Reserved = (ULONG_PTR)device->path;
                SetLastError(ERROR_SUCCESS);
                return TRUE;
            }
            device = device->next;
        }
    }

    // Fallback
    log_misc(plfSetupAPI, "device info fallthrough");
    return TrueSetupDiEnumDeviceInterfaces(DeviceInfoSet, DeviceInfoData, InterfaceClassGuid, MemberIndex,
                                           DeviceInterfaceData);
}

BOOL WINAPI FakeSetupDiGetDeviceInterfaceDetailA(HDEVINFO DeviceInfoSet, PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData,
                                                 PSP_DEVICE_INTERFACE_DETAIL_DATA_A DeviceInterfaceDetailData,
                                                 DWORD DeviceInterfaceDetailDataSize, PDWORD RequiredSize,
                                                 PSP_DEVINFO_DATA DeviceInfoData) {
    log_misc(plfSetupAPI, "SetupDiGetDeviceInterfaceDetailA");
    FAKE_DEVICE* device = fake_devices;
    while (device != NULL) {
        if (device->handle == DeviceInfoSet && (ULONG_PTR)device->path == DeviceInterfaceData->Reserved) {
            log_info(plfSetupAPI, "Intercepted SetupDiGetDeviceInterfaceDetailA");

            const WCHAR* res = (WCHAR*)DeviceInterfaceData->Reserved;
            int new_len = (wcslen(res) + 1) * (sizeof *res);
            char* new_res = (char*)malloc(new_len);
            size_t convertedChars = 0;
            wcstombs_s(&convertedChars, new_res, new_len, res, _TRUNCATE);

            size_t len = offsetof(SP_DEVICE_INTERFACE_DETAIL_DATA_W, DevicePath) + convertedChars;
            if (RequiredSize) *RequiredSize = len;
            if (!DeviceInterfaceDetailData && DeviceInterfaceDetailDataSize < len) {
                SetLastError(ERROR_INSUFFICIENT_BUFFER);
                return FALSE;
            }
            if (!DeviceInterfaceDetailData || sizeof *DeviceInterfaceDetailData != DeviceInterfaceDetailData->cbSize) {
                SetLastError(ERROR_INVALID_PARAMETER);
                return FALSE;
            }
            memcpy(DeviceInterfaceDetailData->DevicePath, new_res, len);
            free(new_res);
            SetLastError(ERROR_SUCCESS);
            return TRUE;
        }
        device = device->next;
    }

    log_misc(plfSetupAPI, "TrueSetupDiGetDeviceInterfaceDetailA fallthrough");
    return TrueSetupDiGetDeviceInterfaceDetailA(DeviceInfoSet, DeviceInterfaceData, DeviceInterfaceDetailData,
                                                DeviceInterfaceDetailDataSize, RequiredSize, DeviceInfoData);
}

BOOL WINAPI FakeSetupDiDestroyDeviceInfoList(HDEVINFO DevicesInfoSet) {
    FAKE_DEVICE* device = fake_devices;
    while (device != NULL) {
        if (device->handle == DevicesInfoSet) device->handle = NULL;
        device = device->next;
    }
    return TrueSetupDiDestroyDeviceInfoList(DevicesInfoSet);
}

void hook_setupapi() {
    hook("Setupapi.DLL", "SetupDiGetClassDevsA", FakeSetupDiGetClassDevsA, (void**)&TrueSetupDiGetClassDevsA);
    hook("Setupapi.DLL", "SetupDiEnumDeviceInterfaces", FakeSetupDiEnumDeviceInterfaces,
         (void**)&TrueSetupDiEnumDeviceInterfaces);
    hook("Setupapi.DLL", "SetupDiGetDeviceInterfaceDetailA", FakeSetupDiGetDeviceInterfaceDetailA,
         (void**)&TrueSetupDiGetDeviceInterfaceDetailA);
    hook("Setupapi.DLL", "SetupDiDestroyDeviceInfoList", FakeSetupDiDestroyDeviceInfoList,
         (void**)&TrueSetupDiDestroyDeviceInfoList);
}
