-- | This module exports a number of functions that produce @HakyllAction@s to
--   manipulate @Context@s.
module Text.Hakyll.ContextManipulations
    ( renderValue
    , changeValue
    , changeUrl
    , copyValue
    , renderDate
    , renderDateWithLocale
    , changeExtension
    , renderBody
    , takeBody
    ) where

import Control.Monad (liftM)
import Control.Arrow (arr)
import System.Locale (TimeLocale, defaultTimeLocale)
import System.FilePath (takeFileName, addExtension, dropExtension)
import Data.Time.Format (parseTime, formatTime)
import Data.Time.Clock (UTCTime)
import Data.Maybe (fromMaybe)
import qualified Data.Map as M

import Text.Hakyll.Regex (substituteRegex)
import Text.Hakyll.HakyllAction (HakyllAction (..))
import Text.Hakyll.Context (Context (..))

-- | Do something with a value in a @Context@, but keep the old value as well.
--   If the key given is not present in the @Context@, nothing will happen.
renderValue :: String             -- ^ Key of which the value should be copied.
            -> String             -- ^ Key the value should be copied to.
            -> (String -> String) -- ^ Function to apply on the value.
            -> HakyllAction Context Context
renderValue source destination f = arr $ \(Context context) -> Context $
    case M.lookup source context of
        Nothing      -> context
        (Just value) -> M.insert destination (f value) context

-- | Change a value in a @Context@.
--
--   > import Data.Char (toUpper)
--   > changeValue "title" (map toUpper)
--
--   Will put the title in UPPERCASE.
changeValue :: String             -- ^ Key to change.
            -> (String -> String) -- ^ Function to apply on the value.
            -> HakyllAction Context Context
changeValue key = renderValue key key

-- | Change the URL of a page. This requires a special function, so dependency
-- handling can happen correctly.
-- 
changeUrl :: (String -> String)            -- ^ Function to change URL with.
          -> HakyllAction Context Context  -- ^ Resulting action.
changeUrl f = let action = changeValue "url" f
              in action {actionUrl = Right $ liftM f}

-- | Copy a value from one key to another in a @Context@.
copyValue :: String -- ^ Source key.
          -> String -- ^ Destination key.
          -> HakyllAction Context Context
copyValue source destination = renderValue source destination id

-- | When the context has a key called @path@ in a
--   @folder/yyyy-mm-dd-title.extension@ format (the convention for pages),
--   this function can render the date.
--
--   > renderDate "date" "%B %e, %Y" "Date unknown"
--
--   Will render something like @January 32, 2010@.
--
renderDate :: String -- ^ Key in which the rendered date should be placed.
           -> String -- ^ Format to use on the date.
           -> String -- ^ Default key, in case the date cannot be parsed.
           -> HakyllAction Context Context
renderDate = renderDateWithLocale defaultTimeLocale

-- | This is an extended version of 'renderDate' that allows you to specify a
-- time locale that is used for outputting the date. For more details, see
-- 'renderDate'.
--
renderDateWithLocale :: TimeLocale  -- ^ Output time locale.
                     -> String      -- ^ Destination key.
                     -> String      -- ^ Format to use on the date.
                     -> String      -- ^ Default key.
                     -> HakyllAction Context Context
renderDateWithLocale locale key format defaultValue =
    renderValue "path" key renderDate'
  where
    renderDate' filePath = fromMaybe defaultValue $ do
        let dateString = substituteRegex "^([0-9]*-[0-9]*-[0-9]*).*" "\\1"
                                         (takeFileName filePath)
        time <- parseTime defaultTimeLocale
                          "%Y-%m-%d"
                          dateString :: Maybe UTCTime
        return $ formatTime locale format time

-- | Change the extension of a file. This is only needed when you want to
--   render, for example, mardown to @.php@ files instead of @.html@ files.
--
--   > changeExtension "php"
--
--   Will render @test.markdown@ to @test.php@ instead of @test.html@.
changeExtension :: String -- ^ Extension to change to.
                -> HakyllAction Context Context
changeExtension extension = changeValue "url" changeExtension'
  where
    changeExtension' = flip addExtension extension . dropExtension

-- | Change the body of a file using a certain manipulation.
--
--   > import Data.Char (toUpper)
--   > renderBody (map toUpper)
--
--   Will put the entire body of the page in UPPERCASE.
renderBody :: (String -> String)
           -> HakyllAction Context Context
renderBody = renderValue "body" "body"

-- | Get the resulting body text from a context
--
takeBody :: HakyllAction Context String
takeBody = arr $ fromMaybe "" . M.lookup "body" . unContext