{-# LANGUAGE OverloadedStrings #-}

-- An example program that demonstrates error propagation between Haskell and
-- Lua. It creates a function call stack with 10 functions, like this:
--
--   Lua function
--   ...
--   Haskell function
--   Lua function
--   Program
--
-- And then the function at the top throws an error, according to the error
-- conventions described in the docs. Error is propagated to the program at the
-- bottom.
--
-- Then same thing happens, starting with Haskell function:
--
--   Haskell function
--   ...
--   Lua function
--   Haskell function
--   Program

import qualified Data.ByteString.Char8 as BC
import Foreign.C.Types (CInt)
import Scripting.Lua as Lua

main :: IO ()
main = do
    l <- newstate
    openlibs l
    registerrawhsfunction l "fail_when_zero_haskell" failWhenZero

    -- Define the Lua function
    loadfile l "examples/err_prop/err_prop.lua"
    call l 0 0

    -- Start the loop by calling Lua function with argument 10
    getfield l globalsindex "fail_when_zero"
    pushinteger l 10
    -- Since Lua function will be the one that propagates error to the program,
    -- we need to catch it using `pcall`
    ret <- pcall l 1 1 0
    errMsg <- tostring l 1
    putStrLn $ "ret: " ++ show ret -- TODO: Implement pcall return values as a type
    putStrLn $ "errMsg: " ++ BC.unpack errMsg

    top <- gettop l
    putStrLn $ "top: " ++ show top
    pop l 1

    -- start the loop by calling Haskell function with argument 10
    getfield l globalsindex "fail_when_zero_haskell"
    pushinteger l 10
    -- Our convention is that Haskell functions never use `lua_error` because
    -- it's never safe(it's not even exported by the library for this reason).
    -- So if we're calling a Haskell function that `pcall` and `call` does the
    -- same thing.
    call l 1 2
    -- We know it failed, so just read the error message without checking first
    -- argument
    errMsg <- tostring l 2
    putStrLn $ "errMsg: " ++ BC.unpack errMsg
    pop l 2

    close l

failWhenZero :: LuaState -> IO CInt
failWhenZero l = do
    i <- tointeger l 1
    putStrLn $ "Haskell: " ++ show i
    if i == 0
      then pushstring l "Failing from Haskell" >> fmap fromIntegral (lerror l)
      else do
        getfield l globalsindex "fail_when_zero"
        pushinteger l (i - 1)
        ret <- pcall l 1 1 0
        if ret /= 0
          then
            -- propagate the error. no need to push error message since it's
            -- already at the top of the stack at this point. (because of how
            -- `pcall` works)
            fmap fromIntegral (lerror l)
          else
            -- Lua function's return value is on the stack, return it
            return 1
