#include "SiliconPulseSMC.h"

#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include <stdint.h>
#include <string.h>

#define KERNEL_INDEX_SMC 2
#define SMC_CMD_READ_BYTES 5
#define SMC_CMD_READ_KEYINFO 9
#define SMC_MAX_DATA_SIZE 32

typedef struct {
    uint8_t major;
    uint8_t minor;
    uint8_t build;
    uint8_t reserved;
    uint16_t release;
} SMCVersion;

typedef struct {
    uint16_t version;
    uint16_t length;
    uint32_t cpuPLimit;
    uint32_t gpuPLimit;
    uint32_t memPLimit;
} SMCPowerLimitData;

typedef struct {
    uint32_t dataSize;
    uint32_t dataType;
    uint8_t dataAttributes;
} SMCKeyInfo;

typedef struct {
    uint32_t key;
    SMCVersion vers;
    SMCPowerLimitData pLimitData;
    SMCKeyInfo keyInfo;
    uint8_t result;
    uint8_t status;
    uint8_t data8;
    uint32_t data32;
    uint8_t bytes[SMC_MAX_DATA_SIZE];
} SMCKeyData;

static uint32_t packKey(const char *key) {
    uint32_t packed = 0;
    for (int i = 0; i < 4 && key[i] != '\0'; i++) {
        packed |= ((uint32_t)(uint8_t)key[i]) << ((3 - i) * 8);
    }
    return packed;
}

static void unpackType(char *type, uint32_t packed) {
    type[0] = (char)((packed >> 24) & 0xff);
    type[1] = (char)((packed >> 16) & 0xff);
    type[2] = (char)((packed >> 8) & 0xff);
    type[3] = (char)(packed & 0xff);
    type[4] = '\0';
}

static kern_return_t openSMCServiceNamed(const char *serviceName, io_connect_t *connection) {
    CFMutableDictionaryRef matching = IOServiceMatching(serviceName);
    if (matching == NULL) {
        return kIOReturnNoMemory;
    }

    io_iterator_t iterator = IO_OBJECT_NULL;
    kern_return_t result = IOServiceGetMatchingServices(kIOMainPortDefault, matching, &iterator);
    if (result != kIOReturnSuccess) {
        return result;
    }

    io_object_t device = IOIteratorNext(iterator);
    IOObjectRelease(iterator);
    if (device == IO_OBJECT_NULL) {
        return kIOReturnNoDevice;
    }

    result = IOServiceOpen(device, mach_task_self(), 0, connection);
    IOObjectRelease(device);
    return result;
}

static kern_return_t openSMC(io_connect_t *connection) {
    kern_return_t result = openSMCServiceNamed("AppleSMCInterface", connection);
    if (result == kIOReturnSuccess) {
        return result;
    }
    return openSMCServiceNamed("AppleSMC", connection);
}

static kern_return_t callSMC(io_connect_t connection, SMCKeyData *input, SMCKeyData *output) {
    size_t outputSize = sizeof(SMCKeyData);
    return IOConnectCallStructMethod(
        connection,
        KERNEL_INDEX_SMC,
        input,
        sizeof(SMCKeyData),
        output,
        &outputSize);
}

static int readSMCKey(io_connect_t connection, const char *key, SMCKeyData *output) {
    SMCKeyData input;
    SMCKeyData keyInfoOutput;
    memset(&input, 0, sizeof(input));
    memset(&keyInfoOutput, 0, sizeof(keyInfoOutput));
    memset(output, 0, sizeof(*output));

    input.key = packKey(key);
    input.data8 = SMC_CMD_READ_KEYINFO;

    kern_return_t result = callSMC(connection, &input, &keyInfoOutput);
    if (result != kIOReturnSuccess || keyInfoOutput.result != 0) {
        return 0;
    }

    input.keyInfo.dataSize = keyInfoOutput.keyInfo.dataSize;
    input.data8 = SMC_CMD_READ_BYTES;

    result = callSMC(connection, &input, output);
    if (result != kIOReturnSuccess || output->result != 0) {
        return 0;
    }

    output->keyInfo = keyInfoOutput.keyInfo;
    return 1;
}

static int convertTemperature(const SMCKeyData *data, double *value) {
    char type[5];
    unpackType(type, data->keyInfo.dataType);

    if (strncmp(type, "flt ", 4) == 0 || strncmp(type, "flt", 3) == 0) {
        float floatValue = 0;
        memcpy(&floatValue, data->bytes, sizeof(floatValue));
        *value = (double)floatValue;
        return 1;
    }

    if (strncmp(type, "sp78", 4) == 0 && data->keyInfo.dataSize >= 2) {
        int16_t raw = (int16_t)((data->bytes[0] << 8) | data->bytes[1]);
        *value = (double)raw / 256.0;
        return 1;
    }

    if (strncmp(type, "ioft", 4) == 0 && data->keyInfo.dataSize >= 4) {
        int32_t raw = 0;
        memcpy(&raw, data->bytes, sizeof(raw));
        *value = (double)raw / 65536.0;
        return 1;
    }

    return 0;
}

int SiliconPulseSMCReadTemperature(const char *key, double *value) {
    if (key == NULL || value == NULL) {
        return 0;
    }

    const char *keys[1] = { key };
    int valid[1] = { 0 };
    double values[1] = { 0 };
    int count = SiliconPulseSMCReadTemperatures(keys, 1, values, valid);
    if (count == 1 && valid[0]) {
        *value = values[0];
        return 1;
    }
    return 0;
}

int SiliconPulseSMCReadTemperatures(const char **keys, int count, double *values, int *valid) {
    if (keys == NULL || count <= 0 || values == NULL || valid == NULL) {
        return 0;
    }

    for (int i = 0; i < count; i++) {
        values[i] = 0.0;
        valid[i] = 0;
    }

    io_connect_t connection = IO_OBJECT_NULL;
    kern_return_t result = openSMC(&connection);
    if (result != kIOReturnSuccess) {
        return 0;
    }

    int validCount = 0;
    for (int i = 0; i < count; i++) {
        if (keys[i] == NULL) {
            continue;
        }

        SMCKeyData data;
        double value = 0.0;
        int ok = readSMCKey(connection, keys[i], &data) && convertTemperature(&data, &value);
        if (ok && value > 0.0 && value <= 130.0) {
            values[i] = value;
            valid[i] = 1;
            validCount += 1;
        }
    }
    IOServiceClose(connection);

    return validCount;
}
