{-|
Module      : $Header$
Description : Functions for rendering mustache templates.
Copyright   : (c) Justus Adam, 2015
License     : LGPL-3
Maintainer  : development@justusadam.com
Stability   : experimental
Portability : POSIX
-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE UnicodeSyntax     #-}
module Text.Mustache.Render
  (
  -- * Substitution
    substitute, substituteValue
  -- * Working with Context
  , Context(..), search, innerSearch
  -- * Util
  , toString
  ) where

--
import           Control.Applicative    ((<|>), (<$>))
import           Data.Foldable          (fold, find)
import           Data.HashMap.Strict    as HM hiding (map)
import           Data.Monoid            (mempty, (<>))
import           Data.Text              hiding (concat, find, map, uncons)
import qualified Data.Text              as T
import           Data.Traversable       (traverse)
import qualified Data.Vector            as V
import           Text.HTML.TagSoup      (escapeHTML)
import           Text.Mustache.Internal
import           Text.Mustache.Types
import           Text.Printf


{-|
  Substitutes all mustache defined tokens (or tags) for values found in the
  provided data structure.

  Equivalent to @substituteValue . toMustache@.
-}
substitute  ToMustache j  MustacheTemplate  j  Either String Text
substitute t = substituteValue t . toMustache


{-|
  Substitutes all mustache defined tokens (or tags) for values found in the
  provided data structure.
-}
substituteValue  MustacheTemplate  Value  Either String Text
substituteValue (MustacheTemplate { ast = cAst, partials = cPartials }) dataStruct =
  joinSubstituted (substitute' (Context mempty dataStruct)) cAst
  where
    joinSubstituted f = fmap fold . traverse f

    -- Main substitution function
    substitute'  Context Value  MustacheNode Text  Either String Text

    -- subtituting text
    substitute' _ (MustacheText t) = return t

    -- substituting a whole section (entails a focus shift)
    substitute' context@(Context parents focus) (MustacheSection secName secAST) =
      case search context secName of
        Just arr@(Array arrCont) 
          if V.null arrCont
            then return mempty
            else flip joinSubstituted arrCont $ \focus' 
              let
                newContext = Context (arr:focus:parents) focus'
              in
                joinSubstituted (substitute' newContext) secAST
        Just (Bool b) | not b  return mempty
        Just (Lambda l)  l context secAST >>= joinSubstituted (substitute' context)
        Just focus' 
          let
            newContext = Context (focus:parents) focus'
          in
            joinSubstituted (substitute' newContext) secAST
        Nothing  return mempty

    -- substituting an inverted section
    substitute' context (MustacheInvertedSection invSecName invSecAST) =
      case search context invSecName of
        Just (Bool False)  contents
        Just (Array a) | V.null a  contents
        Nothing  contents
        _  return mempty
      where
        contents = joinSubstituted (substitute' context) invSecAST

    -- substituting a variable
    substitute' context (MustacheVariable escaped varName) =
      return $ maybe
        mempty
        (if escaped then escapeHTML else id)
        $ toString <$> search context varName

    -- substituting a partial
    substitute' context (MustachePartial pName) =
      maybe
        (Left $ printf "Could not find partial '%s'" pName)
        (joinSubstituted (substitute' context) . ast)
        $ find ((== pName) . name) cPartials


search  Context Value  [T.Text]  Maybe Value
search _ [] = Nothing
search (Context parents focus) val@(x:xs) =
  (
    ( case focus of
      (Object o)  HM.lookup x o
      _  Nothing
    )
    <|> (do
          (newFocus, newParents) <- uncons parents
          search (Context newParents newFocus) val
        )
  )
    >>= innerSearch xs



innerSearch  [T.Text]  Value  Maybe Value
innerSearch [] v = Just v
innerSearch (y:ys) (Object o) = HM.lookup y o >>= innerSearch ys
innerSearch _ _ = Nothing


toString  Value  Text
toString (String t) = t
toString e = pack $ show e