#include "lua_wrapper.h"

lua_wrapper::lua_wrapper(string filename)
{
    m_file = filename;

    //New lua state (for now the only state?)
    m_L = luaL_newstate();
    luaL_openlibs(m_L);

    if (luaL_loadfile(m_L, m_file.c_str()) || lua_pcall(m_L, 0, 0, 0))
    {
        cout << "Error:" << lua_tostring(m_L, -1) << endl;
        exit(0);
    }
}



lua_wrapper::~lua_wrapper()
{
    lua_close(m_L);
}

size_t lua_wrapper::table_len_top()
{
    check_type_error(LUA_TTABLE, "table_len_top, no table on top");
    return luaL_len(m_L,-1);
};


funcref lua_wrapper::func_ref(string name)
{
    lua_getglobal(m_L, name.c_str());
    return func_ref();
}


funcref lua_wrapper::func_ref()
{
    check_type_error(LUA_TFUNCTION, "func_ref_top: No function on top.");
    funcref temp = luaL_ref(m_L, LUA_REGISTRYINDEX);

    if(temp != LUA_REFNIL) return temp;
    else exit(EXIT_FAILURE);
}


void lua_wrapper::get_table_keys(vector<string>& rv, tableref index)
{
    rv.clear();
    open_table(index);
    lua_pushnil(m_L);
    while (lua_next(m_L, -2) != 0)
    {
        lua_pop(m_L, 1);
        rv.push_back(string());
        if( !get_top(rv.back()) ) exit(EXIT_FAILURE);
    }
    close_table();
}


//References global table from name
tableref lua_wrapper::table_ref_global(string name)
{
    open_global_table(name);
    return table_ref_top();
}

//Reference a table in a table with index position
tableref lua_wrapper::table_ref_field(int index)
{
    open_field_table(index);
    return table_ref_top();
}

//Reference a table in a table with field name
tableref lua_wrapper::table_ref_field(string name)
{
    open_field_table(name);
    return table_ref_top();
}

//References a open table on top
tableref lua_wrapper::table_ref_top()
{
    check_type_error(LUA_TTABLE, " table_ref_top : Not table on top.");

    tableref temp = luaL_ref(m_L, LUA_REGISTRYINDEX);

    if(temp != LUA_REFNIL) return temp;
    else exit(EXIT_FAILURE);
}



void lua_wrapper::open_table(tableref index)
{
    lua_rawgeti(m_L, LUA_REGISTRYINDEX, index);
    check_type_error(LUA_TTABLE, "Open global table, got wrong type or non existant value form a lua reference");
}



void lua_wrapper::open_global_table(string name)
{
    lua_getglobal(m_L, name.c_str());
    check_type_error(LUA_TTABLE, "Open global table, getglobal got wrong type or non existant value for ", name);
}



void lua_wrapper::open_field_table(string name)
{
    //Check if in a table
    check_type_error(LUA_TTABLE, "No table on top before calling open_field_table with name ", name);

    lua_getfield(m_L, -1, name.c_str());
    check_type_error(LUA_TTABLE, "open_field_table called ", name, " : Got no value or wrong type");
}



void lua_wrapper::open_field_table(int index)
{
    //Check if in a table
    check_type_error(LUA_TTABLE, "No table on top before calling open_field_table with index ", index);

    lua_rawgeti(m_L, -1, index);
    check_type_error(LUA_TTABLE, "open_field_table indexed ", index, " : Got no value or wrong type");
}



void lua_wrapper::close_table()
{
    check_type_error(LUA_TTABLE, "Close table : not table to close on top");
    lua_pop(m_L, 1);
}



//Overloaded function to get the most basic types (and check for failure)
bool lua_wrapper::get_top(int& value)
{
    if(!lua_isnumber(m_L, -1))
    {
        cout << "get_top: Is not a number (int)" << endl;
        return 0;
    }
    value = lua_tointeger(m_L,-1);
    return 1;
}


//Bit of overhead to check type
bool lua_wrapper::get_top(unsigned int& value)
{
    if(!lua_isnumber(m_L, -1))
    {
        cout << "get_top: Is not a number (unsigned int)" << endl;
        return 0;
    }
    value = (unsigned int)lua_tointeger(m_L,-1);

    return 1;
}


bool lua_wrapper::get_top(double& value)
{
    if(!lua_isnumber(m_L, -1))
    {
        cout << "get_top: Is not a number (double)" << endl;
        return 0;
    }
    value = lua_tonumber(m_L,-1);
    return 1;
}


bool lua_wrapper::get_top(string& value)
{
    if(!lua_isstring(m_L, -1))
    {
        cout << "get_top: Is not a string" << endl;
        return 0;
    }
    value = lua_tostring(m_L,-1);
    return 1;
}


bool lua_wrapper::get_top(bool& value)
{
    if(!lua_isboolean(m_L, -1))
    {
        cout << "get_top: Is not a bool" << endl;
        return 0;
    }
    value = lua_toboolean(m_L,-1);
    return 1;
}



void lua_wrapper::stackDump()
{
    cout << "Stack Dump : " << endl;
    int i;
    int top = lua_gettop(m_L);

    for (i = 1; i <= top; i++)// repeat for each level
    {
        int t = lua_type(m_L, i);
        switch (t) {
          case LUA_TSTRING:  /* strings */

            cout << "'" << lua_tostring(m_L, i) << "'";
            break;

          case LUA_TBOOLEAN:  /* booleans */

            cout << (lua_toboolean(m_L, i) ? "true" : "false");
            break;

          case LUA_TNUMBER:  /* numbers */

            cout << lua_tonumber(m_L, i);
            break;

          default:  /* other values */

            cout << lua_typename(m_L, t);
            break;

        }
        cout << "  ";  // put a separator
    }
    cout << endl;  // end the listing
}


/*******************************************************************************
Overloaded push function
*******************************************************************************/
int lua_wrapper::push(int value)
{
    lua_pushinteger(m_L, value);
    return 1;
}

int lua_wrapper::push(double value)
{
    lua_pushnumber(m_L, value);
    return 1;
}

int lua_wrapper::push(bool value)
{
    lua_pushboolean(m_L, (int)value);
    return 1;
}

int lua_wrapper::push(const string &value)
{
    lua_pushstring(m_L, value.c_str());
    return 1;
}

int lua_wrapper::push(char const * value)
{
    lua_pushstring(m_L, value);
    return 1;
}

int lua_wrapper::push()
{
    lua_pushnil(m_L);
    return 1;
}



string lua_wrapper::type_to_string(int type)
{
    switch ( type )
    {
        case LUA_TNUMBER: return "number";
        case LUA_TNIL: return "nil";
        case LUA_TBOOLEAN: return "bool";
        case LUA_TSTRING: return "string";
        case LUA_TTABLE: return "table";
        case LUA_TFUNCTION: return "function";
        case LUA_TUSERDATA: return "user data";
        case LUA_TTHREAD: return "thread";
        case LUA_TLIGHTUSERDATA: return "light user data";
        default: return "Invalid type";
    }
}
/*
lua_isnumber
lua_isstring
lua_isboolean
lua_istable
lua_iscfunction
lua_isfunction
lua_islightuserdata
lua_isnil
lua_isnone
lua_isnoneornil
lua_isthread
lua_isuserdat
*/
