{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Text.Pandoc.Lua.PandocLua
  ( PandocLua (..)
  , runPandocLua
  , liftPandocLua
  , addFunction
  , loadScriptFromDataDir
  ) where
import Control.Monad (when)
import Control.Monad.Catch (MonadCatch, MonadMask, MonadThrow)
import Control.Monad.Except (MonadError (catchError, throwError))
import Control.Monad.IO.Class (MonadIO (liftIO))
import Foreign.Lua (Lua (..), NumResults, Pushable, ToHaskellFunction)
import Text.Pandoc.Class.PandocIO (PandocIO)
import Text.Pandoc.Class.PandocMonad (PandocMonad (..), readDataFile)
import Text.Pandoc.Error (PandocError)
import Text.Pandoc.Lua.Global (Global (..), setGlobals)
import Text.Pandoc.Lua.ErrorConversion (errorConversion)
import qualified Control.Monad.Catch as Catch
import qualified Foreign.Lua as Lua
import qualified Text.Pandoc.Class.IO as IO
import qualified Text.Pandoc.Lua.Util as LuaUtil
newtype PandocLua a = PandocLua { unPandocLua :: Lua a }
  deriving
    ( Applicative
    , Functor
    , Monad
    , MonadCatch
    , MonadIO
    , MonadMask
    , MonadThrow
    )
liftPandocLua :: Lua a -> PandocLua a
liftPandocLua = PandocLua
runPandocLua :: PandocLua a -> PandocIO a
runPandocLua pLua = do
  origState <- getCommonState
  globals <- defaultGlobals
  (result, newState) <- liftIO . Lua.run' errorConversion . unPandocLua $ do
    putCommonState origState
    liftPandocLua $ setGlobals globals
    r <- pLua
    c <- getCommonState
    return (r, c)
  putCommonState newState
  return result
instance {-# OVERLAPPING #-} ToHaskellFunction (PandocLua NumResults) where
  toHsFun _narg = unPandocLua
instance Pushable a => ToHaskellFunction (PandocLua a) where
  toHsFun _narg x = 1 <$ (unPandocLua x >>= Lua.push)
addFunction :: ToHaskellFunction a => String -> a -> PandocLua ()
addFunction name fn = liftPandocLua $ do
  Lua.push name
  Lua.pushHaskellFunction fn
  Lua.rawset (-3)
loadScriptFromDataDir :: FilePath -> PandocLua ()
loadScriptFromDataDir scriptFile = do
  script <- readDataFile scriptFile
  status <- liftPandocLua $ Lua.dostring script
  when (status /= Lua.OK) . liftPandocLua $
    LuaUtil.throwTopMessageAsError'
      (("Couldn't load '" ++ scriptFile ++ "'.\n") ++)
defaultGlobals :: PandocIO [Global]
defaultGlobals = do
  commonState <- getCommonState
  return
    [ PANDOC_API_VERSION
    , PANDOC_STATE commonState
    , PANDOC_VERSION
    ]
instance MonadError PandocError PandocLua where
  catchError = Catch.catch
  throwError = Catch.throwM
instance PandocMonad PandocLua where
  lookupEnv = IO.lookupEnv
  getCurrentTime = IO.getCurrentTime
  getCurrentTimeZone = IO.getCurrentTimeZone
  newStdGen = IO.newStdGen
  newUniqueHash = IO.newUniqueHash
  openURL = IO.openURL
  readFileLazy = IO.readFileLazy
  readFileStrict = IO.readFileStrict
  glob = IO.glob
  fileExists = IO.fileExists
  getDataFileName = IO.getDataFileName
  getModificationTime = IO.getModificationTime
  getCommonState = PandocLua $ do
    Lua.getglobal "PANDOC_STATE"
    Lua.peek Lua.stackTop
  putCommonState = PandocLua . setGlobals . (:[]) . PANDOC_STATE
  logOutput = IO.logOutput