{-# LANGUAGE DataKinds             #-}
{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NoImplicitPrelude     #-}
{-# LANGUAGE OverloadedStrings     #-}
{-# LANGUAGE RecordWildCards       #-}
{-# LANGUAGE TemplateHaskell       #-}
{-# LANGUAGE TypeApplications      #-}
{-# LANGUAGE TypeFamilies          #-}

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

/License header functions/ are basically functions that allows to post-process
already rendered /license headers/. This is useful to perform some additional
operations such as some sort of text alignment, update some parts of the header,
etc.
-}

module Headroom.HeaderFn
  ( runHeaderFn
  , configuredHeaderFn
  , postProcessHeader
    -- * Environment Data Types
  , ConfiguredEnv(..)
  , mkConfiguredEnv
  )
where

import           Headroom.Configuration.Types   ( CtHeaderFnConfigs
                                                , HeaderFnConfig(..)
                                                , HeaderFnConfigs(..)
                                                , UpdateCopyrightConfig(..)
                                                )
import           Headroom.Data.Has              ( Has(..) )
import           Headroom.Data.Lens             ( suffixLenses
                                                , suffixLensesFor
                                                )
import           Headroom.HeaderFn.Types        ( HeaderFn(..) )
import           Headroom.HeaderFn.UpdateCopyright
                                                ( SelectedAuthors(..)
                                                , UpdateCopyrightMode(..)
                                                , updateCopyright
                                                )
import           Headroom.Meta                  ( TemplateType )
import           Headroom.Template              ( Template(..) )
import           Headroom.Types                 ( CurrentYear(..) )
import           Headroom.Variables.Types       ( Variables(..) )
import           Lens.Micro                     ( traverseOf )
import           RIO


suffixLenses ''HeaderFnConfigs
suffixLenses ''UpdateCopyrightConfig
suffixLensesFor ["hfcConfig"] ''HeaderFnConfig


-- | Runs the /license header function/ using the given /environment/ and text
-- of rendered /license header/ as input.
runHeaderFn :: HeaderFn env
            -- ^ /license header function/ to run
            -> env
            -- ^ environment value
            -> Text
            -- ^ text of rendered /license header/
            -> Text
            -- ^ processed text of /license header/
runHeaderFn (HeaderFn fn) env input = runReader (fn input) env


-- | Composition of various /license header functions/, which environment is
-- based on /YAML/ configuration and which can be enabled/disabled to fit
-- end user's needs.
configuredHeaderFn :: (Has CurrentYear env, Has UpdateCopyrightMode env)
                   => CtHeaderFnConfigs
                   -- ^ configuration of /license header functions/
                   -> HeaderFn env
                   -- ^ composed /license header function/
configuredHeaderFn HeaderFnConfigs {..} = mconcat
  [ifEnabled hfcsUpdateCopyright updateCopyright]
 where
  ifEnabled HeaderFnConfig {..} fn | hfcEnabled = fn
                                   | otherwise  = mempty


-- | Takes already rendered /license header/ and post-process it based on the
-- given configuration.
postProcessHeader :: ConfiguredEnv
                  -- ^ configuration used to define post-processing behaviour
                  -> Text
                  -- ^ rendered text of /license header/
                  -> Text
                  -- ^ post-processed text of /license header/
postProcessHeader env = runHeaderFn (configuredHeaderFn configs) env
  where configs = ceHeaderFnConfigs env


-- | Environemnt data type for the composed /license header function/
-- ('configuredHeaderFn').
data ConfiguredEnv = ConfiguredEnv
  { ceCurrentYear         :: !CurrentYear
  -- ^ current year
  , ceHeaderFnConfigs     :: !CtHeaderFnConfigs
  -- ^ configuration of /license header functions/
  , ceUpdateCopyrightMode :: !UpdateCopyrightMode
  -- ^ mode used by the 'updateCopyright' /license header function/
  }
  deriving (Eq, Show)

suffixLensesFor ["ceCurrentYear", "ceUpdateCopyrightMode"] ''ConfiguredEnv

instance Has CurrentYear ConfiguredEnv where
  hasLens = ceCurrentYearL

instance Has UpdateCopyrightMode ConfiguredEnv where
  hasLens = ceUpdateCopyrightModeL


-- | Constructor function for 'ConfiguredEnv' data type. This function takes
-- 'Variables' as argument, because it performs template compilation on
-- selected fields of 'CtHeaderFnConfigs'.
mkConfiguredEnv :: (MonadThrow m)
                => CurrentYear
                -- ^ current year
                -> Variables
                -- ^ template variables
                -> CtHeaderFnConfigs
                -- ^ configuration of /license header functions/
                -> m ConfiguredEnv
                -- ^ environment data type
mkConfiguredEnv ceCurrentYear vars configs = do
  ceHeaderFnConfigs <- compileTemplates vars configs
  let ceUpdateCopyrightMode = mode ceHeaderFnConfigs
  pure ConfiguredEnv { .. }
 where
  authorsL = hfcsUpdateCopyrightL . hfcConfigL . uccSelectedAuthorsL
  mode configs' = maybe UpdateAllAuthors
                        (UpdateSelectedAuthors . SelectedAuthors)
                        (configs' ^. authorsL)


------------------------------  PRIVATE FUNCTIONS  -----------------------------

compileTemplates :: (MonadThrow m)
                 => Variables
                 -> CtHeaderFnConfigs
                 -> m CtHeaderFnConfigs
compileTemplates vars configs = configs & traverseOf authorsL compileAuthors'
 where
  authorsL        = hfcsUpdateCopyrightL . hfcConfigL . uccSelectedAuthorsL
  compileAuthors' = mapM . mapM $ compileAuthor
  compileAuthor author = do
    parsed <- parseTemplate @TemplateType (Just $ "author " <> author) author
    renderTemplate vars parsed