module Foreign.Lua.Util
  ( getglobal'
  , setglobal'
  , run
  , runEither
  , raiseError
  , Optional (Optional, fromOptional)
  
  , peekEither
  , peekRead
  , popValue
  ) where
import Control.Exception (bracket, try)
import Data.List (groupBy)
import Foreign.Lua.Core (Lua, NumResults, StackIndex)
import Foreign.Lua.Types (Peekable, Pushable)
import Text.Read (readMaybe)
import qualified Control.Monad.Catch as Catch
import qualified Foreign.Lua.Core as Lua
import qualified Foreign.Lua.Types as Lua
run :: Lua a -> IO a
run = (Lua.newstate `bracket` Lua.close) . flip Lua.runWith . Catch.mask_
runEither :: Lua a -> IO (Either Lua.Exception a)
runEither = try . run
getglobal' :: String -> Lua ()
getglobal' = getnested . splitdot
setglobal' :: String -> Lua ()
setglobal' s =
  case reverse (splitdot s) of
    [] ->
      return ()
    [_] ->
      Lua.setglobal s
    (lastField : xs) -> do
      getnested (reverse xs)
      Lua.pushvalue (Lua.nthFromTop 2)
      Lua.setfield (Lua.nthFromTop 2) lastField
      Lua.pop 1
splitdot :: String -> [String]
splitdot = filter (/= ".") . groupBy (\a b -> a /= '.' && b /= '.')
getnested :: [String] -> Lua ()
getnested [] = return ()
getnested (x:xs) = do
  Lua.getglobal x
  mapM_ (\a -> Lua.getfield Lua.stackTop a *> Lua.remove (Lua.nthFromTop 2)) xs
raiseError :: Pushable a => a -> Lua NumResults
raiseError e = do
  Lua.push e
  Lua.error
{-# INLINABLE raiseError #-}
newtype Optional a = Optional { fromOptional :: Maybe a }
instance Peekable a => Peekable (Optional a) where
  peek idx = do
    noValue <- Lua.isnoneornil idx
    if noValue
      then return $ Optional Nothing
      else Optional . Just <$> Lua.peek idx
instance Pushable a => Pushable (Optional a) where
  push (Optional Nothing)  = Lua.pushnil
  push (Optional (Just x)) = Lua.push x
peekRead :: Read a => StackIndex -> Lua a
peekRead idx = do
  s <- Lua.peek idx
  case readMaybe s of
    Just x -> return x
    Nothing -> Lua.throwException ("Could not read: " ++ s)
peekEither :: Peekable a => StackIndex -> Lua (Either String a)
peekEither idx = either (Left . Lua.exceptionMessage) Right <$>
                 Lua.try (Lua.peek idx)
popValue :: Peekable a => Lua a
popValue = Lua.peek Lua.stackTop `Catch.finally` Lua.pop 1
{-# INLINABLE popValue #-}