{-# LANGUAGE TypeFamilies, CPP #-}

-- | The writer monad applied to 'LaTeX' values. Useful to compose 'LaTeX' values
--   using the @do@ notation:
--
-- > anExample :: Monad m => LaTeXT m ()
-- > anExample = do
-- >   documentclass [] article
-- >   author "Daniel Monad"
-- >   title "LaTeX and do notation"
-- >   document $ do
-- >     maketitle
-- >     section "Some words"
-- >     "Using " ; texttt "do" ; " notation "
-- >     "you avoid many ocurrences of the "
-- >     texttt "(<>)" ; " operator and a lot of "
-- >     "parentheses. With the cost of a monad."
--
-- Since 'LaTeXT' is a monad transformer, you can do also:
--
-- > anotherExample :: LaTeXT IO ()
-- > anotherExample = lift (readFileTex "foo") >>= verbatim
--
-- This way, it is easy (without carrying arguments) to include IO outputs
-- in the LaTeX document, like files, times or random objects.
--
-- Another approach could be to have custom counters, label management
-- or any other user-defined feature.
--
-- Of course, you can always use the simpler interface provided by the plain 'LaTeX' type.
--
module Text.LaTeX.Base.Writer
 ( -- * @LaTeXT@ writer
   LaTeXT
 , runLaTeXT
 , execLaTeXT
   -- ** Synonyms
 , LaTeXT_
 , LaTeXM
 , runLaTeXM
 , execLaTeXM
   -- * Utils
 , execLaTeXTWarn
 , extractLaTeX
 , extractLaTeX_
 , textell
 , rendertexM
 , liftFun
 , liftOp
 , mapLaTeXT
   -- * Re-exports
 , lift
 , liftIO
   ) where

-- base
import Control.Monad (liftM, liftM2)
import Control.Arrow
import Data.String
#if !MIN_VERSION_base(4,8,0)
import Data.Monoid
import Control.Applicative
#endif
-- transformers
import Control.Monad.Trans.Writer
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Data.Functor.Identity
-- HaTeX
import Text.LaTeX.Base.Syntax
import Text.LaTeX.Base.Class
import Text.LaTeX.Base.Render
import Text.LaTeX.Base.Warnings (Warning,checkAll,check)

-- | 'WriterT' monad transformer applied to 'LaTeX' values.
newtype LaTeXT m a =
  LaTeXT { unwrapLaTeXT :: WriterT LaTeX m a }

instance Functor f => Functor (LaTeXT f) where
 fmap f = LaTeXT . fmap f . unwrapLaTeXT

instance Applicative f => Applicative (LaTeXT f) where
 pure = LaTeXT . pure
 (LaTeXT f) <*> (LaTeXT x) = LaTeXT $ f <*> x

-- | Type synonym for empty 'LaTeXT' computations.
type LaTeXT_ m = LaTeXT m ()

-- | The 'LaTeXT' monad transformed applied to 'Identity'.
type LaTeXM = LaTeXT Identity

-- | A particular case of 'runLaTeXT'.
--
-- > runLaTeXM = runIdentity . runLaTeXT
--
runLaTeXM :: LaTeXM a -> (a, LaTeX)
runLaTeXM = runIdentity . runLaTeXT

-- | A particular case of 'execLaTeXT'.
--
-- > execLaTeXM = runIdentity . execLaTeXT
--
execLaTeXM :: LaTeXM a -> LaTeX
execLaTeXM = runIdentity . execLaTeXT

instance MonadTrans LaTeXT where
 lift = LaTeXT . lift

instance Monad m => Monad (LaTeXT m) where
 return = LaTeXT . return
 (LaTeXT c) >>= f = LaTeXT $ do 
  a <- c
  let LaTeXT c' = f a
  c'
 fail = return . error

instance MonadIO m => MonadIO (LaTeXT m) where
 liftIO = lift . liftIO

instance (Monad m, a ~ ()) => LaTeXC (LaTeXT m a) where
 liftListL f xs = mapM extractLaTeX_ xs >>= textell . f

-- | Running a 'LaTeXT' computation returns the final 'LaTeX' value.
runLaTeXT :: LaTeXT m a -> m (a,LaTeX)
runLaTeXT = runWriterT . unwrapLaTeXT

-- | This is the usual way to run the 'LaTeXT' monad
--   and obtain a 'LaTeX' value.
--
-- > execLaTeXT = liftM snd . runLaTeXT
--
-- If @anExample@ is defined as above (at the top of this module
-- documentation), use the following to get the LaTeX value
-- generated out.
--
-- > myLaTeX :: Monad m => m LaTeX
-- > myLaTeX = execLaTeXT anExample
--
execLaTeXT :: Monad m => LaTeXT m a -> m LaTeX
execLaTeXT = liftM snd . runLaTeXT

-- | Version of 'execLaTeXT' with possible warning messages.
--   This function applies 'checkAll' to the 'LaTeX' output.
execLaTeXTWarn :: Monad m => LaTeXT m a -> m (LaTeX,[Warning])
execLaTeXTWarn = liftM (id &&& check checkAll) . execLaTeXT

-- | This function run a 'LaTeXT' computation,
--   lifting the result again in the monad.
extractLaTeX :: Monad m => LaTeXT m a -> LaTeXT m (a,LaTeX)
extractLaTeX = LaTeXT . lift . runWriterT . unwrapLaTeXT

-- | Executes a 'LaTeXT' computation, embedding it again in
--   the 'LaTeXT' monad.
--
-- > extractLaTeX_ = liftM snd . extractLaTeX
--
-- This function was heavily used in the past by HaTeX-meta
-- to generate those @.Monad@ modules. The current purpose
-- is to implement the 'LaTeXC' instance of 'LaTeXT', which
-- is closely related.
extractLaTeX_ :: Monad m => LaTeXT m a -> LaTeXT m LaTeX
extractLaTeX_ = liftM snd . extractLaTeX

-- | With 'textell' you can append 'LaTeX' values to the
--   state of the 'LaTeXT' monad.
textell :: Monad m => LaTeX -> LaTeXT m ()
textell = LaTeXT . tell

-- | Lift a function over 'LaTeX' values to a function
--   acting over the state of a 'LaTeXT' computation.
liftFun :: Monad m
        => (LaTeX -> LaTeX)
        -> (LaTeXT m a -> LaTeXT m a)
liftFun f (LaTeXT c) = LaTeXT $ do
 (p,l) <- lift $ runWriterT c
 tell $ f l
 return p

-- | Lift an operator over 'LaTeX' values to an operator
--   acting over the state of two 'LaTeXT' computations.
--
-- /Note: The returned value is the one returned by the/
-- /second argument of the lifted operator./
liftOp :: Monad m
       => (LaTeX -> LaTeX -> LaTeX)
       -> (LaTeXT m a -> LaTeXT m b -> LaTeXT m b)
liftOp op (LaTeXT c) (LaTeXT c') = LaTeXT $ do
 (_,l)  <- lift $ runWriterT c
 (p,l') <- lift $ runWriterT c'
 tell $ l `op` l'
 return p

-- | A helper function for building monad transformers, e.g.
-- @@
--       instance MonadReader r m => MonadReader r (LaTeXT m) where
--         ask = lift ask
--         local = mapLaTeXT . local
-- @@
-- This declaration could be included here, but it would add a
-- dependency on mtl.
mapLaTeXT :: (m (a, LaTeX) -> m (a, LaTeX)) -> LaTeXT m a -> LaTeXT m a
mapLaTeXT f = LaTeXT . mapWriterT f . unwrapLaTeXT

-- | Just like 'rendertex', but with 'LaTeXT' output.
--
-- > rendertexM = textell . rendertex
--
rendertexM :: (Render a, Monad m) => a -> LaTeXT m ()
rendertexM = textell . rendertex

-- Overloaded Strings

instance (Monad m, a ~ ()) => IsString (LaTeXT m a) where
 fromString = textell . fromString

-- Monoids

instance (Monad m, Monoid a) => Monoid (LaTeXT m a) where
 mempty = return mempty
 mappend = liftM2 mappend