{-# LANGUAGE MultiParamTypeClasses      #-}
{- |
   Module      : Text.Pandoc.Parsing
   Copyright   : © 2006-2023 John MacFarlane
   License     : GPL-2.0-or-later
   Maintainer  : John MacFarlane <jgm@berkeley.edu>

Parser state capabilities.
-}
module Text.Pandoc.Parsing.Capabilities
  ( -- * Capabilities

    -- ** Element identifiers
    HasIdentifierList (..)

    -- ** Include files
  , HasIncludeFiles (..)

    -- ** String/Word boundaries
  , HasLastStrPosition (..)
  , updateLastStrPos
  , notAfterString

    -- ** Logging
  , HasLogMessages (..)
  , logMessage
  , reportLogMessages

    -- ** Macros
  , HasMacros (..)

    -- ** Quote context
  , QuoteContext (..)
  , HasQuoteContext (..)
  , failIfInQuoteContext

    -- ** Reader options
  , HasReaderOptions (..)
  , guardEnabled
  , guardDisabled
  )
where

import Control.Monad (guard, when)
import Data.Text (Text)
import Text.Parsec (SourcePos, Stream, ParsecT,
                     getPosition, getState, updateState)
import Text.Pandoc.Class.PandocMonad (PandocMonad, report)
import Text.Pandoc.Logging (LogMessage)
import Text.Pandoc.Options
  ( Extension
  , ReaderOptions(readerExtensions)
  , extensionEnabled
  )
import Text.Pandoc.TeX (Macro)

import qualified Data.Map as M
import qualified Data.Set as Set

class HasReaderOptions st where
  extractReaderOptions :: st -> ReaderOptions
  getOption            :: (Stream s m t) => (ReaderOptions -> b) -> ParsecT s st m b
  -- default
  getOption  f         = f . extractReaderOptions <$> getState

class HasQuoteContext st m where
  getQuoteContext :: (Stream s m t) => ParsecT s st m QuoteContext
  withQuoteContext :: QuoteContext -> ParsecT s st m a -> ParsecT s st m a

failIfInQuoteContext :: (HasQuoteContext st m, Stream s m t)
                     => QuoteContext
                     -> ParsecT s st m ()
failIfInQuoteContext context = do
  context' <- getQuoteContext
  when (context' == context) $ Prelude.fail "already inside quotes"

class HasIdentifierList st where
  extractIdentifierList  :: st -> Set.Set Text
  updateIdentifierList   :: (Set.Set Text -> Set.Set Text) -> st -> st

class HasMacros st where
  extractMacros          :: st -> M.Map Text Macro
  updateMacros           :: (M.Map Text Macro -> M.Map Text Macro) -> st -> st

class HasLastStrPosition st where
  setLastStrPos  :: Maybe SourcePos -> st -> st
  getLastStrPos  :: st -> Maybe SourcePos

class HasLogMessages st where
  addLogMessage :: LogMessage -> st -> st
  getLogMessages :: st -> [LogMessage]

class HasIncludeFiles st where
  getIncludeFiles :: st -> [Text]
  addIncludeFile :: Text -> st -> st
  dropLatestIncludeFile :: st -> st

-- | Add a log message.
logMessage :: (Stream s m a, HasLogMessages st)
           => LogMessage -> ParsecT s st m ()
logMessage msg = updateState (addLogMessage msg)

-- | Report all the accumulated log messages, according to verbosity level.
reportLogMessages :: (PandocMonad m, HasLogMessages st) => ParsecT s st m ()
reportLogMessages = do
  msgs <- getLogMessages <$> getState
  mapM_ report msgs

-- | Succeed only if the extension is enabled.
guardEnabled :: (Stream s m a,  HasReaderOptions st)
             => Extension -> ParsecT s st m ()
guardEnabled ext =
  getOption readerExtensions >>= guard . extensionEnabled ext

-- | Succeed only if the extension is disabled.
guardDisabled :: (Stream s m a, HasReaderOptions st)
              => Extension -> ParsecT s st m ()
guardDisabled ext =
  getOption readerExtensions >>= guard . not . extensionEnabled ext

-- | Update the position on which the last string ended.
updateLastStrPos :: (Stream s m a, HasLastStrPosition st)
                 => ParsecT s st m ()
updateLastStrPos = getPosition >>= updateState . setLastStrPos . Just

-- | Whether we are right after the end of a string.
notAfterString :: (Stream s m a, HasLastStrPosition st) => ParsecT s st m Bool
notAfterString = do
  pos <- getPosition
  st  <- getState
  return $ getLastStrPos st /= Just pos

data QuoteContext
    = InSingleQuote   -- ^ Used when parsing inside single quotes
    | InDoubleQuote   -- ^ Used when parsing inside double quotes
    | NoQuote         -- ^ Used when not parsing inside quotes
    deriving (Eq, Show)
