#include "lua.wrappers.h"
#include "external/lua/lua.hpp"

#include "mini/mini.h"
#include "mini/surf/surf.h"
#include "mini/draw/draw.h"
#include "mini/map/map.h"
#include "mini/pal/pal.h"
#include "mini/font/font.h"
#include "mini/win/win.h"
#include "mini/view/view.h"
#include "mini/shader/shader.h"
#include "mini/audio/audio.h"
#include "mini/config/config.h"
#include "mini/mouse/mouse.h"
#include "mini/key/key.h"
#include "mini/pad/pad.h"

#include "mini/version.h"

#include "backends/backend.h"

#include <filesystem>
#include <vector>
#include <algorithm>

namespace mini
{
    namespace lua
    {
        namespace wrappers
        {
            static int utf8_sub(lua_State *L) {
                size_t slen;
                const char *s = luaL_checklstring(L, 1, &slen);
                int i = luaL_checkinteger(L, 2);
                int j = luaL_optinteger(L, 3, -1);

                // Get utf8.offset
                lua_getglobal(L, "utf8");
                lua_getfield(L, -1, "offset");

                // Compute start byte index
                lua_pushvalue(L, 1);       // string
                lua_pushinteger(L, i);     // start index
                lua_call(L, 2, 1);         // utf8.offset(s, i)
                lua_Integer start = lua_tointeger(L, -1);
                lua_pop(L, 1);

                if (start == 0) {
                    lua_pushliteral(L, "");
                    lua_pop(L, 1);         // pop utf8 table
                    return 1;
                }

                // Compute end byte index (j+1)
                lua_getfield(L, -1, "offset");
                lua_pushvalue(L, 1);
                lua_pushinteger(L, j + 1);
                lua_call(L, 2, 1);
                lua_Integer end = lua_tointeger(L, -1);
                lua_pop(L, 2);             // pop result + utf8 table

                if (end == 0) {
                    // until end of string
                    lua_pushlstring(L, s + start - 1, slen - (start - 1));
                    return 1;
                }

                lua_pushlstring(L, s + start - 1, (end - 1) - (start - 1));
                return 1;
            }

            namespace surf
            {
                static int create(lua_State *L) {
                    int w = luaL_checknumber(L, 1);
                    int h = luaL_checknumber(L, 2);
                    uint8_t s = mini::surf::create(w, h);
                    if (s==255) {
                        luaL_error(L, "Error while creating new surface: Max surfaces reached");
                        return 0;
                    }
                    lua_pushinteger(L, s);
                    return 1;
                }

                static int load(lua_State *L) {
                    const char* str = luaL_checkstring(L, 1);
                    uint8_t s = mini::surf::load(str);
                    if (s==255) {
                        luaL_error(L, "Error while loading surface: Max surfaces reached");
                        return 0;
                    }
                    lua_pushinteger(L, s);
                    return 1;
                }

                static int loadex(lua_State *L) {
                    const char* str = luaL_checkstring(L, 1);
                    uint8_t s = mini::surf::load(str, true);
                    if (s==255) {
                        luaL_error(L, "Error while loading surface: Max surfaces reached");
                        return 0;
                    }
                    lua_pushinteger(L, s);
                    return 1;
                }

                static int save(lua_State *L) {
                    uint8_t surface = luaL_checkinteger(L, 1);
                    const char* str = luaL_checkstring(L, 2);

                    if (lua_istable(L, -1)) {
                        int len = lua_rawlen(L, -1);
                        if (len > 256) len = 256;
                        uint8_t *pal = (uint8_t*)malloc(len*3);
                        uint8_t *p=pal;
                        for (int i=1;i<=len;++i) {
                            lua_rawgeti(L, -1, i);
                            lua_getfield(L, -1, "r");
                            *(p++) = luaL_checknumber(L, -1);
                            lua_pop(L, 1);
                            lua_getfield(L, -1, "g");
                            *(p++) = luaL_checknumber(L, -1);
                            lua_pop(L, 1);
                            lua_getfield(L, -1, "b");
                            *(p++) = luaL_checknumber(L, -1);
                            lua_pop(L, 1);
                            lua_pop(L, 1);
                            //pal[i-1] = (r<<16)+(g<<8)+b;
                        }
                        mini::surf::save(surface, str, pal, len);
                    } else {
                        mini::surf::save(surface, str, nullptr);
                    }

                    return 0;
                }

                static int free(lua_State *L) {
                    uint8_t surface = luaL_checkinteger(L, 1);
                    mini::surf::destroy(surface);
                    return 0;
                }

                static int size(lua_State *L) {
                    uint8_t surface = luaL_checkinteger(L, 1);
                    lua_pushinteger(L, mini::surf::width(surface));
                    lua_pushinteger(L, mini::surf::height(surface));
                    return 2;
                }

                static int target(lua_State *L) {
                    const int numargs = lua_gettop(L);
                    switch (numargs) {
                        case 0: {
                            lua_pushinteger(L, mini::surf::target::get());
                            return 1;
                        }
                        case 1: {
                            uint8_t surface = luaL_checkinteger(L, 1);
                            mini::surf::target::set(surface);
                            return 0;
                        }
                        default:
                            return luaL_error(L, "Function 'surface.target' Unexpected number of parameters.");
                    };
                }

                static int source(lua_State *L) {
                    const int numargs = lua_gettop(L);
                    switch (numargs) {
                        case 0: {
                            lua_pushinteger(L, mini::surf::source::get());
                            return 1;
                        }
                        case 1: {
                            uint8_t surface = luaL_checkinteger(L, 1);
                            mini::surf::source::set(surface);
                            return 0;
                        }
                        default:
                            return luaL_error(L, "Function 'surface.source' Unexpected number of parameters.");
                    };
                }

                static int cls(lua_State *L) {
                    uint8_t color = luaL_optinteger(L, 1, 0);
                    mini::surf::cls(color);
                    return 0;
                }

                static int clip(lua_State *L) {
                    if (lua_gettop(L)==0) {
                        mini::surf::clip::reset();
                        return 0;
                    } else {
                        int x = luaL_checknumber(L, 1);
                        int y = luaL_checknumber(L, 2);
                        int w = luaL_checknumber(L, 3);
                        int h = luaL_checknumber(L, 4);
                        mini::surf::clip::set(x, y, w, h);
                        return 0;
                    }
                }
            }

            namespace map
            {
                static int surf(lua_State *L) {
                    if (lua_gettop(L)==1) {
                        uint8_t surface = luaL_checkinteger(L, 1);
                        mini::map::surf::set(surface);
                    } else {
                        lua_pushinteger(L, mini::map::surf::get());
                        return 1;
                    }

                    return 0;
                }

                static int draw(lua_State *L) {
                    mini::map::draw();
                    return 0;
                }

                static int tile(lua_State *L) {
                    if (lua_gettop(L)==2) {
                        int celx = luaL_checknumber(L, 1);
                        int cely = luaL_checknumber(L, 2);
                        lua_pushinteger(L, mini::map::tile::get(celx, cely));
                        return 1;
                    } else {
                        int celx = luaL_checknumber(L, 1);
                        int cely = luaL_checknumber(L, 2);
                        uint8_t snum = luaL_checkinteger(L, 3);
                        mini::map::tile::set(celx, cely, snum);
                        return 0;
                    }
                }

                static int cell(lua_State *L) {
                    if (lua_gettop(L)==2) {
                        int celx = luaL_checknumber(L, 1);
                        int cely = luaL_checknumber(L, 2);
                        mini::map::cell::set(celx, cely);
                        return 0;
                    } else {
                        lua_pushinteger(L, mini::map::cell::getw());
                        lua_pushinteger(L, mini::map::cell::geth());
                        return 2;
                    }
                }

            }

            namespace pal
            {
                static int load(lua_State *L) {
                    const char* str = luaL_checkstring(L, 1);
                    uint16_t size;
                    uint32_t *pal = mini::pal::load(str, &size);
                    lua_createtable(L, 2, 0);
                    for (int i=0;i<size;++i) {
                        uint32_t color = pal[i];
                        lua_createtable(L, 0, 3);

                        lua_pushinteger(L, (color>>16)&0xff);
                        lua_setfield(L, -2, "r");

                        lua_pushinteger(L, (color>>8)&0xff);
                        lua_setfield(L, -2, "g");

                        lua_pushinteger(L, color&0xff);
                        lua_setfield(L, -2, "b");

                        lua_rawseti(L, -2, i+1);
                    }
                    free(pal);
                    return 1;
                }

                static int set(lua_State *L) {
                    int r,g,b;
                    if (lua_istable(L, -1)) {
                        uint32_t pal[256];
                        int len = lua_rawlen(L, -1);
                        if (len > 256) len = 256;
                        for (int i=0;i<len;++i) {
                            lua_rawgeti(L, -1, i+1);
                            lua_getfield(L, -1, "r");
                            r = luaL_checknumber(L, -1);
                            lua_pop(L, 1);
                            lua_getfield(L, -1, "g");
                            g = luaL_checknumber(L, -1);
                            lua_pop(L, 1);
                            lua_getfield(L, -1, "b");
                            b = luaL_checknumber(L, -1);
                            lua_pop(L, 1);
                            lua_pop(L, 1);
                            pal[i] = (r<<16)+(g<<8)+b;
                        }
                        mini::pal::set(pal);
                    }
                    return 0;
                }

                static int color(lua_State *L) {
                    //if (lua_gettop(L) != 1) return luaL_error(L, "Function 'mini.palette.getColor' Unexpected number of parameters.");
                    if (lua_gettop(L) == 1) {
                        uint8_t index = luaL_checkinteger(L, 1);
                        uint32_t color = mini::pal::color::get(index);
                        lua_pushinteger(L, (color>>16)&0xff);
                        lua_pushinteger(L, (color>>8)&0xff);
                        lua_pushinteger(L, color&0xff);
                        return 3;
                    } else {
                        uint8_t index = luaL_checkinteger(L, 1);
                        uint8_t r = luaL_checkinteger(L, 2);
                        uint8_t g = luaL_optinteger(L, 3, 0);
                        uint8_t b = luaL_optinteger(L, 4, 0);
                        uint32_t color = (r<<16) + (g<<8) + b;
                        mini::pal::color::set(index, color);
                        return 0;
                    }
                }

                static int trans(lua_State *L) {
                    if (lua_gettop(L) == 0) {
                        lua_pushinteger(L, mini::pal::trans::get());
                        return 1;
                    } else {
                        uint8_t index = luaL_checkinteger(L, 1);
                        mini::pal::trans::set(index);
                        return 0;
                    }
                }

                static int subpal(lua_State *L) {
                    const int num_params = lua_gettop(L);
                    uint8_t index, index2, color;
                    switch (num_params) {
                        case 0:
                            mini::pal::subpal::reset();
                            break;
                        case 1:
                            index = luaL_checkinteger(L, 1);
                            lua_pushinteger(L, mini::pal::subpal::set(index,index));
                            return 1;
                            break;
                        case 2:
                            index = luaL_checkinteger(L, 1);
                            color = luaL_checkinteger(L, 2);
                            lua_pushinteger(L, mini::pal::subpal::set(index, color));
                            return 1;
                            break;
                        case 3:
                            index = luaL_checkinteger(L, 1);
                            index2 = luaL_checkinteger(L, 2);
                            color = luaL_checkinteger(L, 3);
                            for (int i=index;i<=index2;++i) mini::pal::subpal::set(i, color);
                            break;
                    }
                    return 0;
                }
            }

            namespace view
            {
                static int origin(lua_State *L) {
                    if (lua_gettop(L) == 0) {
                        lua_pushinteger(L, mini::view::origin::getx());
                        lua_pushinteger(L, mini::view::origin::gety());
                        return 2;
                    } else {
                        int x = luaL_checknumber(L, 1);
                        int y = luaL_checknumber(L, 2);
                        mini::view::origin::set(x, y);
                        return 0;
                    }
                }

                static int tolocal(lua_State *L) {
                    int x = luaL_checknumber(L, 1);
                    int y = luaL_checknumber(L, 2);
                    lua_pushinteger(L, x+mini::view::origin::getx());
                    lua_pushinteger(L, y+mini::view::origin::gety());
                    return 2;
                }
            }

            namespace draw
            {
                static int pixel(lua_State *L) {
                    if (lua_gettop(L)==2) {
                        int x = luaL_checknumber(L, 1);
                        int y = luaL_checknumber(L, 2);
                        lua_pushinteger(L, mini::draw::pixel::get(x, y));
                        return 1;
                    } else {
                        int x = luaL_checknumber(L, 1);
                        int y = luaL_checknumber(L, 2);
                        uint8_t color = luaL_checkinteger(L, 3);
                        mini::draw::pixel::set(x, y, color);
                        return 0;
                    }
                }

                static int line(lua_State *L) {
                    int x0 = luaL_checknumber(L, 1);
                    int y0 = luaL_checknumber(L, 2);
                    int x1 = luaL_checknumber(L, 3);
                    int y1 = luaL_checknumber(L, 4);
                    uint8_t color = luaL_checkinteger(L, 5);
                    mini::draw::line(x0, y0, x1, y1, color);
                    return 0;
                }

                static int hline(lua_State *L) {
                    int x0 = luaL_checknumber(L, 1);
                    int y = luaL_checknumber(L, 2);
                    int x1 = luaL_checknumber(L, 3);
                    uint8_t color = luaL_checkinteger(L, 4);
                    mini::draw::hline(x0, y, x1, color);
                    return 0;
                }

                static int vline(lua_State *L) {
                    int x = luaL_checknumber(L, 1);
                    int y0 = luaL_checknumber(L, 2);
                    int y1 = luaL_checknumber(L, 3);
                    uint8_t color = luaL_checkinteger(L, 4);
                    mini::draw::vline(x, y0, y1, color);
                    return 0;
                }

                static int rect(lua_State *L) {
                    int x = luaL_checknumber(L, 1);
                    int y = luaL_checknumber(L, 2);
                    int w = luaL_checknumber(L, 3);
                    int h = luaL_checknumber(L, 4);
                    uint8_t color = luaL_checkinteger(L, 5);
                    mini::draw::rect(x, y, w, h, color);
                    return 0;
                }

                static int rectf(lua_State *L) {
                    int x = luaL_checknumber(L, 1);
                    int y = luaL_checknumber(L, 2);
                    int w = luaL_checknumber(L, 3);
                    int h = luaL_checknumber(L, 4);
                    uint8_t color = luaL_checkinteger(L, 5);
                    mini::draw::rectf(x, y, w, h, color);
                    return 0;
                }

                static int circ(lua_State *L) {
                    int x = luaL_checknumber(L, 1);
                    int y = luaL_checknumber(L, 2);
                    int r = luaL_optnumber(L, 3, 4);
                    uint8_t color = luaL_checkinteger(L, 4);
                    mini::draw::circ(x, y, r, color);
                    return 0;
                }

                static int circf(lua_State *L) {
                    int x = luaL_checknumber(L, 1);
                    int y = luaL_checknumber(L, 2);
                    int r = luaL_optnumber(L, 3, 4);
                    uint8_t color = luaL_checkinteger(L, 4);
                    mini::draw::circf(x, y, r, color);
                    return 0;
                }

                static int roundrect(lua_State *L) {
                    int x = luaL_checknumber(L, 1);
                    int y = luaL_checknumber(L, 2);
                    int w = luaL_checknumber(L, 3);
                    int h = luaL_checknumber(L, 4);
                    int r = luaL_optnumber(L, 5, 4);
                    uint8_t color = luaL_checkinteger(L, 6);
                    mini::draw::roundrect(x, y, w, h, r, color);
                    return 0;
                }

                static int roundrectf(lua_State *L) {
                    int x = luaL_checknumber(L, 1);
                    int y = luaL_checknumber(L, 2);
                    int w = luaL_checknumber(L, 3);
                    int h = luaL_checknumber(L, 4);
                    int r = luaL_optnumber(L, 5, 4);
                    uint8_t color = luaL_checkinteger(L, 6);
                    mini::draw::roundrectf(x, y, w, h, r, color);
                    return 0;
                }

                static int oval(lua_State *L) {
                    int x0 = luaL_checknumber(L, 1);
                    int y0 = luaL_checknumber(L, 2);
                    int x1 = luaL_checknumber(L, 3);
                    int y1 = luaL_checknumber(L, 4);
                    uint8_t color = luaL_checkinteger(L, 5);
                    mini::draw::oval(x0, y0, x1, y1, color);
                    return 0;
                }

                static int ovalf(lua_State *L) {
                    int x0 = luaL_checknumber(L, 1);
                    int y0 = luaL_checknumber(L, 2);
                    int x1 = luaL_checknumber(L, 3);
                    int y1 = luaL_checknumber(L, 4);
                    uint8_t color = luaL_checkinteger(L, 5);
                    mini::draw::ovalf(x0, y0, x1, y1, color);
                    return 0;
                }

                static int pattern(lua_State *L) {
                    if (lua_gettop(L) == 0) {
                        mini::draw::pattern::set(0xffff);
                    } else {
                        uint16_t pat = luaL_checkinteger(L, 1);
                        bool transparent = lua_toboolean(L, 2);
                        mini::draw::pattern::set(pat, transparent);
                    }
                    return 0;
                }

                static int surf(lua_State *L) {
                    int sx = luaL_checknumber(L, 1);
                    int sy = luaL_checknumber(L, 2);
                    int sw = luaL_checknumber(L, 3);
                    int sh = luaL_checknumber(L, 4);
                    int dx = luaL_checknumber(L, 5);
                    int dy = luaL_checknumber(L, 6);
                    int dw = luaL_optnumber(L, 7, 0);
                    int dh = luaL_optnumber(L, 8, 0);
                    bool flip_x = lua_toboolean(L, 9);
                    bool flip_y = lua_toboolean(L, 10);
                    bool invert = lua_toboolean(L, 11);
                    mini::draw::surf(sx, sy, sw, sh, dx, dy, dw, dh, flip_x, flip_y, invert);
                    return 0;
                }

                static int surfrot(lua_State *L) {
                    int sx = luaL_checknumber(L, 1);
                    int sy = luaL_checknumber(L, 2);
                    int sw = luaL_checknumber(L, 3);
                    int sh = luaL_checknumber(L, 4);
                    int dx = luaL_checknumber(L, 5);
                    int dy = luaL_checknumber(L, 6);
                    float a = luaL_checknumber(L, 7);
                    int dw = luaL_optnumber(L, 8, 0);
                    int dh = luaL_optnumber(L, 9, 0);
                    bool flip_x = lua_toboolean(L, 10);
                    bool flip_y = lua_toboolean(L, 11);
                    mini::draw::surfrot(sx, sy, sw, sh, dx, dy, dw, dh, flip_x, flip_y, a);
                    return 0;
                }

                static int text(lua_State *L) {
                    const char* str = luaL_checkstring(L, 1);
                    int x = luaL_checknumber(L, 2);
                    int y = luaL_checknumber(L, 3);
                    uint8_t color = luaL_checkinteger(L, 4);
                    mini::draw::text(str, x, y, color);
                    return 0;
                }

                static int mode(lua_State *L) {
                    int mode = luaL_checknumber(L, 1);
                    mini::draw::mode::set(mode);
                    return 0;
                }
            }

            namespace shader
            {
                static int init(lua_State *L) {
                    const char* vstr = luaL_optstring(L, 1, NULL);
                    const char* fstr = luaL_optstring(L, 2, NULL);
                    mini::shader::init(vstr, fstr);
                    return 0;
                }

                static int enable(lua_State *L) {
                    mini::shader::enable();
                    return 0;
                }

                static int disable(lua_State *L) {
                    mini::shader::disable();
                    return 0;
                }
            }

            namespace music
            {
                static int play(lua_State *L) {
                    const char* str = luaL_checkstring(L, 1);
                    const int loop = luaL_optinteger(L, 2, -1);
                    mini::audio::music::play(str, loop);
                    return 0;
                }

                static int pause(lua_State *L) {
                    mini::audio::music::pause();
                    return 0;
                }

                static int resume(lua_State *L) {
                    mini::audio::music::resume();
                    return 0;
                }

                static int stop(lua_State *L) {
                    const int time = luaL_optinteger(L, 1, 1000);
                    mini::audio::music::stop(time);
                    return 0;
                }

                static int pos(lua_State *L) {
                    if (lua_gettop(L) == 0) {
                        lua_pushnumber(L, mini::audio::music::pos::get());
                        return 1;
                    } else {
                        mini::audio::music::pos::set(luaL_checknumber(L, 1));
                        return 0;
                    }
                }

                static int enable(lua_State *L) {
                    if (lua_gettop(L) == 0) {
                        lua_pushboolean(L, mini::audio::music::enable::get());
                        return 1;
                    } else {
                        mini::audio::music::enable::set(lua_toboolean(L, 1));
                        return 0;
                    }
                }
            }

            namespace sound
            {
                static int load(lua_State *L) {
                    const char* str = luaL_checkstring(L, 1);
                    lua_pushinteger(L, mini::audio::sound::load(str));
                    return 1;
                }

                static int free(lua_State *L) {
                    const int sound = luaL_checknumber(L, 1);
                    mini::audio::sound::free(sound);
                    return 0;
                }

                static int play(lua_State *L) {
                    const int sound = luaL_checknumber(L, 1);
                    const int volume = luaL_optinteger(L, 2, -1);
                    lua_pushinteger(L, mini::audio::sound::play(sound, volume));
                    return 1;
                }

                static int stop(lua_State *L) {
                    const int sound = luaL_checknumber(L, 1);
                    mini::audio::sound::stop(sound);
                    return 0;
                }

                static int enable(lua_State *L) {
                    if (lua_gettop(L) == 0) {
                        lua_pushboolean(L, mini::audio::sound::enable::get());
                        return 1;
                    } else {
                        mini::audio::sound::enable::set(lua_toboolean(L, 1));
                        return 0;
                    }
                }
            }

            namespace sys
            {
                static int delta(lua_State *L) {
                    lua_pushnumber(L, mini::sys::delta());
                    return 1;
                }

                static int time(lua_State *L) {
                    lua_pushnumber(L, mini::sys::time());
                    return 1;
                }

                static uint32_t chrono_time = 0;
                static int chrono(lua_State *L) {
                    if (lua_gettop(L) == 0) {
                        lua_pushnumber(L, float(backend::get_time_ms()-chrono_time)/1000.0f);
                        return 1;
                    } else {
                        chrono_time = backend::get_time_ms();
                        return 0;
                    }
                }

                static int beat(lua_State *L) {
                    if (lua_gettop(L) == 0) {
                        lua_pushboolean(L, mini::sys::beat(-1));
                        return 1;
                    } else {
                        int16_t beats = luaL_optnumber(L, 1, -1);
                        mini::sys::beat(beats);
                        return 0;
                    }
                }

                static int update(lua_State *L) {
                    if (lua_gettop(L) == 0) {
                        lua_pushinteger(L, mini::sys::update::get());
                        return 1;
                    } else {
                        int mode = luaL_checknumber(L, 1);
                        int interval = luaL_optinteger(L, 2, 0);
                        mini::sys::update::set(mode, interval);
                        return 0;
                    }
                }

                namespace fs = std::filesystem;
                static int dir(lua_State *L) {
                    std::string path = "./data";
                    if (lua_gettop(L) > 0) path = luaL_checkstring(L, 1);

                    // Collect entries
                    std::vector<fs::directory_entry> entries;
                    for (const auto& entry : fs::directory_iterator(path)) {
                        entries.push_back(entry);
                    }

                    // Sort: directories first, then files; both alphabetically
                    std::sort(entries.begin(), entries.end(),
                        [](const fs::directory_entry& a, const fs::directory_entry& b) {
                            bool adir = a.is_directory();
                            bool bdir = b.is_directory();
                            if (adir != bdir) return adir > bdir; // directories before files
                            return a.path().filename().string() < b.path().filename().string();
                        });

                    // Build Lua table
                    lua_newtable(L);
                    int i = 1;
                    for (const auto& entry : entries) {
                        lua_newtable(L);

                        // name field (canonical absolute path)
                        lua_pushstring(L, (const char*)fs::canonical(entry.path()).u8string().c_str());
                        lua_setfield(L, -2, "name");

                        // dir field
                        lua_pushboolean(L, entry.is_directory());
                        lua_setfield(L, -2, "dir");

                        lua_rawseti(L, -2, i++);
                    }

                    return 1;
                }

                static int exit(lua_State *L) {
                    mini::sys::exit();
                    return 0;
                }

                static int fps(lua_State *L) {
                    lua_pushnumber(L, mini::sys::getfps());
                    return 1;
                }

                static int debug(lua_State *L) {
                    #ifdef DEBUG
                        lua_pushboolean(L, true);
                    #else
                        lua_pushboolean(L, false);
                    #endif
                    return 1;
                }

                static int clipboard(lua_State *L) {
                    if (lua_gettop(L) == 0) {
                        lua_pushstring(L, backend::clipboard());
                        return 1;
                    } else {
                        const char* value = luaL_checkstring(L, 1);
                        backend::clipboard(value);
                        return 0;
                    }
                }

                static int version(lua_State *L) {
                    lua_pushstring(L, MINI_VERSION);
                    return 1;
                }
            }

            namespace win
            {
                static int zoom(lua_State *L) {
                    if (lua_gettop(L) == 0) {
                        lua_pushinteger(L, mini::win::zoom::get());
                        return 1;
                    } else {
                        const int value = luaL_optinteger(L, 1, 0);
                        mini::win::zoom::set(value);
                        return 0;
                    }
                }

                static int fullscreen(lua_State *L) {
                    if (lua_gettop(L) == 0) {
                        lua_pushboolean(L, mini::win::fullscreen::get());
                        return 1;
                    } else {
                        mini::win::fullscreen::set(lua_toboolean(L, 1));
                        return 0;
                    }
                }

                static int cursor(lua_State *L) {
                    if (lua_gettop(L) == 0) {
                        lua_pushboolean(L, mini::win::cursor::get());
                        return 1;
                    } else {
                        mini::win::cursor::set(lua_toboolean(L, 1));
                        return 0;
                    }
                }

                static int res(lua_State *L) {
                    if (lua_gettop(L) == 0) {
                        lua_pushinteger(L, mini::win::res::getw());
                        lua_pushinteger(L, mini::win::res::geth());
                        return 2;
                    } else {
                        const int w = luaL_optinteger(L, 1, 160);
                        const int h = luaL_optinteger(L, 2, 120);
                        mini::win::res::set(w, h);
                        return 0;
                    }
                }
            }

            namespace conf
            {
                static int key(lua_State *L) {
                    const char* key = luaL_checkstring(L, 1);
                    if (lua_gettop(L) > 1) {
                        const char* value = luaL_checkstring(L, 2);
                        mini::config::key::set(key, value);
                        return 0;
                    } else {
                        const char* value = mini::config::key::get(key);
                        if (value==NULL) {
                            lua_pushnil(L);
                        } else {
                            lua_pushstring(L, value);
                        }
                        return 1;
                    }
                }

                static int folder(lua_State *L) {
                    lua_pushstring(L, mini::config::folder());
                    return 1;
                }
            }

            namespace font
            {
                static int load(lua_State *L) {
                    const char* str = luaL_checkstring(L, 1);
                    uint8_t s = mini::font::load(str);
                    if (s==255) {
                        luaL_error(L, "Error while loading font: Max fonts reached");
                        return 0;
                    }
                    lua_pushinteger(L, s);
                    return 1;
                }

                static int current(lua_State *L) {
                    const int numargs = lua_gettop(L);
                    switch (numargs) {
                        case 0: {
                            lua_pushinteger(L, mini::font::current::get());
                            return 1;
                        }
                        case 1: {
                            uint8_t font = luaL_checkinteger(L, 1);
                            mini::font::current::set(font);
                            return 0;
                        }
                        default:
                            return luaL_error(L, "Function 'font.current' Unexpected number of parameters.");
                    };
                }

                static int spacing(lua_State *L) {
                    const int numargs = lua_gettop(L);
                    switch (numargs) {
                        case 0: {
                            lua_pushinteger(L, mini::font::spacing::get());
                            return 1;
                        }
                        case 1: {
                            uint8_t spacing = luaL_checkinteger(L, 1);
                            mini::font::spacing::set(spacing);
                            return 0;
                        }
                        default:
                            return luaL_error(L, "Function 'font.spacing' Unexpected number of parameters.");
                    };
                }
            }

            namespace mouse
            {
                static int pos(lua_State *L) {
                    lua_pushinteger(L, mini::mouse::posx());
                    lua_pushinteger(L, mini::mouse::posy());
                    return 2;
                }

                static int wheel(lua_State *L) {
                    lua_pushinteger(L, mini::mouse::wheel());
                    return 1;
                }

                static int down(lua_State *L) {
                    uint8_t i = luaL_checkinteger(L, 1);
                    lua_pushboolean(L, mini::mouse::down(i));
                    return 1;
                }

                static int press(lua_State *L) {
                    uint8_t i = luaL_checkinteger(L, 1);
                    lua_pushboolean(L, mini::mouse::press(i));
                    return 1;
                }

                static int dblclick(lua_State *L) {
                    lua_pushboolean(L, mini::mouse::dblclick());
                    return 1;
                }

                static int discard(lua_State *L) {
                    mini::mouse::discard();
                    return 0;
                }

                static int inside(lua_State *L) {
                    int x = luaL_checkinteger(L, 1);
                    int y = luaL_checkinteger(L, 2);
                    int w = luaL_checkinteger(L, 3);
                    int h = luaL_checkinteger(L, 4);
                    lua_pushboolean(L, mini::mouse::inside(x,y,w,h));
                    return 1;
                }
            }

            namespace key
            {
                static int down(lua_State *L) {
                    uint8_t i = luaL_checkinteger(L, 1);
                    lua_pushboolean(L, mini::key::down(i));
                    return 1;
                }

                static int press(lua_State *L) {
                    if (lua_gettop(L) >=1 ) {
                        uint8_t i = luaL_checkinteger(L, 1);
                        lua_pushboolean(L, mini::key::press(i));
                    } else {
                        lua_pushinteger(L, mini::key::press());
                    }
                    return 1;
                }

                static int any(lua_State *L) {
                    lua_pushboolean(L, mini::key::any());
                    return 1;
                }

                static int text(lua_State *L) {
                    mini::key::text(lua_toboolean(L, 1));
                    return 0;
                }

                static int utf8char(lua_State *L) {
                    lua_pushstring(L, mini::key::utf8char());
                    return 1;
                }
            }

            namespace pad
            {
                static int down(lua_State *L) {
                    int8_t i = luaL_checkinteger(L, 1);
                    lua_pushboolean(L, mini::pad::down(i));
                    return 1;
                }

                static int press(lua_State *L) {
                    if (lua_gettop(L) >=1 ) {
                        int8_t i = luaL_checkinteger(L, 1);
                        lua_pushboolean(L, mini::pad::press(i));
                    } else {
                        lua_pushinteger(L, mini::pad::press());
                    }
                    return 1;
                }

                static int any(lua_State *L) {
                    lua_pushinteger(L, mini::pad::press());
                    return 1;
                }

            }
        }

        void push_functions(lua_State *L) {
            lua_newtable(L);
            lua_setglobal(L, "mini");

            lua_newtable(L);
            lua_pushcfunction(L,wrappers::surf::create);     lua_setfield(L, -2, "new");
            lua_pushcfunction(L,wrappers::surf::load);       lua_setfield(L, -2, "load");
            lua_pushcfunction(L,wrappers::surf::loadex);     lua_setfield(L, -2, "loadex");
            lua_pushcfunction(L,wrappers::surf::save);       lua_setfield(L, -2, "save");
            lua_pushcfunction(L,wrappers::surf::free);       lua_setfield(L, -2, "free");
            lua_pushcfunction(L,wrappers::surf::size);       lua_setfield(L, -2, "size");
            lua_pushcfunction(L,wrappers::surf::target);     lua_setfield(L, -2, "target");
            lua_pushcfunction(L,wrappers::surf::source);     lua_setfield(L, -2, "source");
            lua_pushcfunction(L,wrappers::surf::cls);        lua_setfield(L, -2, "cls");
            lua_pushcfunction(L,wrappers::surf::clip);       lua_setfield(L, -2, "clip");

            lua_pushinteger(L, 0);                           lua_setfield(L, -2, "SCREEN");
            lua_setglobal(L, "surf");

            lua_newtable(L);
            lua_pushcfunction(L,wrappers::map::surf);        lua_setfield(L, -2, "surf");
            lua_pushcfunction(L,wrappers::map::draw);        lua_setfield(L, -2, "draw");
            lua_pushcfunction(L,wrappers::map::tile);        lua_setfield(L, -2, "tile");
            lua_pushcfunction(L,wrappers::map::cell);        lua_setfield(L, -2, "cell");
            lua_setglobal(L, "map");
            
            lua_newtable(L);
            lua_pushcfunction(L,wrappers::pal::load);        lua_setfield(L, -2, "load");
            lua_pushcfunction(L,wrappers::pal::set);         lua_setfield(L, -2, "set");
            lua_pushcfunction(L,wrappers::pal::color);       lua_setfield(L, -2, "color");
            lua_pushcfunction(L,wrappers::pal::trans);       lua_setfield(L, -2, "trans");
            lua_pushcfunction(L,wrappers::pal::subpal);      lua_setfield(L, -2, "subpal");
            lua_setglobal(L, "pal");

            lua_newtable(L);
            lua_pushcfunction(L,wrappers::view::origin);     lua_setfield(L, -2, "origin");
            lua_pushcfunction(L,wrappers::view::tolocal);    lua_setfield(L, -2, "tolocal");
            lua_setglobal(L, "view");

            lua_newtable(L);
            lua_pushcfunction(L,wrappers::draw::pixel);      lua_setfield(L, -2, "pixel");
            lua_pushcfunction(L,wrappers::draw::line);       lua_setfield(L, -2, "line");
            lua_pushcfunction(L,wrappers::draw::hline);      lua_setfield(L, -2, "hline");
            lua_pushcfunction(L,wrappers::draw::vline);      lua_setfield(L, -2, "vline");
            lua_pushcfunction(L,wrappers::draw::rect);       lua_setfield(L, -2, "rect");
            lua_pushcfunction(L,wrappers::draw::rectf);      lua_setfield(L, -2, "rectf");
            lua_pushcfunction(L,wrappers::draw::circ);       lua_setfield(L, -2, "circ");
            lua_pushcfunction(L,wrappers::draw::circf);      lua_setfield(L, -2, "circf");
            lua_pushcfunction(L,wrappers::draw::roundrect);  lua_setfield(L, -2, "rrect");
            lua_pushcfunction(L,wrappers::draw::roundrectf); lua_setfield(L, -2, "rrectf");
            lua_pushcfunction(L,wrappers::draw::oval);       lua_setfield(L, -2, "oval");
            lua_pushcfunction(L,wrappers::draw::ovalf);      lua_setfield(L, -2, "ovalf");
            lua_pushcfunction(L,wrappers::draw::pattern);    lua_setfield(L, -2, "pattern");
            lua_pushcfunction(L,wrappers::draw::surf);       lua_setfield(L, -2, "surf");
            lua_pushcfunction(L,wrappers::draw::surfrot);    lua_setfield(L, -2, "surfrot");
            lua_pushcfunction(L,wrappers::draw::text);       lua_setfield(L, -2, "text");
            lua_pushcfunction(L,wrappers::draw::mode);       lua_setfield(L, -2, "mode");

            lua_pushinteger(L, 0);                           lua_setfield(L, -2, "NORMAL");
            lua_pushinteger(L, 1);                           lua_setfield(L, -2, "PATTERN");
            lua_pushinteger(L, 2);                           lua_setfield(L, -2, "AND");
            lua_pushinteger(L, 3);                           lua_setfield(L, -2, "OR");
            lua_pushinteger(L, 4);                           lua_setfield(L, -2, "XOR");
            lua_pushinteger(L, 5);                           lua_setfield(L, -2, "NOT");

            lua_setglobal(L, "draw");

            lua_newtable(L);
            lua_pushcfunction(L,wrappers::shader::init);     lua_setfield(L, -2, "init");
            lua_pushcfunction(L,wrappers::shader::enable);   lua_setfield(L, -2, "enable");
            lua_pushcfunction(L,wrappers::shader::disable);  lua_setfield(L, -2, "disable");
            lua_setglobal(L, "shader");

            lua_newtable(L);
            lua_pushcfunction(L,wrappers::music::play);      lua_setfield(L, -2, "play");
            lua_pushcfunction(L,wrappers::music::pause);     lua_setfield(L, -2, "pause");
            lua_pushcfunction(L,wrappers::music::resume);    lua_setfield(L, -2, "resume");
            lua_pushcfunction(L,wrappers::music::stop);      lua_setfield(L, -2, "stop");
            lua_pushcfunction(L,wrappers::music::pos);       lua_setfield(L, -2, "pos");
            lua_pushcfunction(L,wrappers::music::enable);    lua_setfield(L, -2, "enabled");
            lua_setglobal(L, "music");

            lua_newtable(L);
            lua_pushcfunction(L,wrappers::sound::load);      lua_setfield(L, -2, "load");
            lua_pushcfunction(L,wrappers::sound::free);      lua_setfield(L, -2, "free");
            lua_pushcfunction(L,wrappers::sound::play);      lua_setfield(L, -2, "play");
            lua_pushcfunction(L,wrappers::sound::stop);      lua_setfield(L, -2, "stop");
            lua_pushcfunction(L,wrappers::sound::enable);    lua_setfield(L, -2, "enabled");
            lua_setglobal(L, "sound");

            lua_newtable(L);
            lua_pushcfunction(L,wrappers::sys::delta);       lua_setfield(L, -2, "delta");
            lua_pushcfunction(L,wrappers::sys::time);        lua_setfield(L, -2, "time");
            lua_pushcfunction(L,wrappers::sys::chrono);      lua_setfield(L, -2, "chrono");
            lua_pushcfunction(L,wrappers::sys::beat);        lua_setfield(L, -2, "beat");
            lua_pushcfunction(L,wrappers::sys::update);      lua_setfield(L, -2, "update");
            lua_pushcfunction(L,wrappers::sys::dir);         lua_setfield(L, -2, "dir");
            lua_pushcfunction(L,wrappers::sys::exit);        lua_setfield(L, -2, "quit");
            lua_pushcfunction(L,wrappers::sys::fps);         lua_setfield(L, -2, "fps");
            lua_pushcfunction(L,wrappers::sys::debug);       lua_setfield(L, -2, "debug");
            lua_pushcfunction(L,wrappers::sys::clipboard);   lua_setfield(L, -2, "clipboard");
            lua_pushcfunction(L,wrappers::sys::version);     lua_setfield(L, -2, "version");

            lua_pushinteger(L, 0);                           lua_setfield(L, -2, "UPDATE_ALWAYS");
            lua_pushinteger(L, 1);                           lua_setfield(L, -2, "UPDATE_WAIT");
            lua_pushinteger(L, 2);                           lua_setfield(L, -2, "UPDATE_TIMEOUT");

            lua_setglobal(L, "sys");

            lua_newtable(L);
            lua_pushcfunction(L,wrappers::win::zoom);        lua_setfield(L, -2, "zoom");
            lua_pushcfunction(L,wrappers::win::fullscreen);  lua_setfield(L, -2, "fullscreen");
            lua_pushcfunction(L,wrappers::win::cursor);      lua_setfield(L, -2, "cursor");
            lua_pushcfunction(L,wrappers::win::res);         lua_setfield(L, -2, "res");
            lua_setglobal(L, "win");

            lua_newtable(L);
            lua_pushcfunction(L,wrappers::conf::key);        lua_setfield(L, -2, "key");
            lua_pushcfunction(L,wrappers::conf::folder);     lua_setfield(L, -2, "folder");
            lua_setglobal(L, "config");

            lua_newtable(L);
            lua_pushcfunction(L,wrappers::font::load);       lua_setfield(L, -2, "load");
            lua_pushcfunction(L,wrappers::font::current);    lua_setfield(L, -2, "current");
            lua_pushcfunction(L,wrappers::font::spacing);    lua_setfield(L, -2, "spacing");

            lua_pushinteger(L, 0);                           lua_setfield(L, -2, "DEFAULT");
            lua_setglobal(L, "font");

            lua_newtable(L);
            lua_pushcfunction(L,wrappers::mouse::pos);       lua_setfield(L, -2, "pos");
            lua_pushcfunction(L,wrappers::mouse::wheel);     lua_setfield(L, -2, "wheel");
            lua_pushcfunction(L,wrappers::mouse::down);      lua_setfield(L, -2, "down");
            lua_pushcfunction(L,wrappers::mouse::press);     lua_setfield(L, -2, "press");
            lua_pushcfunction(L,wrappers::mouse::dblclick);  lua_setfield(L, -2, "dblclick");
            lua_pushcfunction(L,wrappers::mouse::discard);   lua_setfield(L, -2, "discard");
            lua_pushcfunction(L,wrappers::mouse::inside);    lua_setfield(L, -2, "inside");

            lua_pushinteger(L, 1);                           lua_setfield(L, -2, "LEFT");
            lua_pushinteger(L, 2);                           lua_setfield(L, -2, "MIDDLE");
            lua_pushinteger(L, 3);                           lua_setfield(L, -2, "RIGHT");
            lua_setglobal(L, "mouse");

            lua_newtable(L);
            lua_pushcfunction(L,wrappers::key::down);        lua_setfield(L, -2, "down");
            lua_pushcfunction(L,wrappers::key::press);       lua_setfield(L, -2, "press");
            lua_pushcfunction(L,wrappers::key::any);         lua_setfield(L, -2, "any");
            lua_pushcfunction(L,wrappers::key::text);        lua_setfield(L, -2, "text");
            lua_pushcfunction(L,wrappers::key::utf8char);    lua_setfield(L, -2, "utf8char");
            //lua_setglobal(L, "key");

            //lua_newtable(L);
            lua_pushinteger(L, 0);                           lua_setfield(L, -2, "UNKNOWN");
            lua_pushinteger(L, 4);                           lua_setfield(L, -2, "A");
            lua_pushinteger(L, 5);                           lua_setfield(L, -2, "B");
            lua_pushinteger(L, 6);                           lua_setfield(L, -2, "C");
            lua_pushinteger(L, 7);                           lua_setfield(L, -2, "D");
            lua_pushinteger(L, 8);                           lua_setfield(L, -2, "E");
            lua_pushinteger(L, 9);                           lua_setfield(L, -2, "F");
            lua_pushinteger(L, 10);                          lua_setfield(L, -2, "G");
            lua_pushinteger(L, 11);                          lua_setfield(L, -2, "H");
            lua_pushinteger(L, 12);                          lua_setfield(L, -2, "I");
            lua_pushinteger(L, 13);                          lua_setfield(L, -2, "J");
            lua_pushinteger(L, 14);                          lua_setfield(L, -2, "K");
            lua_pushinteger(L, 15);                          lua_setfield(L, -2, "L");
            lua_pushinteger(L, 16);                          lua_setfield(L, -2, "M");
            lua_pushinteger(L, 17);                          lua_setfield(L, -2, "N");
            lua_pushinteger(L, 18);                          lua_setfield(L, -2, "O");
            lua_pushinteger(L, 19);                          lua_setfield(L, -2, "P");
            lua_pushinteger(L, 20);                          lua_setfield(L, -2, "Q");
            lua_pushinteger(L, 21);                          lua_setfield(L, -2, "R");
            lua_pushinteger(L, 22);                          lua_setfield(L, -2, "S");
            lua_pushinteger(L, 23);                          lua_setfield(L, -2, "T");
            lua_pushinteger(L, 24);                          lua_setfield(L, -2, "U");
            lua_pushinteger(L, 25);                          lua_setfield(L, -2, "V");
            lua_pushinteger(L, 26);                          lua_setfield(L, -2, "W");
            lua_pushinteger(L, 27);                          lua_setfield(L, -2, "X");
            lua_pushinteger(L, 28);                          lua_setfield(L, -2, "Y");
            lua_pushinteger(L, 29);                          lua_setfield(L, -2, "Z");
            lua_pushinteger(L, 30);                          lua_setfield(L, -2, "N1");
            lua_pushinteger(L, 31);                          lua_setfield(L, -2, "N2");
            lua_pushinteger(L, 32);                          lua_setfield(L, -2, "N3");
            lua_pushinteger(L, 33);                          lua_setfield(L, -2, "N4");
            lua_pushinteger(L, 34);                          lua_setfield(L, -2, "N5");
            lua_pushinteger(L, 35);                          lua_setfield(L, -2, "N6");
            lua_pushinteger(L, 36);                          lua_setfield(L, -2, "N7");
            lua_pushinteger(L, 37);                          lua_setfield(L, -2, "N8");
            lua_pushinteger(L, 38);                          lua_setfield(L, -2, "N9");
            lua_pushinteger(L, 39);                          lua_setfield(L, -2, "N0");
            lua_pushinteger(L, 40);                          lua_setfield(L, -2, "RETURN");
            lua_pushinteger(L, 41);                          lua_setfield(L, -2, "ESCAPE");
            lua_pushinteger(L, 42);                          lua_setfield(L, -2, "BACKSPACE");
            lua_pushinteger(L, 43);                          lua_setfield(L, -2, "TAB");
            lua_pushinteger(L, 44);                          lua_setfield(L, -2, "SPACE");
            lua_pushinteger(L, 45);                          lua_setfield(L, -2, "MINUS");
            lua_pushinteger(L, 46);                          lua_setfield(L, -2, "EQUALS");
            lua_pushinteger(L, 47);                          lua_setfield(L, -2, "LEFTBRACKET");
            lua_pushinteger(L, 48);                          lua_setfield(L, -2, "RIGHTBRACKET");
            lua_pushinteger(L, 49);                          lua_setfield(L, -2, "BACKSLASH");
            lua_pushinteger(L, 50);                          lua_setfield(L, -2, "NONUSHASH");
            lua_pushinteger(L, 51);                          lua_setfield(L, -2, "SEMICOLON");
            lua_pushinteger(L, 52);                          lua_setfield(L, -2, "APOSTROPHE");
            lua_pushinteger(L, 53);                          lua_setfield(L, -2, "GRAVE");
            lua_pushinteger(L, 54);                          lua_setfield(L, -2, "COMMA");
            lua_pushinteger(L, 55);                          lua_setfield(L, -2, "PERIOD");
            lua_pushinteger(L, 56);                          lua_setfield(L, -2, "SLASH");
            lua_pushinteger(L, 57);                          lua_setfield(L, -2, "CAPSLOCK");
            lua_pushinteger(L, 58);                          lua_setfield(L, -2, "F1");
            lua_pushinteger(L, 59);                          lua_setfield(L, -2, "F2");
            lua_pushinteger(L, 60);                          lua_setfield(L, -2, "F3");
            lua_pushinteger(L, 61);                          lua_setfield(L, -2, "F4");
            lua_pushinteger(L, 62);                          lua_setfield(L, -2, "F5");
            lua_pushinteger(L, 63);                          lua_setfield(L, -2, "F6");
            lua_pushinteger(L, 64);                          lua_setfield(L, -2, "F7");
            lua_pushinteger(L, 65);                          lua_setfield(L, -2, "F8");
            lua_pushinteger(L, 66);                          lua_setfield(L, -2, "F9");
            lua_pushinteger(L, 67);                          lua_setfield(L, -2, "F10");
            lua_pushinteger(L, 68);                          lua_setfield(L, -2, "F11");
            lua_pushinteger(L, 69);                          lua_setfield(L, -2, "F12");
            lua_pushinteger(L, 70);                          lua_setfield(L, -2, "PRINTSCREEN");
            lua_pushinteger(L, 71);                          lua_setfield(L, -2, "SCROLLLOCK");
            lua_pushinteger(L, 72);                          lua_setfield(L, -2, "PAUSE");
            lua_pushinteger(L, 73);                          lua_setfield(L, -2, "INSERT");
            lua_pushinteger(L, 74);                          lua_setfield(L, -2, "HOME");
            lua_pushinteger(L, 75);                          lua_setfield(L, -2, "PAGEUP");
            lua_pushinteger(L, 76);                          lua_setfield(L, -2, "DELETE");
            lua_pushinteger(L, 77);                          lua_setfield(L, -2, "END");
            lua_pushinteger(L, 78);                          lua_setfield(L, -2, "PAGEDOWN");
            lua_pushinteger(L, 79);                          lua_setfield(L, -2, "RIGHT");
            lua_pushinteger(L, 80);                          lua_setfield(L, -2, "LEFT");
            lua_pushinteger(L, 81);                          lua_setfield(L, -2, "DOWN");
            lua_pushinteger(L, 82);                          lua_setfield(L, -2, "UP");
            lua_pushinteger(L, 83);                          lua_setfield(L, -2, "NUMLOCKCLEAR");
            lua_pushinteger(L, 84);                          lua_setfield(L, -2, "KP_DIVIDE");
            lua_pushinteger(L, 85);                          lua_setfield(L, -2, "KP_MULTIPLY");
            lua_pushinteger(L, 86);                          lua_setfield(L, -2, "KP_MINUS");
            lua_pushinteger(L, 87);                          lua_setfield(L, -2, "KP_PLUS");
            lua_pushinteger(L, 88);                          lua_setfield(L, -2, "KP_ENTER");
            lua_pushinteger(L, 89);                          lua_setfield(L, -2, "KP_1");
            lua_pushinteger(L, 90);                          lua_setfield(L, -2, "KP_2");
            lua_pushinteger(L, 91);                          lua_setfield(L, -2, "KP_3");
            lua_pushinteger(L, 92);                          lua_setfield(L, -2, "KP_4");
            lua_pushinteger(L, 93);                          lua_setfield(L, -2, "KP_5");
            lua_pushinteger(L, 94);                          lua_setfield(L, -2, "KP_6");
            lua_pushinteger(L, 95);                          lua_setfield(L, -2, "KP_7");
            lua_pushinteger(L, 96);                          lua_setfield(L, -2, "KP_8");
            lua_pushinteger(L, 97);                          lua_setfield(L, -2, "KP_9");
            lua_pushinteger(L, 98);                          lua_setfield(L, -2, "KP_0");
            lua_pushinteger(L, 99);                          lua_setfield(L, -2, "KP_PERIOD");
            lua_pushinteger(L, 100);                         lua_setfield(L, -2, "NONUSBACKSLASH");
            lua_pushinteger(L, 101);                         lua_setfield(L, -2, "APPLICATION");
            lua_pushinteger(L, 224);                         lua_setfield(L, -2, "LCTRL");
            lua_pushinteger(L, 225);                         lua_setfield(L, -2, "LSHIFT");
            lua_pushinteger(L, 226);                         lua_setfield(L, -2, "LALT");
            lua_pushinteger(L, 227);                         lua_setfield(L, -2, "LGUI");
            lua_pushinteger(L, 228);                         lua_setfield(L, -2, "RCTRL");
            lua_pushinteger(L, 229);                         lua_setfield(L, -2, "RSHIFT");
            lua_pushinteger(L, 230);                         lua_setfield(L, -2, "RALT");
            lua_pushinteger(L, 231);                         lua_setfield(L, -2, "RGUI");

            lua_setglobal(L, "key");

            lua_newtable(L);
            lua_pushcfunction(L,wrappers::pad::down);        lua_setfield(L, -2, "down");
            lua_pushcfunction(L,wrappers::pad::press);       lua_setfield(L, -2, "press");
            lua_pushcfunction(L,wrappers::pad::any);         lua_setfield(L, -2, "any");
            //lua_setglobal(L, "gamepad");

            //lua_newtable(L);
            lua_pushinteger(L, -1);                          lua_setfield(L, -2, "INVALID");
            lua_pushinteger(L, 0);                           lua_setfield(L, -2, "A");
            lua_pushinteger(L, 1);                           lua_setfield(L, -2, "B");
            lua_pushinteger(L, 2);                           lua_setfield(L, -2, "X");
            lua_pushinteger(L, 3);                           lua_setfield(L, -2, "Y");
            lua_pushinteger(L, 4);                           lua_setfield(L, -2, "BACK");
            lua_pushinteger(L, 5);                           lua_setfield(L, -2, "GUIDE");
            lua_pushinteger(L, 6);                           lua_setfield(L, -2, "START");
            lua_pushinteger(L, 7);                           lua_setfield(L, -2, "LEFTSTICK");
            lua_pushinteger(L, 8);                           lua_setfield(L, -2, "RIGHTSTICK");
            lua_pushinteger(L, 9);                           lua_setfield(L, -2, "LEFTSHOULDER");
            lua_pushinteger(L, 10);                          lua_setfield(L, -2, "RIGHTSHOULDER");
            lua_pushinteger(L, 11);                          lua_setfield(L, -2, "UP");
            lua_pushinteger(L, 12);                          lua_setfield(L, -2, "DOWN");
            lua_pushinteger(L, 13);                          lua_setfield(L, -2, "LEFT");
            lua_pushinteger(L, 14);                          lua_setfield(L, -2, "RIGHT");
            lua_pushinteger(L, 15);                          lua_setfield(L, -2, "MISC1");
            lua_pushinteger(L, 16);                          lua_setfield(L, -2, "PADDLE1");
            lua_pushinteger(L, 17);                          lua_setfield(L, -2, "PADDLE2");
            lua_pushinteger(L, 18);                          lua_setfield(L, -2, "PADDLE3");
            lua_pushinteger(L, 19);                          lua_setfield(L, -2, "PADDLE4");
            lua_pushinteger(L, 20);                          lua_setfield(L, -2, "TOUCHPAD");

            lua_setglobal(L, "pad");


            lua_getglobal(L, "utf8");                // push utf8 table
            lua_pushcfunction(L, wrappers::utf8_sub);      // push C function
            lua_setfield(L, -2, "sub");              // utf8.sub = cpp_utf8_sub
            lua_pop(L, 1);                           // pop utf8 table
        }
    }
}
