-- | Module for reading templates from files
module Text.StringTemplates.TemplatesLoader ( Templates
                                            , GlobalTemplates
                                            , localizedVersion
                                            , readGlobalTemplates
                                            , renderTemplateMain
                                            , getTemplatesModTime
                                            ) where

import Prelude hiding (fail)
import Data.List (isSuffixOf,find)
import Data.Maybe (fromMaybe)
import Text.StringTemplate
import Control.Monad hiding (fail)
import Control.Monad.Fail (MonadFail(..))
import Control.Monad.IO.Class
import qualified Data.Map as Map
import Text.Html (stringToHtmlString)
import Data.Time.Clock
import Text.StringTemplates.Files
import Text.StringTemplates.TextTemplates
import Text.StringTemplates.Utils

-- | Group of string templates
type Templates = STGroup String
-- | Global map of templates (for a project),
--   indexed by a language name (for text string templates, see TextTemplates for doc)
type GlobalTemplates = Map.Map String Templates

-- | Retrieve templates for specified language name
localizedVersion :: String -> GlobalTemplates -> Templates
localizedVersion col mtemplates = fromMaybe (error $ "localizedVersion: undefined language: " ++ show col) $ Map.lookup col mtemplates

-- Fixme: Make this do only one read of all files !!
-- | Reads text templates and templates from files (see TextTemplates and Files modules docs respectively).
readGlobalTemplates :: (MonadIO m, MonadFail m) =>
                      FilePath   -- ^ dir path to recursively scan for .json files containing text templates
                    -> FilePath  -- ^ dir path to recursively scan for .st files containing string templates
                    -> String    -- ^ default language. We can guarantee that empty language texts will be replaced
                    -> m GlobalTemplates
readGlobalTemplates textTemplatesFilePath templatesDirPath  defaultLang = do
  files <- liftIO $ directoryFilesRecursive templatesDirPath
  let templatesFilePaths = filter (".st" `isSuffixOf`) files
  ts <- liftIO $ mapM getTemplates templatesFilePaths
  tts <- liftIO $ getTextTemplates textTemplatesFilePath
  let defaultLangTemplates = case (find (\l -> defaultLang == fst l) $ Map.toList tts) of
                               Just (_,t) -> t
                               Nothing -> error $ "Default language " ++ defaultLang ++ " is not defined."
  liftM Map.fromList $ forM (Map.toList tts) $ \(col,t) -> do
    let  t' =  fixTT t defaultLangTemplates
    checked <- mapM newCheckedTemplate $ (concat ts) ++ t'
    return ((col, groupStringTemplates checked)::(String, Templates))

-- All empty templates will be replaced by values from default lang.
-- Missing templates from defaul lang will be added
fixTT:: [(String, String)] -> [(String, String)] -> [(String, String)]
fixTT [] d = d
fixTT ((n,""):r) d = case find (\x -> n == fst x) d of
                          Just t -> t :  fixTT r (filter (\x -> n /= fst x) d)
                          Nothing -> (n,"") : fixTT r d
fixTT ((n,v):r) d = (n,v) :  fixTT r (filter (\x -> n /= fst x) d)



newCheckedTemplate :: (Monad m, MonadFail m) => (String, String) -> m (String, StringTemplate String)
newCheckedTemplate (n,v) = do
  let t = newSTMP v
      (errors, _, _) = checkTemplate t
  maybe (return ()) (\e -> fail $ "newCheckedTemplate: problem with template " ++ show n ++ ": " ++ e) errors
  return (n,t)

-- | Returns the latest modification time across all template files
getTemplatesModTime :: FilePath   -- ^ path to dir containing .csv files with template files
                    -> FilePath -- ^ dir path to recursively scan for .st files containing string templates
                    -> IO UTCTime
getTemplatesModTime textTemplatesDir templatesDirPath = do
    mt1 <- getRecursiveMTime templatesDirPath
    mt2 <- getRecursiveMTime textTemplatesDir
    return $ maximum $ [mt1,mt2]

-- | main template rendering function.
--   renders template by name (it's an error to render template that's not present in templates group),
--   and using list of named template params. simple 'noescape' template is added for convenience
renderTemplateMain :: ToSElem a =>
                     Templates     -- ^ group of templates
                   -> String        -- ^ template name
                   -> [(String, a)] -- ^ named template params
                   -> (StringTemplate String -> StringTemplate String) -- ^ additional template altering function
                   -> String -- ^ rendered template
renderTemplateMain ts name params f = case mt of
  Just t  -> render $ f (setManyAttrib params t)
  Nothing -> error  $ "No template named " ++ name
  where
    ts' = setEncoderGroup stringToHtmlString ts
    noescape = groupStringTemplates [("noescape", newSTMP "$it$" :: StringTemplate String)]
    mt = getStringTemplate name $ mergeSTGroups noescape ts'