/////////////////////////////////////////////////////////
//
// LUASTACK: See the markdown document "Our In-House
// Lua API" for more information about what this is.
//
/////////////////////////////////////////////////////////

#pragma once

#include "util.hpp"
#include "wrap-string.hpp"
#include "wrap-set.hpp"
#include <cstring>
#include <type_traits>
#include <cassert>
#include <optional>

#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
#include "eris.h"

////////////////////////////////////////////////////////////////////
//
// LUA TABLE TYPES
//
////////////////////////////////////////////////////////////////////

enum LuaTableType {
    LUA_TT_GENERAL = LUA_NUMTAGS,
    LUA_TT_REGISTRY,
    LUA_TT_GLOBALENV,
    LUA_TT_TANGIBLE,
    LUA_TT_TANGIBLEMETA,
    LUA_TT_CLASS,
    
    LUA_TT_SENTINEL
};

////////////////////////////////////////////////////////////////////
//
// LuaToken
//
////////////////////////////////////////////////////////////////////

struct LuaToken {
public:
    uint64_t value;

    // Get rid of the default constructors.
    //
    template<class T>
    LuaToken(T arg) = delete;

    // Construct a token from a string.
    //
    // If the string is not a valid token, then this
    // initializes the token to the empty token (zero)
    //
    LuaToken(std::string_view s) : value(util::encode_token(s)) {}
    LuaToken(const eng::string &s) : value(util::encode_token(s)) {}

    // Construct a token from a compile-time constant string.
    //
    // It appears that the code below throws an exception if the
    // string not parseable.  But in reality, since this function is
    // consteval (evaluated at compile time), the error is
    // generated during the compilation.
    //
    // WARNING: The Lua lexer in llex.c contains a duplicate of the
    // encoding logic (in the '@' token literal case). If you change
    // the encoding in util::encode_token, you must update llex.c to match.
    //
    consteval LuaToken(const char *s) : value(util::encode_token(s)) {
        if (empty()) throw "cannot parse token";
    }

    // Construct a token from an int64.
    //
    LuaToken(uint64_t v) : value(v) {}

    // Construct a token from a void pointer.
    //
    LuaToken(void *v) : value((uint64_t)v) {}

    // Default constructor: construct the empty token.
    //
    LuaToken() : value(0) {}

    // Assignment operator.
    void operator =(const LuaToken &other) { value = other.value; }

    // Empty: return true if the token is zero.
    //
    constexpr bool empty() const { return value == 0; }

    // Compare two tokens for equality.
    //
    bool operator ==(const LuaToken &other) const { return value == other.value; }

    // Convert the token to a void pointer.
    //
    void *voidvalue() const { return (void*)value; }

    // Convert the token to a string.
    //
    eng::string str() const { return util::decode_token(value); }
};

////////////////////////////////////////////////////////////////////
//
// LuaSlots.
//
////////////////////////////////////////////////////////////////////

class LuaSlot : public eng::nevernew {
protected:
    int index_;

    constexpr LuaSlot(int n) : index_(n) {}

public:
    // LuaSlots are normally constructed without arguments.
    // They are uninitialized until the LuaStack constructor runs.
    //
    constexpr LuaSlot() : index_(0) {}

    // You can fetch the stack index from the LuaSlot.
    //
    inline int index() const {
        return index_;
    }
    
private:
    // Our code can fetch the stack index using an implicit conversion.
    //
    inline operator int() const {
        return index_;
    }

    friend class LuaCoreStack;
    friend class LuaDefStack;
    friend class LuaExtStack;
    friend class LuaKeywordParser;
};

class LuaArg : public LuaSlot {};
class LuaRet : public LuaSlot {};
class LuaVar : public LuaSlot {};

class LuaSpecial : public LuaSlot {
public:
    constexpr LuaSpecial(int n) : LuaSlot(n) {}
};

extern LuaSpecial LuaRegistry;

////////////////////////////////////////////////////////////////////
//
// LuaExtraArgs
//
////////////////////////////////////////////////////////////////////

class LuaExtraArgs {
private:
    int index_; 
    int size_;

public:
    LuaExtraArgs() : index_(0), size_(0) {}
    LuaExtraArgs(int i, int s) : index_(i), size_(s) {}

    LuaSpecial operator[] (int n) const { return LuaSpecial(index_ + n); }
    int index(int n) const { return index_ + n; }
    int size() const { return size_; }

    friend class LuaCoreStack;
    friend class LuaDefStack;
};

////////////////////////////////////////////////////////////////////
//
// LuaNil and LuaNewTable
//
////////////////////////////////////////////////////////////////////

class LuaNilMarker {};
extern LuaNilMarker LuaNil;

class LuaNewTableMarker {};
extern LuaNewTableMarker LuaNewTable;

////////////////////////////////////////////////////////////////////
//
// LuaCoreStack
//
// This is the common base class for LuaDefStack and LuaExtStack.
// You should use one of those classes in your code, not this class.
// However, this class is where all the interesting operators on lua
// local variables resides.
//
////////////////////////////////////////////////////////////////////

class LuaCoreStack : public eng::nevernew {
protected:
    lua_State *L_;

public:
    // Constructor.  You should almost never use this, instead,
    // you should construct a LuaDefStack or a LuaExtStack.
    //
    LuaCoreStack(lua_State *L) : L_(L) {}

    // Get the raw pointer to the lua_State.
    //
    lua_State *state() const { return L_; }

    // Turn a Lua value into a C++ value, if possible.
    //
    // If the lua value doesn't match the desired type, then these return
    // an empty optional.  The ones that return bool only verify
    // the value's type, they don't actually fetch the value.
    //
    std::optional<bool>             tryboolean(LuaSlot s) const;
    std::optional<lua_Integer>      tryinteger(LuaSlot s) const;
    std::optional<int>              tryint(LuaSlot s) const;
    std::optional<lua_Number>       trynumber(LuaSlot s) const;
    std::optional<eng::string>      trystring(LuaSlot s) const;
    std::optional<std::string_view> trystringview(LuaSlot s) const;
    std::optional<lua_State*>       trythread(LuaSlot s) const;
    std::optional<LuaToken>         trytoken(LuaSlot s) const;
    std::optional<util::DXYZ>       tryxyz(LuaSlot s) const;
    bool                            trytable(LuaSlot s) const;
    bool                            trynil(LuaSlot s) const;
    bool                            tryfunction(LuaSlot s) const;
    bool                            trycfunction(LuaSlot s) const;
    bool                            trytangible(LuaSlot s) const;
    
    // Turn a lua value into a C++ value, or throw an error.
    //
    // If the lua value doesn't match the desired type,
    // then these throw a lua error.  It is invalid to use these
    // outside of a protected context.  The argname is used
    // for making a nice error message.
    //
    bool             ckboolean(LuaSlot s, const char *argname = "value") const;
    lua_Integer      ckinteger(LuaSlot s, const char *argname = "value") const;
    int              ckint(LuaSlot s, const char *argname = "value") const;
    lua_Number       cknumber(LuaSlot s, const char *argname = "value") const;
    eng::string      ckstring(LuaSlot s, const char *argname = "value") const;
    std::string_view ckstringview(LuaSlot s, const char *argname = "value") const;
    lua_State *      ckthread(LuaSlot s, const char *argname = "value") const;
    LuaToken         cktoken(LuaSlot s, const char *argname = "value") const;
    util::DXYZ       ckxyz(LuaSlot s, const char *argname = "value") const;
    void             cktable(LuaSlot s, const char *argname = "value") const;
    void             cknil(LuaSlot s, const char *argname = "value") const;
    void             ckfunction(LuaSlot s, const char *argname = "value") const;
    void             ckcfunction(LuaSlot s, const char *argname = "value") const;
    void             cktangible(LuaSlot s, const char *argname = "value") const;
    
    // Check if a lua value can be converted to C++.
    //
    // These functions check if a value can be converted
    // to a C++ value.  They don't actually return the C++ value.
    // It is more efficient to use the 'try' or 'ck' functions above if
    // you also want the value.
    //
    bool isboolean(LuaSlot s) const { return lua_type(L_, s) == LUA_TBOOLEAN; }
    bool isinteger(LuaSlot s) const { return bool(tryinteger(s)); }
    bool isint(LuaSlot s) const { return bool(tryint(s)); }
    bool isnumber(LuaSlot s) const { return lua_type(L_, s) == LUA_TNUMBER; }
    bool isstring(LuaSlot s) const { return lua_type(L_, s) == LUA_TSTRING; }
    bool isstringview(LuaSlot s) const { return lua_type(L_, s) == LUA_TSTRING; }
    bool isthread(LuaSlot s) const { return lua_type(L_, s) == LUA_TTHREAD; }
    bool istoken(LuaSlot s) const { return lua_type(L_, s) == LUA_TLIGHTUSERDATA; }
    bool isxyz(LuaSlot s) const { return bool(tryxyz(s)); }
    bool istable(LuaSlot s) const { return lua_istable(L_, s); }
    bool isnil(LuaSlot s) const { return lua_isnil(L_, s); }
    bool isfunction(LuaSlot s) const { return lua_isfunction(L_, s); }
    bool iscfunction(LuaSlot s) const { return lua_iscfunction(L_, s); }
    bool istangible(LuaSlot s) const { return bool(trytangible(s)); }

    // Create a new interpreter using the specified allocator.
    //
    // Typically, the allocator used would be eng::l_alloc.
    // You can also pass nullptr to use a default allocator based
    // on malloc.
    //
    static lua_State *newstate (lua_Alloc allocf);

    // Create a new thread.
    //
    // The target parameter is an output parameter, this will contain
    // the new thread.  Also returns a C++ pointer to the thread.  Remember
    // that the C++ pointer by itself doesn't prevent garbage collection,
    // you must keep the thread in the LuaSlot or in some other lua data
    // structure to prevent it from getting garbage collected.
    //
    lua_State *newthread(LuaSlot target) const;

    // Get the type of a LuaSlot.
    //
    // Returns one of the standard lua type tags.  These include:
    //
    // LUA_TBOOLEAN, LUA_TNUMBER, LUA_TSTRING, LUA_TTABLE, LUA_TFUNCTION,
    // LUA_TTHREAD, LUA_TLIGHTUSERDATA, LUA_TUSERDATA.
    //
    int type(LuaSlot s) const { return lua_type(L_, s); }
    const char *typestr(LuaSlot s) const { return lua_typename(L_, lua_type(L_, s)); }

    // Get the extended type of a LuaSlot.
    //
    // If the variable contains a table, returns one of the LuaTableType
    // constants.  Search for this enum above.  If it is not a table,
    // returns one of the standard lua type tags.  See the 'type'
    // method above.
    //
    int xtype(LuaSlot slot) const;

    // Get the table type of a lua table.
    //
    // Tab must contain a lua table.  Returns a value from enum LuaTableType.
    //
    int gettabletype(LuaSlot tab) const;

    // Set the table type of a lua table.
    //
    // Tab must contain a lua table.  T must be a value from enum LuaTableType.
    //
    void settabletype(LuaSlot tab, int t) const;

    // Get the length of a lua string.
    //
    // Techically, you can also use this on tables, but it's not recommended.
    // Instead, use 'nkeys' below.  The semantics of rawlen on tables
    // is just plain weird: see lua documentation if you are curious.
    //
    int rawlen(LuaSlot val) const;
    
    // Get the number of key/value pairs in a lua table.
    //
    // This works on any table, even tables where the keys aren't integers.
    //
    int nkeys(LuaSlot tab) const;

    // Get the metatable of a table.
    //
    // Tab must be a table.  The metatable of tab is stored in mt.
    //
    bool getmetatable(LuaSlot mt, LuaSlot tab) const;

    // Set the metatable of a table.
    //
    // Tab must be a table.  Mt must be a table or nil.
    //
    void setmetatable(LuaSlot tab, LuaSlot mt) const;

    // Remove the metatable from a table.
    //
    // Tab must be a table.  The metatable, if any, is removed from tab.
    //
    void clearmetatable(LuaSlot tab) const;

    // Create a new table.
    //
    // The new table is stored in target.
    //
    void newtable(LuaSlot target) const;

    // Create a new table with a storage hint.
    //
    // The new table is stored in target.  The new table has space
    // pre-allocated for narr array elements and nrec non-array elements.
    //
    void createtable(LuaSlot target, int narr, int nrec) const;

    // Get the global environment table.
    //
    // The global environment table is stored in gltab.
    //
    void getglobaltable(LuaSlot gltab) const;
    
    // Delete everything in a table.
    //
    // Tab must be a table.  Removes all (key,val) pairs from tab.
    // If clearmeta is true, then the metatable is also removed from tab.
    //
    void cleartable(LuaSlot tab, bool clearmeta) const;

    // Iterate over the key,val pairs in a table.
    //
    // Before the iteration begins, you should initialize 'key' to nil.
    // Then, you should call 'next' to fetch the next key,val pair in
    // the table.  You can keep calling 'next' to obtain successive key,val
    // pairs until 'next' returns false.
    //
    // Do not alter 'key' during the iteration, if you do, then the 'next'
    // function will return the wrong next-value.
    //
    bool next(LuaSlot tab, LuaSlot key, LuaSlot val) const;

    // Compile lua code.
    //
    // If the code contains a syntax error, then the result variable
    // is set to the error message, and the error message is returned.
    //
    // If the code is valid, then the result variable is set to a
    // closure, and an empty string is returned.
    //
    // If a syntax error occurs, the error message may contain the
    // token <eof>.  If so, the problem is an incomplete expression.
    //
    eng::string load(LuaSlot result, std::string_view code, std::string_view context);

    // Return true if the int64 can be stored losslessly in a lua_Number.
    //
    // Lua numbers are actually double-precision floating point.  double
    // can hold integers losslessly as long as they're small enough to
    // fit within the double's mantissa.  The mantissa of an IEEE double
    // is big enough to hold a 53-bit integer. 
    //
    static bool validinteger(int64_t value) { return (value <= MAXINT) && (value >= -MAXINT); }

    // Return true if the int64 can be stored losslessly and is positive.
    //
    // This returns true if the number is a validinteger (see above), and is
    // a positive number.
    //
    static bool validpositiveinteger(int64_t value) { return (value <= MAXINT) && (value >= 1); }

    // Get the class table and class name of X.
    //
    // The object passed in can be:
    //
    //   * A valid class table.
    //   * A valid, existing class name.
    //   * A tangible that has a class.
    //   * A normal table with a class metatable.
    //
    // On success, sets classtab, classname, and clears error.
    // On failure, sets classtab to nil, clears classname, and sets error.
    //
    void getclassinfo(LuaSlot classtab, eng::string &classname, eng::string &error, LuaSlot input) const;

    // Get the class name of X.
    //
    // The object passed in can be:
    //
    //   * A valid class table.
    //   * A valid, existing class name.
    //   * A tangible that has a class.
    //   * A normal table with a class metatable.
    //
    // If the object is none of these, returns empty string.
    //
    eng::string classname(LuaSlot x) const;

    // Get the class table of X.
    //
    // The object passed in can be:
    //
    //   * A valid class table.
    //   * A valid, existing class name.
    //   * A tangible that has a class.
    //   * A normal table with a class metatable.
    //
    // If there is a problem, returns an error message
    // and sets tab to nil.
    //
    eng::string getclass(LuaSlot tab, LuaSlot obj) const;
    eng::string getclass(LuaSlot tab, std::string_view name) const;

    // Create a class, or look up an existing class.
    //
    // Creates a new class, unless the class already exists.
    // Stores the class in the global environment table, and
    // in the class database.  This routine assert-fails if
    // the parameter is not a valid classname.
    //
    void makeclass(LuaSlot tab, LuaSlot name) const;
    void makeclass(LuaSlot tab, std::string_view name) const;

    // Create a tangible, or look up an existing tangible.
    //
    // If the tangible doesn't exist yet, this creates a tangible stub.
    // A stub tangible is an empty table with a metatable containing the
    // tangible's ID.  Nothing else is present in the stub.  It is up to
    // the World module to transform stubs into full-blown tangibles.
    // Assert-fails if the tangible ID is not a validpositiveinteger.
    //
    void maketan(LuaSlot tab, int64_t id) const;

    // Return true if a tangible is empty, ie, a stub.
    //
    bool tanblank(LuaSlot tab) const;

    // Get the ID of a tangible.
    //
    // This works on both full-blown tangibles and stubs.  If tab
    // is not a valid tangible, returns zero.
    //
    int64_t tanid(LuaSlot tab) const;

    // Get the class of a tangible.
    //
    // If the tangible has been assigned a class, then puts the class
    // table into classobj and returns true.  Otherwise, sets classobj
    // to nil and returns false.
    //
    bool tangetclass(LuaSlot classobj, LuaSlot tan);

    // Assign a lua variable.
    //
    // Copies value into target. The 'value' parameter can be a LuaSlot or
    // any lua-convertible C++ type.
    //
    template<typename VT>
    void set(LuaSlot target, VT value) const {
        push_any_value(value);
        lua_replace(L_, target);
    }

    // Return true if two values are equal.
    //
    // Checks if the two values are equal.  Note that in lua, if two strings
    // contain the same text, then they're equal.   The 'value' parameter
    // can be a LuaSlot or any lua-convertible C++ type.
    //
    // This could possibly be faster if we were to write some specializations
    // for strings, numbers, and bools.
    //
    template<typename VT>
    bool rawequal(LuaSlot v1, VT value) const {
        push_any_value(value);
        bool result = lua_rawequal(L_, v1, -1);
        lua_pop(L_, 1);
        return result;
    }

    // Return true if val1 is less than val2.
    //
    // This is NOT the same as the lua_lessthan function.  This is a more
    // general function that can compare any two lua objects.
    //
    // Numbers are compared in the obvious numeric manner.
    // Strings are compared alphabetically.
    // Booleans are compared with false being less than true.
    // Tables are all considered equal to other tables.
    // Functions are all considered equal to other functions.
    // Coroutines are all considered equal to other coroutines.
    //
    // Numbers are less than strings.
    // Strings are less than booleans.
    // Booleans are less than functions.
    // Functions are less than coroutines.
    // Coroutines are less than tables.
    //
    // Does not call metamethods.
    //
    bool genlt(LuaSlot val1, LuaSlot val2) const { return lua_genlt(L_, val1, val2); }

    // Return true if the value is a sortable key.
    //
    // Sortable keys are: strings, booleans, and numbers.
    // These three types can be meaningfully compared using genlt,
    // above.  They also can be meaningfully transferred from lua to C++
    // and back without losing anything.
    //
    bool issortablekey(LuaSlot s) const;

    // Move a sortable key to another lua environment. 
    //
    // This is used when you've created two lua interpreters and you
    // want to move data from one to the other.  The only kinds of data
    // you can move are strings, booleans, and numbers.  
    //
    void movesortablekey(LuaSlot val, LuaCoreStack &other, LuaSlot otherslot);

    // Fetch a value from a table.
    //
    // Fetches the specified key from the table tab, and stores the
    // result in target.  The key parameter can be a LuaSlot or any lua-
    // convertible C++ value.
    //
    template<typename KT>
    void rawget(LuaSlot target, LuaSlot tab, KT key) const {
        push_any_value(key);
        lua_rawget(L_, tab);
        lua_replace(L_, target);
    }

    // Store a value in a table.
    //
    // Sets the table entry for key to value.  The key and val
    // parameters can be LuaSlots, or they can be any lua-convertible
    // C++ value.
    //
    template<typename KT, typename VT>
    void rawset(LuaSlot tab, KT key, VT value) const {
        push_any_value(key);
        push_any_value(value);
        lua_rawset(L_, tab);
    }
    
    // Get the 'visited' bit from a lua table.
    //
    bool getvisited(LuaSlot tab) const;

    // Set the 'visited' bit in a lua table.
    //
    void setvisited(LuaSlot tab, bool visited) const;

    // This is the largest integer that can be stored in a lua_Number.
    // In other words, any 53-bit number can be stored.
    //
    static const int64_t MAXINT = 0x001FFFFFFFFFFFFF;

    // Template Specializations.
    //
    // These are all specializations of functions that are defined above.
    // These are typically here purely to make the functions above faster.
    //
    void set(LuaSlot target, LuaSlot value) const {
        lua_copy(L_, value, target);
    }

    bool rawequal(LuaSlot v1, LuaSlot v2) const {
        return lua_rawequal(L_, v1, v2);
    }

    void rawget(LuaSlot target, LuaSlot tab, int key) const {
        lua_rawgeti(L_, tab, key);
        lua_replace(L_, target);
    }
    
    template<typename VT>
    void rawset(LuaSlot tab, int key, VT value) const {
        push_any_value(value);
        lua_rawseti(L_, tab, key);
    }

protected:

    // Assign slots: this is used by the LuaDefStack and LuaExtStack
    // constructors to assign stack indices to LuaSlots.
    //
    template<class... SS>
    inline void vassign_slots(int retp, int argp, int varp, int extrap, int extrac, LuaRet &v, SS & ... stackslots) {
        v.index_ = retp;
        vassign_slots(retp+1, argp, varp, extrap, extrac, stackslots...);
    }
    template<class... SS>
    inline void vassign_slots(int retp, int argp, int varp, int extrap, int extrac, LuaArg &v, SS & ... stackslots) {
        v.index_ = argp;
        vassign_slots(retp, argp+1, varp, extrap, extrac, stackslots...);
    }
    template<class... SS>
    inline void vassign_slots(int retp, int argp, int varp, int extrap, int extrac, LuaVar &v, SS & ... stackslots) {
        v.index_ = varp;
        vassign_slots(retp, argp, varp+1, extrap, extrac, stackslots...);
    }
    template<class... SS>
    inline void vassign_slots(int retp, int argp, int varp, int extrap, int extrac, LuaExtraArgs &v, SS & ... stackslots) {
        v.index_ = extrap;
        v.size_ = extrac;
        vassign_slots(retp, argp, varp, extrap, extrac, stackslots...);
    }
    inline void vassign_slots(int retp, int argp, int varp, int extrap, int extrac) {}


    // Push any value on the stack, by type.
    //
    inline void push_any_value(LuaNewTableMarker s) const { lua_newtable(L_); }
    inline void push_any_value(LuaNilMarker s) const { lua_pushnil(L_); }
    inline void push_any_value(LuaSlot s) const { lua_pushvalue(L_, s); }
    inline void push_any_value(const eng::string &s) const { lua_pushlstring(L_, s.c_str(), s.size()); }
    inline void push_any_value(std::string_view s) const { lua_pushlstring(L_, s.data(), s.size()); }
    inline void push_any_value(const char *s) const { lua_pushstring(L_, s); }
    inline void push_any_value(float s) const { lua_pushnumber(L_, s); }
    inline void push_any_value(double s) const { lua_pushnumber(L_, s); }
    inline void push_any_value(int s) const { lua_pushinteger(L_, s); }
    inline void push_any_value(lua_Integer s) const { lua_pushinteger(L_, s); }
    inline void push_any_value(lua_CFunction s) const { lua_pushcfunction(L_, s); }
    inline void push_any_value(bool b) const { lua_pushboolean(L_, b ? 1:0); }
    inline void push_any_value(LuaToken token) const { lua_pushlightuserdata(L_, (void*)(token.value)); }

    // Push multiple values on the stack, in order, by type.
    //
    template<typename T0, typename... T>
    void push_any_values(T0 arg0, T... args) {
        push_any_value(arg0);
        push_any_values(args...);
    }
    void push_any_values() {
    }

    // Throw a lua error message 
    void argerr(const char *arg, const char *tp) const;
};

////////////////////////////////////////////////////////////////////
//
// Argument Counting Templates.
//
// These are internal functions used by LuaDefStack and LuaExtStack
// to help with the processing of constructor arguments.
//
////////////////////////////////////////////////////////////////////

struct LuaArgCounts {
    int nret;
    int narg;
    int nvar;
    int nextra;
    constexpr LuaArgCounts(int nr, int na, int nv, int ne) : nret(nr), narg(na), nvar(nv), nextra(ne) {}
    constexpr LuaArgCounts operator +(LuaArgCounts b) const {
        return LuaArgCounts(nret + b.nret, narg + b.narg, nvar + b.nvar, nextra + b.nextra);
    }
};


template<class... Ts>
struct LuaCountArgs;
template<>
struct LuaCountArgs<> {
    static constexpr LuaArgCounts value = LuaArgCounts(0,0,0,0);
};
template<class... Ts>
struct LuaCountArgs<LuaRet, Ts...> {
    static constexpr LuaArgCounts value = LuaArgCounts(1, 0, 0, 0) + LuaCountArgs<Ts...>::value;
};
template<class... Ts>
struct LuaCountArgs<LuaArg, Ts...> {
    static constexpr LuaArgCounts value = LuaArgCounts(0, 1, 0, 0) + LuaCountArgs<Ts...>::value;
};
template<class... Ts>
struct LuaCountArgs<LuaVar, Ts...> {
    static constexpr LuaArgCounts value = LuaArgCounts(0, 0, 1, 0) + LuaCountArgs<Ts...>::value;
};
template<class... Ts>
struct LuaCountArgs<LuaExtraArgs, Ts...> {
    static constexpr LuaArgCounts value = LuaArgCounts(0, 0, 0, 1) + LuaCountArgs<Ts...>::value;
};

////////////////////////////////////////////////////////////////////
//
// LuaDefStack
//
// This version of LuaStack should only be used inside a
// LuaDefine.  It handles the passing of arguments from lua
// to C++, and return values from C++ to lua.  See the
// markdown for more information.
//
////////////////////////////////////////////////////////////////////

class LuaDefStack : public LuaCoreStack {
private:
    int nret_;


public:
    template<class... SS>
    inline LuaDefStack(lua_State *L, SS & ... stackslots) : LuaCoreStack(L) {
        constexpr LuaArgCounts counts = LuaCountArgs<SS...>::value;
        int nargs = lua_gettop(L);
        if (counts.nextra == 0) {
            if (nargs != counts.narg) {
                luaL_error(L_, "function expects exactly %d arguments", counts.narg);
            }
        } else {
            if (nargs < counts.narg) {
                luaL_error(L_, "function expects at least %d arguments", counts.narg);
            }
        }
        lua_checkstack(L, counts.nret + counts.nvar + 20);
        lua_insert_frame(L, counts.nret + counts.nvar);
        vassign_slots(1, 1 + counts.nret + counts.nvar, 1 + counts.nret, 1 + counts.nret + counts.nvar + counts.narg, nargs - counts.narg, stackslots...);
        nret_ = counts.nret;
    }

    int result() {
        lua_settop(L_, nret_);
        return nret_;
    }

    // Tail-call into lua.
    //
    // This is meant to be used as follows: return LS.tailcall(passup, func, arg, arg...)
    //
    // If passup is true, the return value to our caller consists of our
    // LuaRet arguments concatenated to the return values from the tail-call.
    // If passup is false, the return value to our caller consists solely
    // of our LuaRet arguments.
    //
    template<typename... T>
    int tailcall(bool passup, LuaSlot func, T... args) {
        lua_checkstack(L_, nret_ + 20);
        int base = lua_gettop(L_);
        for (int i = 1; i <= nret_; i++) {
            lua_pushvalue(L_, i);
        }
        push_any_value(func);
        int argbase = lua_gettop(L_);
        push_any_values(args...);
        int nargs = lua_gettop(L_) - argbase;
        return tailcall_internal(passup, base, nargs);
    }
    
    ~LuaDefStack() { }

private:
    int tailcall_internal(bool passup, int base, int nargs);
};

////////////////////////////////////////////////////////////////////
//
// LuaExtStack
//
// This version of LuaStack is meant to be used in any context where
// you want to assign stack slots to some LuaVars, and then you want
// to automatically deallocate those LuaVars when the LuaExtStack
// goes out of scope.  See the markdown for more information.
//
////////////////////////////////////////////////////////////////////

class LuaExtStack : public LuaCoreStack {
private:
    int oldtop_;

public:
    template<class... SS>
    LuaExtStack(lua_State *L, SS & ... stackslots) : LuaCoreStack(L) {
        constexpr LuaArgCounts counts = LuaCountArgs<SS...>::value;
        static_assert(counts.narg == 0, "LuaExtStack only allows LuaVar, not LuaArg");
        static_assert(counts.nret == 0, "LuaExtStack only allows LuaVar, not LuaRet");
        static_assert(counts.nextra == 0, "LuaExtStack only allows LuaVar, not LuaExtraArgs");
        lua_checkstack(L_, counts.nvar + 20);
        oldtop_ = lua_gettop(L_);
        for (int i = 0; i < counts.nvar; i++) {
            lua_pushnil(L_);
        }
        vassign_slots(0, 0, oldtop_ + 1, 0, 0, stackslots...);
    }

    template<class... SS>
    LuaExtStack(const LuaCoreStack &LS0, SS & ... stackslots) : LuaCoreStack(LS0.state(), stackslots...) {}

    int oldtop() const { return oldtop_; }

    ~LuaExtStack() {
        if (!lua_isthrowing(L_)) {
            if (lua_gettop(L_) > oldtop_) {
                lua_settop(L_, oldtop_);
            }
        }
    }
};




////////////////////////////////////////////////////////////////////
//
// The Lua Constant Registry
//
////////////////////////////////////////////////////////////////////

class LuaConstantReg : public eng::nevernew {
private:
    const char *name_;
    const char *docs_;
    LuaToken tokenvalue_;
    lua_Number numbervalue_;
    LuaConstantReg *next_;

public:
    static LuaConstantReg *All;
    LuaConstantReg(const char *name, const char *docs, LuaToken tokenvalue, lua_Number numbervalue);

    const char *get_name() const { return name_; }
    const char *get_docs() const { return docs_; }
    LuaToken get_tokenvalue() const { return tokenvalue_; }
    lua_Number get_numbervalue() const { return numbervalue_; }
    LuaConstantReg *next() const { return next_; }
};

////////////////////////////////////////////////////////////////////
//
// The Lua Function Registry
//
////////////////////////////////////////////////////////////////////

class LuaFunctionReg : public eng::nevernew {
private:
    const char *name_;
    const char *args_;
    const char *docs_;
    bool sandbox_;
    lua_CFunction func_;
    LuaFunctionReg *next_;

public:
    static LuaFunctionReg *All;
    LuaFunctionReg(const char *name, const char *args, const char *docs, bool sand, lua_CFunction f);
    static const LuaFunctionReg *lookup(lua_CFunction fn);

    const char *get_name() const { return name_; }
    const char *get_args() const { return args_; }
    const char *get_docs() const { return docs_; }
    lua_CFunction get_func() const { return func_; }
    bool get_sandbox() const { return sandbox_; }
    LuaFunctionReg *next() const { return next_; }
    void set_func(lua_CFunction fn) { func_ = fn; }
};

////////////////////////////////////////////////////////////////////
//
// LuaDefine and friends.
//
////////////////////////////////////////////////////////////////////

#define LuaTokenConstant(name, tvalue, docs) \
    LuaToken ltoken_##name(tvalue); \
    LuaConstantReg reg_##name(#name, docs, LuaToken(tvalue), 0);

#define LuaNumberConstant(name, nvalue, docs) \
    lua_Number lnumber_##name(nvalue); \
    LuaConstantReg reg_##name(#name, docs, LuaToken(), nvalue);

#define LuaDefine(name, args, docs) \
    int lfn_##name(lua_State *L); \
    const char *lfnarg_##name = args; \
    const char *lfndoc_##name = docs; \
    LuaFunctionReg reg_##name(#name, lfnarg_##name, lfndoc_##name, false, lfn_##name); \
    int lfn_##name(lua_State *L)

#define LuaDefineAlias(name1, name2) \
    LuaFunctionReg reg_##name1(#name1, lfnarg_##name2, lfndoc_##name2, false, lfn_##name2); \

#define LuaDefineBuiltin(name, args, docs) \
    LuaFunctionReg reg_##name(#name, args, docs, false, nullptr);

#define LuaSandboxBuiltin(name, args, docs) \
    LuaFunctionReg reg_##name(#name, args, docs, true, nullptr);

#define LuaStringify(x) #x
#define LuaAssert(L, x) if (!(x)) { luaL_error((L), "Assert failed: %s (file %s line %d)", LuaStringify(x), __FILE__, __LINE__); }
#define LuaAssertStrEq(L, x, y) { eng::string _s1_(x); eng::string _s2_(y); if (_s1_ != _s2_) luaL_error((L), "Assert failed: value=%s (file %s line %d)", _s1_.c_str(), __FILE__, __LINE__); }
