#include "../hooks/setupapi_.h"
#include "../lib/dmi/dmi.h"
#include "mx.h"
#include "smbus.h"

smbus_callback_t* smbus_devices[256] = { NULL };

void smbus_install(BYTE v_addr, smbus_callback_t* write, smbus_callback_t* read) {
    smbus_devices[v_addr << 1] = write;
    smbus_devices[(v_addr << 1) + 1] = read;
}

BOOL handle_smbus(BYTE command, WORD v_addr, WORD command_code, BYTE nbytes, BYTE* data) {
    WORD p_addr = (v_addr & 0x7fff) << 1;
    ich9_cmd_t smb_cmd = ICH9_CMD_QUICK;
    switch (command) {
        case MXSMBUS_CMD_WRITE_QUICK:
            break;
        case MXSMBUS_CMD_READ_QUICK:
            p_addr++;
            break;
        case MXSMBUS_CMD_WRITE_BYTE:
            smb_cmd = ICH9_CMD_BYTE;
            break;
        case MXSMBUS_CMD_READ_BYTE:
            p_addr++;
            smb_cmd = ICH9_CMD_BYTE;
            break;
        case MXSMBUS_CMD_WRITE_BYTE_DATA:
            smb_cmd = ICH9_CMD_BYTE_DATA;
            break;
        case MXSMBUS_CMD_READ_BYTE_DATA:
            p_addr++;
            smb_cmd = ICH9_CMD_BYTE_DATA;
            break;
        case MXSMBUS_CMD_WRITE_WORD_DATA:
            smb_cmd = ICH9_CMD_WORD_DATA;
            break;
        case MXSMBUS_CMD_READ_WORD_DATA:
            p_addr++;
            smb_cmd = ICH9_CMD_WORD_DATA;
            break;
        case MXSMBUS_CMD_WRITE_BLOCK:
            smb_cmd = ICH9_CMD_BLOCK;
            break;
        case MXSMBUS_CMD_READ_BLOCK:
            p_addr++;
            smb_cmd = ICH9_CMD_BLOCK;
            break;
        case MXSMBUS_CMD_PROCESS_CALL:
            smb_cmd = ICH9_CMD_PROCESS_CALL;
            break;
        case MXSMBUS_CMD_I2C:
            smb_cmd = ICH9_CMD_I2C_READ;
            break;
    }

    log_trace(plfMxSmbus, "Making request to %02X (virtual: %04X/%04x, cmd: %01X, code: %04X)", p_addr,
              v_addr, command, smb_cmd, command_code);

    smbus_callback_t* callback = smbus_devices[p_addr];
    if (callback == NULL) {
        log_error(plfMxSmbus, "Request to unregistered address: %02x", p_addr);
        return FALSE;
    }
    return (*callback)(smb_cmd, command_code, nbytes, data);
}

BOOL WINAPI mxsmbus_DeviceIoControl(file_context_t* ctx, DWORD dwIoControlCode, LPVOID lpInBuffer,
                             DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize,
                             LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped) {
    // Default value
    if (lpBytesReturned) *lpBytesReturned = nOutBufferSize;

    switch (dwIoControlCode) {
        case IOCTL_MXSMBUS_GET_VERSION:
            log_misc(plfMxSmbus,
                     "DeviceIoControl(<mxsmbus>, <get version>, 0x%p, 0x%x, "
                     "-, 0x%x, -, -)",
                     lpInBuffer, nInBufferSize, nOutBufferSize);

            ((LPDWORD)lpOutBuffer)[0] = 0x01020001;
            if (lpBytesReturned) *lpBytesReturned = 4;
            break;
        case IOCTL_MXSMBUS_I2C: {
            // BYTE command = ((BYTE*)lpInBuffer)[1];
            PMXSMBUS_I2C_PACKET packet = (PMXSMBUS_I2C_PACKET)lpInBuffer;
            BYTE command = packet->command;
            if (command > 10) {
                ((BYTE*)lpOutBuffer)[0] = 0x19;
                return FALSE;
            }
            if (handle_smbus(command, packet->v_addr & 0x7fff, packet->command_code, packet->nbytes, packet->data)) {
                ((BYTE*)lpOutBuffer)[0] = 0x00;
                return TRUE;
            } else {
                ((BYTE*)lpOutBuffer)[0] = 0x01;
                return FALSE;
            };
        }
        case IOCTL_MXSMBUS_REQUEST: {
            // BYTE command = ((BYTE*)lpInBuffer)[1];
            PMXSMBUS_REQUEST_PACKET packet = (PMXSMBUS_REQUEST_PACKET)lpInBuffer;
            BYTE command = packet->command;
            if (command > 11) {
                ((BYTE*)lpOutBuffer)[0] = 0x19;
                return FALSE;
            }
            if (handle_smbus(command, packet->v_addr & 0x7f, packet->command_code, packet->nbytes, packet->data)) {
                ((BYTE*)lpOutBuffer)[0] = 0x00;
                return TRUE;
            } else {
                ((BYTE*)lpOutBuffer)[0] = 0x01;
                return FALSE;
            };
        }
        default:
            log_warning(plfMxSmbus, "unhandled 0x%08x", dwIoControlCode);
            return FALSE;
    }
    return TRUE;
}

void setup_mxsmbus() {
    file_hook_t* mxsmbus = new_file_hook(L"\\\\.\\mxsmbus");
    mxsmbus->DeviceIoControl = &mxsmbus_DeviceIoControl;

    hook_file(mxsmbus);

    if (!add_fake_device(&MXSMBUS_GUID, L"\\\\.\\mxsmbus")) {
        log_error(plfMxSmbus, "failed to install mxsmbus device");
    }
}
