{-# LANGUAGE OverloadedStrings      #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses  #-}

{-|

'Snap.Extension.Heist' exports the 'MonadHeist' interface which allows you to
integrate Heist templates into your Snap application. The interface's
operations are 'heistServe', 'heistServeSingle', 'heistLocal' and 'render'. As
a convenience, we also provide 'renderWithSplices' that combines 'heistLocal'
and 'render' into a single function call.

'Snap.Extension.Heist.Impl' contains the only implementation of this interface
and can be used to turn your application's monad into a 'MonadHeist'.

'MonadHeist' is unusual among Snap extensions in that it's a multi-parameter
typeclass. The last parameter is your application's monad, and the first is
the monad you want the 'TemplateState' to use. This is usually, but not
always, also your application's monad.

This module should not be used directly. Instead, import
"Snap.Extension.Heist.Impl" in your application.

-}

module Snap.Extension.Heist
  ( MonadHeist(..)
  , renderWithSplices ) where

import           Control.Applicative
import           Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as B
import           Data.Text (Text)
import           Snap.Types
import           Snap.Util.FileServe
import           Text.Templating.Heist


------------------------------------------------------------------------------
-- | The 'MonadHeist' type class. Minimal complete definition: 'render',
-- 'heistLocal'.
class (Monad n, MonadSnap m) => MonadHeist n m | m -> n where
    -- | Renders a template as text\/html. If the given template is not found,
    -- this returns 'empty'.
    render     :: ByteString -> m ()

    -- | Renders a template as the given content type.  If the given template
    -- is not found, this returns 'empty'.
    renderAs   :: ByteString -> ByteString -> m ()

    -- | Runs an action with a modified 'TemplateState'. You might want to use
    -- this if you had a set of splices which were customised for a specific
    -- action. To do that you would do:
    --
    -- > heistLocal (bindSplices mySplices) $ render "myTemplate"
    heistLocal :: (TemplateState n -> TemplateState n) -> m a -> m a

    -- | Analogous to 'fileServe'. If the template specified in the request
    -- path is not found, it returns 'empty'.
    heistServe :: m ()
    heistServe = ifTop (render "index") <|> (render . B.pack =<< getSafePath)

    -- | Analogous to 'fileServeSingle'. If the given template is not found,
    -- this throws an error.
    heistServeSingle :: ByteString -> m ()
    heistServeSingle t = render t
        <|> error ("Template " ++ show t ++ " not found.")


------------------------------------------------------------------------------
-- | Helper function for common use case:
-- Render a template with a given set of splices.
renderWithSplices
  :: (MonadHeist n m)
  => ByteString                 -- ^ Template to render
  -> [(Text, Splice n)]   -- ^ Splice mapping
  -> m ()
renderWithSplices t sps = heistLocal bsps $ render t
  where bsps = bindSplices sps