{-# LANGUAGE CPP #-}
{-|
Module      : Lua.Auxiliary
Copyright   : © 2007–2012 Gracjan Polak;
              © 2012–2016 Ömer Sinan Ağacan;
              © 2017-2024 Albert Krewinkel
License     : MIT
Maintainer  : Albert Krewinkel <tarleb@hslua.org>
Stability   : beta
Portability : non-portable (depends on GHC)

Raw bindings to functions and constants of the auxiliary library.
-}
module Lua.Auxiliary
  ( -- * The Auxiliary Library
    luaL_getmetafield
  , luaL_getmetatable
  , luaL_loadbuffer
  , luaL_loadfile
  , luaL_loadfilex
  , luaL_openlibs
  , luaL_newmetatable
  , luaL_ref
  , luaL_testudata
  , luaL_traceback
  , luaL_unref
  , luaL_where
    -- ** Registry fields
  , loadedTableRegistryField
  , preloadTableRegistryField
    -- ** References
  , Reference (..)
  , fromReference
  , toReference
  ) where

import Foreign.C (CChar, CInt (CInt), CSize (CSize), CString)
import Lua.Types as Lua
import Foreign.Ptr
import System.IO.Unsafe (unsafePerformIO)
import qualified GHC.Foreign as GHC
import qualified GHC.IO.Encoding as GHC

#ifdef ALLOW_UNSAFE_GC
#define SAFTY unsafe
#else
#define SAFTY safe
#endif

-- * The Auxiliary Library

-- | Key, in the registry, for table of loaded modules.
loadedTableRegistryField :: String
loadedTableRegistryField = unsafePerformIO $ do
  GHC.peekCString GHC.char8 c_loaded_table
{-# NOINLINE loadedTableRegistryField #-}

foreign import capi unsafe "lauxlib.h value LUA_LOADED_TABLE"
  c_loaded_table :: CString

-- | Key, in the registry, for table of preloaded loaders.
preloadTableRegistryField :: String
preloadTableRegistryField = unsafePerformIO $ do
  GHC.peekCString GHC.char8 c_preload_table
{-# NOINLINE preloadTableRegistryField #-}

foreign import capi unsafe "lauxlib.h value LUA_PRELOAD_TABLE"
  c_preload_table :: CString

-- | Pushes onto the stack the field @e@ from the metatable of the
-- object at index @obj@ and returns the type of the pushed value. If
-- the object does not have a metatable, or if the metatable does not
-- have this field, pushes nothing and returns
-- @'Lua.Constants.LUA_TNIL'@.
foreign import capi SAFTY "lauxlib.h luaL_getmetafield"
  luaL_getmetafield :: Lua.State
                    -> StackIndex      -- ^ obj
                    -> CString         -- ^ e
                    -> IO TypeCode

-- | Pushes onto the stack the metatable associated with name tname in
-- the registry (see @'luaL_newmetatable'@) (__nil__ if there is no
-- metatable associated with that name). Returns the type of the pushed
-- value.
foreign import capi SAFTY "lauxlib.h luaL_getmetatable"
  luaL_getmetatable :: Lua.State -> CString -> IO Lua.TypeCode

-- | Loads a buffer as a Lua chunk. This function uses @lua_load@ to
-- load the chunk in the buffer pointed to by @buff@ with size @sz@.
--
-- This function returns the same results as @lua_load@. @name@ is the
-- chunk name, used for debug information and error messages.
foreign import capi SAFTY "lauxlib.h luaL_loadbuffer"
  luaL_loadbuffer :: Lua.State
                  -> Ptr CChar         -- ^ buff
                  -> CSize             -- ^ sz
                  -> CString           -- ^ name
                  -> IO Lua.StatusCode

-- | Equivalent to luaL_loadfilex with mode equal to @NULL@.
foreign import capi SAFTY "lauxlib.h luaL_loadfile"
  luaL_loadfile :: Lua.State
                -> Ptr CChar  -- ^ filename
                -> IO Lua.StatusCode

-- | Loads a file as a Lua chunk. This function uses @lua_load@ to load
-- the chunk in the file named filename. If filename is @NULL@, then it
-- loads from the standard input. The first line in the file is ignored
-- if it starts with a @#@.
--
-- The string mode works as in function @lua_load@.
--
-- This function returns the same results as @lua_load@, but it has an
-- extra error code LUA_ERRFILE for file-related errors (e.g., it cannot
-- open or read the file).
--
-- As @lua_load@, this function only loads the chunk; it does not run
-- it.
foreign import capi SAFTY "lauxlib.h luaL_loadfilex"
  luaL_loadfilex :: Lua.State
                 -> Ptr CChar  -- ^ filename
                 -> Ptr CChar  -- ^ mode
                 -> IO Lua.StatusCode

-- | If the registry already has the key @tname@, returns @0@.
-- Otherwise, creates a new table to be used as a metatable for
-- userdata, adds to this new table the pair @__name = tname@, adds to
-- the registry the pair @[tname] = new table@, and returns @1@. (The
-- entry @__name@ is used by some error-reporting functions.)
--
-- In both cases pushes onto the stack the final value associated with
-- @tname@ in the registry.
foreign import capi SAFTY "lauxlib.h luaL_newmetatable"
  luaL_newmetatable :: Lua.State -> CString {- ^ tname -} -> IO Lua.LuaBool

-- | Opens all standard Lua libraries into the given state.
--
-- <https://www.lua.org/manual/5.4/manual.html#luaL_openlibs>
foreign import capi unsafe "lualib.h luaL_openlibs"
  luaL_openlibs :: Lua.State -> IO ()

-- | Creates and returns a reference, in the table at index @t@, for the
-- object at the top of the stack (and pops the object).
--
-- A reference is a unique integer key. As long as you do not manually
-- add integer keys into table @t@, @luaL_ref@ ensures the uniqueness of
-- the key it returns. You can retrieve an object referred by reference
-- @r@ by calling @lua_rawgeti l t r@. Function @'luaL_unref'@ frees a
-- reference and its associated object.
--
-- If the object at the top of the stack is nil, @luaL_ref@ returns the
-- constant @'Lua.Constants.LUA_REFNIL'@. The constant
-- @'Lua.Constants.LUA_NOREF'@ is guaranteed to be different
-- from any reference returned by @luaL_ref@.
foreign import capi SAFTY "lauxlib.h luaL_ref"
  luaL_ref :: Lua.State -> StackIndex {- ^ t -} -> IO CInt

-- | Creates and pushes a traceback of the stack @l1@. If @msg@ is not
-- @NULL@ it is appended at the beginning of the traceback. The level
-- parameter tells at which level to start the traceback.
foreign import capi SAFTY "lauxlib.h luaL_traceback"
  luaL_traceback :: Lua.State  -- ^ l
                 -> Lua.State  -- ^ l1
                 -> CString    -- ^ msg
                 -> CInt       -- ^ level
                 -> IO ()

-- | Releases reference @ref@ from the table at index @t@ (see
-- @'luaL_ref'@). The entry is removed from the table, so that the
-- referred object can be collected. The reference @ref@ is also freed
-- to be used again.
foreign import capi SAFTY "lauxlib.h luaL_unref"
  luaL_unref :: Lua.State -> StackIndex {- ^ t -} -> CInt {- ^ ref -} -> IO ()

-- | Checks whether the function argument @arg@ is a userdata of the
-- type @tname@ (see @'luaL_newmetatable'@) and returns the userdata
-- address (see @'Lua.lua_touserdata'@). Returns @NULL@ if the
-- test fails.
--
-- <https://www.lua.org/manual/5.4/manual.html#luaL_testudata>
foreign import capi SAFTY "lauxlib.h luaL_testudata"
  luaL_testudata :: Lua.State   -- ^ l
                 -> StackIndex  -- ^ arg
                 -> CString     -- ^ tname
                 -> IO (Ptr ())

-- | Pushes onto the stack a string identifying the current position of
-- the control at level @lvl@ in the call stack. Typically this string
-- has the following format:
--
-- > chunkname:currentline:
--
-- Level 0 is the running function, level 1 is the function that called
-- the running function, etc.
--
-- This function is used to build a prefix for error messages.
foreign import capi SAFTY "lauxlib.h luaL_where"
  luaL_where :: Lua.State -- ^ l
             -> CInt      -- ^ lvl
             -> IO ()

--
-- References
--

-- | Reference to a stored value.
data Reference =
    Reference CInt -- ^ Reference to a stored value
  | RefNil         -- ^ Reference to a nil value
  deriving (Eq, Show)

foreign import capi SAFTY "lauxlib.h value LUA_REFNIL"
  refnil :: CInt

-- | Convert a reference to its C representation.
fromReference :: Reference -> CInt
fromReference = \case
  Reference x -> x
  RefNil      -> refnil

-- | Create a reference from its C representation.
toReference :: CInt -> Reference
toReference x =
  if x == refnil
  then RefNil
  else Reference x
