#ifndef LUNE_H
#define LUNE_H

extern "C" {
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
}

#include "luaxx.hpp"
#include <string>

namespace lua
{
    class object
    {
        state &myst;
        int myref;
    public:
        object(state &myst):myst(myst)
        {
            myref=luaL_ref(myst,LUA_REGISTRYINDEX);
        }
        ~object()
        {
            luaL_unref(myst,LUA_REGISTRYINDEX,myref);
        }
        void Get()
        {
            lua_rawgeti(myst,LUA_REGISTRYINDEX,myref);
        }
        state &GetState(){return myst;};
    };

    class local_object
    {
        state myst;
        int myref;
        static object *mytbl;
    public:
        local_object(lua_State *L)
        {

            myst=state(L);
            //LOG<<"Creating local object...\n";
            //StackDump(L);
            if(!mytbl)
            {
                //LOG<<" Metable...\n";
                myst.newtable(); //2
                if(myst.newmetatable("WEAKTABLE"))//3
                {
                    //StackDump(L);
                    myst.push("kv"); //4
                    myst.setfield("__mode");//3
                    //LOG<<" Setting Metable...\n";
                    //StackDump(L);
                }
                //LOG<<" Attaching to holder...\n";

                //myst.setfield("__metatable");//2
                lua_setmetatable(myst,-1);
                mytbl=new object(myst);
                //StackDump(L);
                //LOG<<" Done Metatable...\n";
            }
            //StackDump(L);
            mytbl->Get();
            //LOG<<" Got my table...\n";
            //StackDump(L);
            myst.insert(-2);
            myref=luaL_ref(myst,-2);
            //LOG<<"Before pop:";
            //StackDump(L);
            myst.pop(1);
            GetTable();
            //LOG<<"========Done...\n"<<"Ref="<<myref<<"\n";
            //mytbl->Get();
            //StackDump(L);
            //LOG<<"===========================\n";
        }
        ~local_object()
        {
            //LOG<<"Deleting local object...\n";
            ReleaseTable();
        }
        void ReleaseTable()
        {
            mytbl->Get();
            int pos=myst.gettop();
            luaL_unref(myst,pos,myref);
            myst.remove(pos);
        }
        state GetState(){return myst;}
        void GetTable()
        {
            //LOG<<"Getting ref="<<myref<<"\n";
            //StackDump(myst);
            //LOG<<"Tbl preget\n";
            mytbl->Get();
            int pos=myst.gettop();
            //StackDump(myst);
            //LOG<<"Tbl get\n";
            //int pos=myst.gettop();
            lua_rawgeti(myst,pos,myref);
            //StackDump(myst);
            //LOG<<"Done\n";
            myst.remove(pos);
        }
    protected:

    };
};

template <typename T,bool GC=true>
class Lune
{
public:
    typedef struct
    {
        T *pT;
        int tableref;
    } userdataType;

    typedef int (T::*mfp)(lua_State *L);
    typedef struct
    {
        const char *name;
        mfp mfunc;
    } RegType;

    static void Register(lua_State *L)
    {
        lua_newtable(L);
        int methods = lua_gettop(L);

        luaL_newmetatable(L, T::className);
        int metatable = lua_gettop(L);

        // store method table in globals so that
        // scripts can add functions written in Lua.
        lua_pushstring(L, T::className);
        lua_pushvalue(L, methods);
        lua_settable(L, LUA_GLOBALSINDEX);

        lua_pushliteral(L, "__metatable");
        lua_pushvalue(L, methods);
        lua_settable(L, metatable);  // hide metatable from Lua getmetatable()

        lua_pushliteral(L, "__index");
        lua_pushcfunction(L, index_T);
        lua_settable(L, metatable);

        //lua_pushliteral(L, "__name");
        //lua_pushstring(L, T::className);
        //lua_settable(L, metatable);

        lua_pushliteral(L, "__newindex");
        lua_pushcfunction(L, newindex_T);
        lua_settable(L, metatable);

        lua_pushliteral(L, "__instances");
        lua_newtable(L);
        lua_settable(L, metatable);
        if(GC)
        {
            lua_pushliteral(L, "__gc");
            lua_pushcfunction(L, gc_T);
            lua_settable(L, metatable);
        }


        lua_newtable(L);                // metatable for method table
        int mt = lua_gettop(L);
        lua_pushliteral(L, "__call");
        lua_pushcfunction(L, new_T);
        lua_pushliteral(L, "new");
        lua_pushvalue(L, -2);           // dup new_T function
        lua_settable(L, methods);       // add new_T to method table
        lua_settable(L, mt);            // mt.__call = new_T
        lua_setmetatable(L, methods);
        //LOG<<"lune: registered class \""<<T::className<<"\"\n";
        // fill method table with methods from class T
        for (RegType *l = T::methods; l->name; l++)
        {
            /* edited by Snaily: shouldn't it be const RegType *l ... ? */
            lua_pushstring(L, l->name);
            lua_pushlightuserdata(L, (void*)l);
            lua_pushcclosure(L, thunk, 1);
            lua_settable(L, methods);
            //LOG<<"lune:    method \""<<l->name<<"\"\n";
        }

        lua_pop(L, 2);  // drop metatable and method table
    };
    static void GetTable(lua_State *L,T *p)
    {
        GetTableEx(L,p->GetTableId());
    }
    static void GetTableEx(lua_State *L,int id)
    {
        lua::state s(L);
        s.getmetatable(T::className);
        s.getfield("__instances");
        int ins=s.gettop();
        lua_rawgeti(L, ins, id);
        s.insert(-3);
        s.pop(2);
    }
    static T *check(lua_State *L, int narg)
    {
        userdataType *ud =
            static_cast<userdataType*>(luaL_checkudata(L, narg, T::className)); //TODO FIX THIs..
        //(lua_touserdata(L, narg));//
        if(!ud) luaL_typerror(L, narg, T::className);
        return ud->pT;  // pointer to T object
    }
protected:
private:

    static int RegTable(lua_State *L)
    {
       // LOG<<"Regging....\n";
        //lua::StackDump(L);

        lua::state s(L);
        int ssize=s.gettop();
        //s.getglobal(T::className);
        s.getmetatable(T::className);
        s.getfield("__instances");
        int ins=s.gettop();
        s.newtable();
        int id=luaL_ref(L,ins);
        //LOG<<"After reg:\n";
        //lua::StackDump(L);
        s.settop(ssize);
        return id;
    }
    static void UnregTable(lua_State *L,int id)
    {
        lua::state s(L);
        s.getmetatable(T::className);
        s.getfield("__instances");
        int ins=s.gettop();
        //LOG<<"Unreg table id:"<<id<<"stack dump:\n";
        //lua::StackDump(L);
        luaL_unref(L,ins,id);
    }
    static int index_T(lua_State *L)  // calls with (table, key), return value
    {
        lua::state st(L);
        std::string key=st.as<std::string>(-1);
        T *p=check(L,1);
        GetTable(L,p);
        st.insert(-2);
        //LOG<<"Index:\n";
        //lua::StackDump(L);
        lua_rawget(L,-2); //try getting from normal table
        if(st.is<lua::nil>()) //failed
        {
            st.pop(2);
            st.getglobal(T::className); //try class tables then
            st.push(key);
            st.gettable();
        }
        return 1;
    }
    static int newindex_T(lua_State *L)
    {
        //LOG<<"New index....\n";
        //lua::StackDump(L);

        lua::state st(L);
        T *p=check(L,1);
        GetTable(L,p);
        //st.insert(-3);
        //LOG<<"Before set:\n";
        st.insert(-3);
        //lua::StackDump(L);
        lua_rawset(L,-3);
        return 0;
    }
    static int thunk(lua_State *L)
    {
        //LOG<<"Size of stack:"<<lua_gettop(L)<<"\n";
        //lua::StackDump(L);
        if(lua_gettop(L)<1)
            luaL_error(L,"Member function called without 'self'");
        //LOG<<"Size of stack after:"<<lua_gettop(L)<<"\n";
        // stack has userdata, followed by method args
        T *obj = check(L, 1);  // get 'self', or if you prefer, 'this'
        //T *obj=static_cast<userdataType*>(lua_touserdata(L,1))->pT;
        lua_remove(L, 1);  // remove self so member function args start at index 1
        // get member function from upvalue
        RegType *l = static_cast<RegType*>(lua_touserdata(L, lua_upvalueindex(1)));
        return (obj->*(l->mfunc))(L);  // call member function
    }
    static int gc_T(lua_State *L)
    {
        //lua_getfield(L,,"__ud");
        //LOG<<"Garbage collecting.\n";
        //lua::StackDump(L);
        userdataType *ud = static_cast<userdataType*>(lua_touserdata(L, 1));
        T *obj = ud->pT;

        delete obj;  // call destructor for T objects
        UnregTable(L,ud->tableref);
        return 0;
    }
    static int new_T(lua_State *L)
    {
        //LOG<<"Pre build:"<<lua_gettop(L)<<"\n";
        //lua::StackDump(L);
        lua_remove(L, 1);   // use classname:new(), instead of classname.new()
        //lua_newtable(L);
        int id=RegTable(L);
        //LOG<<"Registred as:"<<id<<"\n";
        //int ssize=lua_gettop(L);
        T *obj = new T(L,id);  // call constructor for T objects
        lua_settop(L,0); //no need for parameters later.
        //LOG<<"Post build:"<<lua_gettop(L)<<"\t";
        //lua::StackDump(L);
        //LOG<<"TSOP\n";

        userdataType *ud =
            static_cast<userdataType*>(lua_newuserdata(L, sizeof(userdataType)));
        //lua::StackDump(L);
        luaL_getmetatable(L, T::className);  // lookup metatable in Lua registry
        lua_setmetatable(L,-2);
        //LOG<<"metatable set\n";

        //lua::StackDump(L);
        GetTable(L,obj);
        lua_pushliteral(L,"__obj");
        lua_pushvalue(L,-3);
        lua_settable(L,-3);
        lua_pop(L,1);
        //LOG<<"Object referenced\n";
        //lua::StackDump(L);
        //T *p = new(ud) T(L);  // call constructor for T objects
        //lua::StackDump(L);
        ud->pT = obj;  // store pointer to object in userdata
        ud->tableref=id;
        //luaL_getmetatable(L, T::className);  // lookup metatable in Lua registry
        //lua_setmetatable(L, tableindex);
        //lua::StackDump(L);
        //LOG<<"Push done\n";
        return 1;  // userdata containing pointer to T object
    }
    Lune() {}; //non constructable...
};
#define method(class, name) {#name, &class::name}
#define DEF_LUNE(class) static const char className[];\
                         static Lune<class>::RegType methods[];
#define DEF_LUNE_NOGC(class) static const char className[];\
                         static Lune<class,false>::RegType methods[];
#define IMP_LUNE(class,lua_name) const char class::className[]=#lua_name;
#define LUNE_METHODS_START(class) Lune<class>::RegType class::methods[] = {
#define LUNE_METHODS_START_NOGC(class) Lune<class,false>::RegType class::methods[] = {
#define LUNE_METHODS_END() {0,0}}
#endif // LUNE_H