{-# LANGUAGE LambdaCase        #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications  #-}

{-|
Module      : Headroom.Variables
Description : Support for template variables
Copyright   : (c) 2019-2020 Vaclav Svejcar
License     : BSD-3-Clause
Maintainer  : vaclav.svejcar@gmail.com
Stability   : experimental
Portability : POSIX

Module containing costructor and useful functions for the 'Variables' data type.
-}

module Headroom.Variables
  ( -- * Constructing Variables
    mkVariables
  , dynamicVariables
    -- * Parsing Variables
  , parseVariables
    -- * Processing Variables
  , compileVariables
  )
where

import           Headroom.Meta                  ( TemplateType )
import           Headroom.Template              ( Template(..) )
import           Headroom.Types                 ( CurrentYear(..)
                                                , fromHeadroomError
                                                , toHeadroomError
                                                )
import           Headroom.Variables.Types       ( Variables(..) )
import           RIO
import qualified RIO.HashMap                   as HM
import qualified RIO.Text                      as T


-- | Constructor function for 'Variables' data type.
--
-- >>> mkVariables [("key1", "value1")]
-- Variables (fromList [("key1","value1")])
mkVariables :: [(Text, Text)]
            -- ^ pairs of /key-value/
            -> Variables
            -- ^ constructed variables
mkVariables = Variables . HM.fromList


-- | /Dynamic variables/ that are common for all parsed files.
--
-- * @___current_year__@ - current year
dynamicVariables :: CurrentYear
                 -- ^ current year
                 -> Variables
                 -- ^ map of /dynamic variables/
dynamicVariables (CurrentYear year) =
  mkVariables [("_current_year", tshow year)]


-- | Parses variables from raw input in @key=value@ format.
--
-- >>> parseVariables ["key1=value1"]
-- Variables (fromList [("key1","value1")])
parseVariables :: MonadThrow m
               => [Text]
               -- ^ list of raw variables
               -> m Variables
               -- ^ parsed variables
parseVariables variables = fmap mkVariables (mapM parse variables)
 where
  parse input = case T.split (== '=') input of
    [key, value] -> pure (key, value)
    _            -> throwM $ InvalidVariable input


-- | Compiles variable values that are itself mini-templates, where their
-- variables will be substituted by other variable values (if possible).
-- Note that recursive variable reference and/or cyclic references are not
-- supported.
--
-- >>> compileVariables $ mkVariables [("name", "John"), ("msg", "Hello, {{ name }}")]
-- Variables (fromList [("msg","Hello, John"),("name","John")])
compileVariables :: (MonadThrow m)
                 => Variables
                 -- ^ input variables to compile
                 -> m Variables
                 -- ^ compiled variables
compileVariables variables@(Variables kvs) = do
  compiled <- mapM compileVariable (HM.toList kvs)
  pure $ mkVariables compiled
 where
  compileVariable (key, value) = do
    parsed   <- parseTemplate @TemplateType (Just $ "variable " <> key) value
    rendered <- renderTemplate variables parsed
    pure (key, rendered)


---------------------------------  Error Types  --------------------------------

-- | Exception specific to the "Headroom.Variables" module.
data VariablesError = InvalidVariable !Text
                    -- ^ invalid variable input (as @key=value@)
  deriving (Eq, Show)


instance Exception VariablesError where
  displayException = displayException'
  toException      = toHeadroomError
  fromException    = fromHeadroomError


displayException' :: VariablesError -> String
displayException' = T.unpack . \case
  InvalidVariable raw -> ("Cannot parse variable key=value from: " <> raw)