-- | A Module that allows easy rendering of RSS feeds. If you use this module,
--   you must make sure you set the `absoluteUrl` field in the main Hakyll
--   configuration.
--
--   Apart from that, the main rendering functions (@renderRss@, @renderAtom@) 
--   all assume that you pass the list of items so that the most recent entry
--   in the feed is the first item in the list.
--
--   Also note that the @Context@s should have (at least) the following
--   fields to produce a correct feed:
--
--   - @$title@: Title of the item.
--
--   - @$description@: Description to appear in the feed.
--
--   - @$url@: URL to the item - this is usually set automatically.
--
--   Furthermore, the feed will be generated, but will be incorrect (it won't
--   validate) if an empty list is passed.
--
module Text.Hakyll.Feed
    ( FeedConfiguration (..)
    , renderRss
    , renderAtom
    ) where

import Control.Arrow ((>>>), second)
import Control.Monad.Reader (liftIO)
import Data.Maybe (fromMaybe)
import qualified Data.Map as M

import Text.Hakyll.Context (Context (..))
import Text.Hakyll.CreateContext (createListing)
import Text.Hakyll.ContextManipulations (renderDate)
import Text.Hakyll.HakyllMonad (Hakyll)
import Text.Hakyll.Render (render, renderChain)
import Text.Hakyll.HakyllAction

import Paths_hakyll

-- | This is a data structure to keep the configuration of a feed.
data FeedConfiguration = FeedConfiguration
    { -- | Url of the feed (relative to site root). For example, @rss.xml@.
      feedUrl         :: String
    , -- | Title of the feed.
      feedTitle       :: String
    , -- | Description of the feed.
      feedDescription :: String
    , -- | Name of the feed author.
      feedAuthorName  :: String
    }

-- | This is an auxiliary function to create a listing that is, in fact, a feed.
--   The items should be sorted on date.
createFeed :: FeedConfiguration         -- ^ Feed configuration.
           -> [HakyllAction () Context] -- ^ Items to include.
           -> FilePath                  -- ^ Feed template.
           -> FilePath                  -- ^ Item template.
           -> HakyllAction () Context
createFeed configuration renderables template itemTemplate =
    listing >>> render template
  where
    listing = createListing (feedUrl configuration)
                            [itemTemplate] renderables additional

    additional = map (second $ Left . ($ configuration))
        [ ("title", feedTitle)
        , ("description", feedDescription)
        , ("authorName", feedAuthorName)
        ] ++ updated

    -- Take the first timestamp, which should be the most recent.
    updated = let action = createHakyllAction $ return . fromMaybe "foo" 
                                              . M.lookup "timestamp" . unContext
                  toTuple r = ("timestamp", Right $ r >>> action)
              in map toTuple $ take 1 renderables
            

-- | Abstract function to render any feed.
renderFeed :: FeedConfiguration         -- ^ Feed configuration.
           -> [HakyllAction () Context] -- ^ Items to include in the feed.
           -> FilePath                  -- ^ Feed template.
           -> FilePath                  -- ^ Item template.
           -> Hakyll ()
renderFeed configuration renderables template itemTemplate = do
    template' <- liftIO $ getDataFileName template
    itemTemplate' <- liftIO $ getDataFileName itemTemplate
    let renderFeed' = createFeed configuration renderables
                                 template' itemTemplate'
    renderChain [] renderFeed'

-- | Render an RSS feed with a number of items.
renderRss :: FeedConfiguration         -- ^ Feed configuration.
          -> [HakyllAction () Context] -- ^ Items to include in the feed.
          -> Hakyll ()
renderRss configuration renderables =
    renderFeed configuration (map (>>> renderRssDate) renderables)
               "templates/rss.xml" "templates/rss-item.xml"
  where
    renderRssDate = renderDate "timestamp" "%a, %d %b %Y %H:%M:%S UT"
                               "No date found."

-- | Render an Atom feed with a number of items.
renderAtom :: FeedConfiguration         -- ^ Feed configuration.
           -> [HakyllAction () Context] -- ^ Items to include in the feed.
           -> Hakyll ()
renderAtom configuration renderables =
    renderFeed configuration (map (>>> renderAtomDate) renderables)
               "templates/atom.xml" "templates/atom-item.xml"
  where
    renderAtomDate = renderDate "timestamp" "%Y-%m-%dT%H:%M:%SZ"
                                            "No date found."