#include "twn_game_api.h"
#include "state.h"

#include <SDL2/SDL.h>

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

#include <malloc.h>


/* generated by bindgen.py */
void bindgen_load_twn(lua_State *L);
void bindgen_unload_twn(lua_State *L);


/* require will go through physicsfs exclusively so that scripts can be in the data dir */
static int physfs_loader(lua_State *L) {
    const char *name = luaL_checkstring(L, 1);
    char *final_path = NULL;
    SDL_asprintf(&final_path, "%s.lua", name);

    if (!file_exists(final_path)) {
        char *error_message = NULL;
        SDL_asprintf(&error_message, "could not find module %s in filesystem", name);
        lua_pushstring(L, error_message);
        free(error_message);

        free(final_path);
        return 1;
    }

    unsigned char *buf = NULL;
    int64_t buf_size = file_to_bytes(final_path, &buf);
    free(final_path);

    luaL_loadbuffer(L, (char *)buf, buf_size, name);
    free(buf);

    return 1;
}


void game_tick(void) {
    if (ctx.initialization_needed) {
        if (!ctx.udata)
            ctx.udata = ccalloc(1, sizeof (State));

        State *state = ctx.udata;

        /* let's init lua */
        /* state existed already */
        if (state->L != NULL) {
            lua_close(state->L);
        }
        state->L = luaL_newstate();

        /* fakey version of luaL_openlibs() that excludes file i/o and os stuff */
        {
            static const luaL_Reg loaded_libs[] = {
               { LUA_GNAME, luaopen_base },
               { LUA_LOADLIBNAME, luaopen_package },
               { LUA_COLIBNAME, luaopen_coroutine },
               { LUA_TABLIBNAME, luaopen_table },
               { LUA_STRLIBNAME, luaopen_string },
               { LUA_MATHLIBNAME, luaopen_math },
               { LUA_UTF8LIBNAME, luaopen_utf8 },
               { LUA_DBLIBNAME, luaopen_debug },
               { NULL, NULL },
            };

            for (const luaL_Reg *lib = loaded_libs; lib->func; ++lib) {
                luaL_requiref(state->L, lib->name, lib->func, true);
                lua_pop(state->L, 1); /* already stored, don't need the copy */
            }
        }

        /* package.searchers = { physfs_loader } */
        lua_getglobal(state->L, "package");
        lua_newtable(state->L);
        lua_setfield(state->L, -2, "searchers");

        lua_getfield(state->L, -1, "searchers");
        lua_pushcfunction(state->L, physfs_loader);
        lua_seti(state->L, -2, 1);

        /* pop package, package.searchers */
        lua_pop(state->L, 2);

        /* binding */
        // lua_register(state->L, "sprite", b_sprite);
        // lua_register(state->L, "rectangle", b_rectangle);
        // lua_register(state->L, "text", b_text);

        bindgen_load_twn(state->L);

        /* now finally get to running the code */
        unsigned char *game_buf = NULL;
        size_t game_buf_size = file_to_bytes("/scripts/game.lua", &game_buf);
        if (luaL_loadbuffer(state->L, (char *)game_buf, game_buf_size, "game.lua") == LUA_OK) {
            if (lua_pcall(state->L, 0, 0, 0) != LUA_OK) {
                log_critical("%s", lua_tostring(state->L, -1));
                lua_pop(state->L, 1);
            }
        }
        free(game_buf);
        /* from this point we have access to everything defined in lua */
    }

    State *state = ctx.udata;

    lua_getglobal(state->L, "game_tick");
    if (lua_pcall(state->L, 0, 0, 0) != LUA_OK) {
        log_critical("%s", lua_tostring(state->L, -1));
        lua_pop(state->L, 1);
    }
}


void game_end(void) {
    State *state = ctx.udata;
    bindgen_unload_twn(state->L);
    lua_close(state->L);
    free(state);
}
