    --------------------------------------------------------------------------------
    {-# LANGUAGE OverloadedStrings #-}
    module Hakyll.Web.Paginate
        ( PageNumber
        , Paginate (..)
        , buildPaginate
        , buildPaginateWith
        , paginateRules
        , paginateContext
        ) where

    --------------------------------------------------------------------------------
    import           Control.Monad                  (forM_)
    import           Data.List                      (unfoldr)
    import qualified Data.Map                       as M
    import           Data.Monoid                    (mconcat)
    import           Text.Printf                    (printf)

    --------------------------------------------------------------------------------
    import           Hakyll.Core.Compiler
    import           Hakyll.Core.Identifier
    import           Hakyll.Core.Identifier.Pattern
    import           Hakyll.Core.Item
    import           Hakyll.Core.Metadata
    import           Hakyll.Core.Rules
    import           Hakyll.Web.Html
    import           Hakyll.Web.Template.Context

    --------------------------------------------------------------------------------
    type PageNumber = Int

    --------------------------------------------------------------------------------
    -- | Data about paginators
    data Paginate = Paginate
        { paginatePages      :: M.Map PageNumber [Identifier]
        , paginatePlaces     :: M.Map Identifier PageNumber
        , paginateMakeId     :: PageNumber -> Identifier
        , paginateDependency :: Dependency
        } deriving (Show)

    --------------------------------------------------------------------------------
    buildPaginate :: MonadMetadata m
                  => Pattern
                  -> m Paginate
    buildPaginate pattern = do
        idents <- getMatches pattern
        let pagPages  = M.fromList $ zip [1 ..] (map return idents)
            pagPlaces = M.fromList $ zip idents [1 ..]
            makeId pn = case M.lookup pn pagPages of
                Just [id'] -> id'
                _          -> error $
                    "Hakyll.Web.Paginate.buildPaginate: " ++
                    "invalid page number: " ++ show pn

        return $ Paginate pagPages pagPlaces makeId
            (PatternDependency pattern idents)

    --------------------------------------------------------------------------------
    buildPaginateWith :: MonadMetadata m
                      => Int
                      -> (PageNumber -> Identifier)
                      -> Pattern
                      -> m Paginate
    buildPaginateWith n makeId pattern = do
        -- TODO: there is no sensible order for `ids` here, for now it's random;
        -- but it should be `resectFirst` order because most recent posts should
        -- correspond to 1st paginator page and oldest one to last page
        idents <- getMatches pattern
        let pages          = flip unfoldr idents $ \xs ->
                if null xs then Nothing else Just (splitAt n xs)
            nPages         = length pages
            paginatePages' = zip [1..] pages
            pagPlaces'     =
                [(ident, idx) | (idx,ids) <- paginatePages', ident <- ids] ++
                [(makeId i, i) | i <- [1 .. nPages]]

        return $ Paginate (M.fromList paginatePages') (M.fromList pagPlaces') makeId
            (PatternDependency pattern idents)

    --------------------------------------------------------------------------------
    paginateRules :: Paginate -> (PageNumber -> Pattern -> Rules ()) -> Rules ()
    paginateRules paginator rules =
        forM_ (M.toList $ paginatePages paginator) $ \(idx, identifiers) ->
            create [paginateMakeId paginator idx] $
                rulesExtraDependencies [paginateDependency paginator] $
                    rules idx $ fromList identifiers

    --------------------------------------------------------------------------------
    -- | Takes first, current, last page and produces index of next page
    type RelPage = PageNumber -> PageNumber -> PageNumber -> Maybe PageNumber

    --------------------------------------------------------------------------------
    paginateField :: Paginate -> String -> RelPage -> Context a
    paginateField pag fieldName relPage = field fieldName $ \item ->
        let identifier = itemIdentifier item
        in case M.lookup identifier (paginatePlaces pag) of
            Nothing -> fail $ printf
                "Hakyll.Web.Paginate: there is no page %s in paginator map."
                (show identifier)
            Just pos -> case relPage 1 pos nPages of
                Nothing   -> fail "Hakyll.Web.Paginate: No page here."
                Just pos' -> do
                    let nextId = paginateMakeId pag pos'
                    mroute <- getRoute nextId
                    case mroute of
                        Nothing -> fail $ printf
                            "Hakyll.Web.Paginate: unable to get route for %s."
                            (show nextId)
                        Just rt -> return $ toUrl rt
      where
        nPages = M.size (paginatePages pag)

    --------------------------------------------------------------------------------
    paginateContext :: Paginate -> Context a
    paginateContext pag = mconcat
        [ paginateField pag "firstPage"
            (\f c _ -> if c <= f then Nothing else Just f)
        , paginateField pag "previousPage"
            (\f c _ -> if c <= f then Nothing else Just (c - 1))
        , paginateField pag "nextPage"
            (\_ c l -> if c >= l then Nothing else Just (c + 1))
        , paginateField pag "lastPage"
            (\_ c l -> if c >= l then Nothing else Just l)
        ]