#ifndef NO_LUA
#include "gm9internali2c.h"
#include "fsperm.h"
#include "system/i2c.h"

typedef struct {
    uint8_t dev_id;
    uint8_t reg_addr_start;
    uint8_t reg_addr_end;
} WhiteListPair;

static const WhiteListPair WriteWhitelist[] = {
    // Device_id, Register_address, Register_address_end
    { I2C_DEV_MCU, 0x15, 0x17 },
    { I2C_DEV_MCU, 0x1c, 0x1f },
    { I2C_DEV_MCU, 0x28, 0x29 },
    { I2C_DEV_MCU, 0x2a, 0x2d },
};

static bool IsWriteAllowed(uint8_t dev_id, uint8_t reg_addr)
{
    for (unsigned i = 0; i < sizeof(WriteWhitelist)/sizeof(WriteWhitelist[0]); i++) {
        if (WriteWhitelist[i].dev_id == dev_id &&
            reg_addr >= WriteWhitelist[i].reg_addr_start &&
            reg_addr <= WriteWhitelist[i].reg_addr_end)
            return true;
    }
    return false;
}

static int i2c_read(lua_State *L) {
    int dev_id = luaL_checkinteger(L, 1);
    int reg_addr = luaL_checkinteger(L, 2);
    int length = luaL_checkinteger(L, 3);

    if (dev_id < 0 || dev_id > 17) {
        return luaL_error(L, "Invalid device ID: %d (must be 0-17)", dev_id);
    }

    if (length <= 0 || length > 64) {
        return luaL_error(L, "Invalid length: %d (must be 1-64)", length);
    }

    if (reg_addr < 0 || reg_addr > 255) {
        return luaL_error(L, "Invalid register address: %d (must be 0-255)", reg_addr);
    }

    // Create a buffer for the read data
    u8 *buffer = malloc(length);
    if (!buffer) {
        return luaL_error(L, "Memory allocation failed");
    }
    bool success = I2C_readRegBuf((I2cDevice)dev_id, (u8)reg_addr, buffer, (u32)length);

    if (success) {
        // Create a Lua table to hold the byte values
        lua_createtable(L, length, 0);

        // Fill the table with byte values as integers
        for (int i = 0; i < length; i++) {
            lua_pushinteger(L, buffer[i]);  // Push byte value as integer
            lua_rawseti(L, -2, i + 1);      // Set table[i+1] = byte
        }

        free(buffer);
        return 1;
    } else {
        free(buffer);
        return luaL_error(L, "I2C read failed");
    }
}

static int i2c_write(lua_State *L) {
    SetWritePermissionsLuaError(L, PERM_MEMORY);

    int dev_id = luaL_checkinteger(L, 1);
    int reg_addr = luaL_checkinteger(L, 2);

    if (!lua_istable(L, 3)) {
        return luaL_error(L, "Third parameter must be a table of byte values");
    }

    int length = lua_rawlen(L, 3);

    if (dev_id < 0 || dev_id > 17) {
        return luaL_error(L, "Invalid device ID: %d (must be 0-17)", dev_id);
    }

    if (length <= 0 || length > 64) {
        return luaL_error(L, "Invalid data length: %d (must be 1-64)", length);
    }

    if (reg_addr < 0 || reg_addr > 255) {
        return luaL_error(L, "Invalid register address: %d (must be 0-255)", reg_addr);
    }

    if (!IsWriteAllowed(dev_id, reg_addr)) {
        return luaL_error(L, "Write to device %d, register %d is not allowed", dev_id, reg_addr);
    }

    // Create a buffer for the write data
    u8 *buffer = malloc(length);
    if (!buffer) {
        return luaL_error(L, "Memory allocation failed");
    }

    // Extract byte values from Lua table
    for (int i = 0; i < length; i++) {
        lua_rawgeti(L, 3, i + 1);  // Get table[i+1]

        if (!lua_isinteger(L, -1)) {
            free(buffer);
            return luaL_error(L, "Table element %d is not an integer", i + 1);
        }

        int value = lua_tointeger(L, -1);
        if (value < 0 || value > 255) {
            free(buffer);
            return luaL_error(L, "Table element %d is out of range: %d (must be 0-255)", i + 1, value);
        }

        buffer[i] = (u8)value;
        lua_pop(L, 1);  // Remove the value from stack
    }

    bool success = I2C_writeRegBuf((I2cDevice)dev_id, (u8)reg_addr, buffer, (u32)length);

    free(buffer);

    if (success) {
        return 0;
    } else {
        return luaL_error(L, "I2C write failed");
    }
}

static const luaL_Reg i2c[] = {
    {"read", i2c_read},
    {"write", i2c_write},
    {NULL, NULL}
};

int gm9lua_open_internali2c(lua_State* L) {
    luaL_newlib(L, i2c);
    return 1;
}
#endif
