// ============================================================================
// LUA GAME WRAPPER - IMPLEMENTATION
// ============================================================================
// Manages Lua VM lifecycle and script execution

#include "lua_game.h"
#include "lua_bindings.h"
#include "sd_card.h"
#include <stdio.h>
#include <string.h>

extern "C" {
    #include "ff.h"  // FatFS for SD card access
}

LuaGame::LuaGame(const char* script_path, uint16_t width, uint16_t height,
                 LowLevelRenderer* renderer, LowLevelGUI* gui, InputManager* input_manager)
    : Game(width, height, renderer, gui, input_manager),
      L(nullptr),
      script_path(script_path),
      loaded(false) {
    
    // Create new Lua state
    L = luaL_newstate();
    if (!L) {
        error_message = "Failed to create Lua state";
        printf("LuaGame: %s\n", error_message.c_str());
        return;
    }
    
    // Open standard Lua libraries (math, string, table, coroutine)
    luaL_openlibs(L);
    
    // Register game API bindings
    lua_bindings_register(L, this);
    
    // Load the script
    loaded = load_script();
    
    if (!loaded) {
        printf("LuaGame: Failed to load %s: %s\n", script_path, error_message.c_str());
    } else {
        printf("LuaGame: Successfully loaded %s\n", script_path);
    }
}

LuaGame::~LuaGame() {
    if (L) {
        lua_close(L);
        L = nullptr;
    }
}

bool LuaGame::load_script() {
    FIL fil;
    FRESULT fr;
    
    // Set SPI speed for SD card operations
    uint prev_speed = sd_card_set_spi_speed();
    
    // Open Lua script from SD card
    fr = f_open(&fil, script_path.c_str(), FA_READ);
    if (fr != FR_OK) {
        sd_card_restore_spi_speed(prev_speed);
        error_message = "Failed to open file (FatFS error: ";
        error_message += std::to_string((int)fr);
        error_message += ")";
        return false;
    }
    
    // Get file size
    FSIZE_t file_size = f_size(&fil);
    if (file_size == 0 || file_size > 64 * 1024) {  // Limit to 64KB
        f_close(&fil);
        sd_card_restore_spi_speed(prev_speed);
        error_message = "Script file size invalid (0 or > 64KB)";
        return false;
    }
    
    // Allocate buffer for script
    char* script_buffer = new char[file_size + 1];
    if (!script_buffer) {
        f_close(&fil);
        sd_card_restore_spi_speed(prev_speed);
        error_message = "Failed to allocate memory for script";
        return false;
    }
    
    // Read script into buffer
    UINT bytes_read;
    fr = f_read(&fil, script_buffer, file_size, &bytes_read);
    f_close(&fil);
    
    // Restore SPI speed immediately after file operations
    sd_card_restore_spi_speed(prev_speed);
    
    if (fr != FR_OK || bytes_read != file_size) {
        delete[] script_buffer;
        error_message = "Failed to read script file";
        return false;
    }
    
    script_buffer[file_size] = '\0';
    
    // Load script into Lua
    int result = luaL_loadbuffer(L, script_buffer, file_size, script_path.c_str());
    delete[] script_buffer;
    
    if (result != LUA_OK) {
        report_error("load script");
        return false;
    }
    
    // Execute script (loads functions into global namespace)
    result = lua_pcall(L, 0, 0, 0);
    if (result != LUA_OK) {
        report_error("execute script");
        return false;
    }
    
    return true;
}

void LuaGame::init() {
    if (!loaded) return;
    
    // Call Lua init() function if it exists
    lua_getglobal(L, "init");
    if (lua_isfunction(L, -1)) {
        call_lua_function("init", 0, 0);
    } else {
        lua_pop(L, 1);  // Pop non-function value
        printf("LuaGame: Warning - no init() function found\n");
    }
}

bool LuaGame::update(const InputEvent& event) {
    if (!loaded) return false;
    
    // Call Lua update(event) function if it exists
    lua_getglobal(L, "update");
    if (!lua_isfunction(L, -1)) {
        lua_pop(L, 1);
        return false;  // No update function, no redraw needed
    }
    
    // Push event table to Lua
    lua_newtable(L);
    
    lua_pushstring(L, "type");
    lua_pushinteger(L, (int)event.type);
    lua_settable(L, -3);
    
    lua_pushstring(L, "x");
    lua_pushinteger(L, event.x);
    lua_settable(L, -3);
    
    lua_pushstring(L, "y");
    lua_pushinteger(L, event.y);
    lua_settable(L, -3);
    
    lua_pushstring(L, "button_id");
    lua_pushinteger(L, event.button_id);
    lua_settable(L, -3);
    
    lua_pushstring(L, "valid");
    lua_pushboolean(L, event.valid);
    lua_settable(L, -3);
    
    // Call update(event) with 1 arg, expecting 1 result (needs_redraw)
    if (!call_lua_function("update", 1, 1)) {
        return false;
    }
    
    // Get return value (needs redraw?)
    bool needs_redraw = lua_toboolean(L, -1);
    lua_pop(L, 1);
    
    return needs_redraw;
}

void LuaGame::draw() {
    if (!loaded) return;
    
    // Call Lua draw() function if it exists
    lua_getglobal(L, "draw");
    if (lua_isfunction(L, -1)) {
        call_lua_function("draw", 0, 0);
    } else {
        lua_pop(L, 1);
    }
}

bool LuaGame::wants_to_exit() const {
    if (!L) return false;
    
    // Check if Lua script requested exit
    lua_pushstring(L, "__exit_requested");
    lua_gettable(L, LUA_REGISTRYINDEX);
    bool exit = lua_toboolean(L, -1);
    lua_pop(L, 1);
    
    return exit;
}

bool LuaGame::wants_frame_updates() const {
    if (!L) return false;
    
    // Check if Lua script wants continuous frame updates
    lua_pushstring(L, "__wants_frame_updates");
    lua_gettable(L, LUA_REGISTRYINDEX);
    bool wants_updates = lua_toboolean(L, -1);
    lua_pop(L, 1);
    
    return wants_updates;
}

bool LuaGame::call_lua_function(const char* func_name, int nargs, int nresults) {
    int result = lua_pcall(L, nargs, nresults, 0);
    if (result != LUA_OK) {
        report_error(func_name);
        return false;
    }
    return true;
}

void LuaGame::report_error(const char* context) {
    const char* msg = lua_tostring(L, -1);
    if (msg) {
        error_message = context;
        error_message += ": ";
        error_message += msg;
        printf("LuaGame Error [%s]: %s\n", context, msg);
    }
    lua_pop(L, 1);  // Pop error message
}
