// https://github.com/vinniefalco/LuaBridge
// Copyright 2012, Vinnie Falco <vinnie.falco@gmail.com>
// Copyright 2008, Nigel Atkinson <suprapilot+LuaCode@gmail.com>
// SPDX-License-Identifier: MIT

#pragma once

#include <exception>
#include <string>

namespace luabridge {

class LuaException : public std::exception
{
private:
    lua_State* m_L;
    std::string m_what;

public:
    //----------------------------------------------------------------------------
    /**
        Construct a LuaException after a lua_pcall().
    */
    LuaException(lua_State* L, int /*code*/) : m_L(L) { whatFromStack(); }

    //----------------------------------------------------------------------------

    LuaException(lua_State* L, char const*, char const*, long) : m_L(L) { whatFromStack(); }

    //----------------------------------------------------------------------------

    ~LuaException() throw() {}

    //----------------------------------------------------------------------------

    char const* what() const throw() { return m_what.c_str(); }

    //============================================================================
    /**
        Throw an exception.

        This centralizes all the exceptions thrown, so that we can set
        breakpoints before the stack is unwound, or otherwise customize the
        behavior.
    */
    template<class Exception>
    static void Throw(Exception e)
    {
        throw e;
    }

    //----------------------------------------------------------------------------
    /**
        Wrapper for lua_pcall that throws.
    */
    static void pcall(lua_State* L, int nargs = 0, int nresults = 0, int msgh = 0)
    {
        int code = lua_pcall(L, nargs, nresults, msgh);

        if (code != LUABRIDGE_LUA_OK)
            Throw(LuaException(L, code));
    }

    //----------------------------------------------------------------------------
    /**
        Initializes error handling. Subsequent Lua errors are translated to C++ exceptions.
    */
    static void enableExceptions(lua_State* L) { lua_atpanic(L, throwAtPanic); }

    /** Retrieve the lua_State associated with the exception.

      @returns A Lua state.
    */
    lua_State* state() const { return m_L; }

protected:
    void whatFromStack()
    {
        if (lua_gettop(m_L) > 0)
        {
            char const* s = lua_tostring(m_L, -1);
            m_what = s ? s : "";
        }
        else
        {
            // stack is empty
            m_what = "missing error";
        }
    }

private:
    static int throwAtPanic(lua_State* L) { throw LuaException(L, -1); }
};

//----------------------------------------------------------------------------
/**
    Initializes error handling. Subsequent Lua errors are translated to C++ exceptions.
*/
static void enableExceptions(lua_State* L)
{
    LuaException::enableExceptions(L);
}

} // namespace luabridge
