{-# LANGUAGE QuasiQuotes, TemplateHaskell, CPP, TypeFamilies, FlexibleInstances, MultiParamTypeClasses, FlexibleContexts #-}
-- | Provides a pagination subsite for Yesod.
--
-- Route layout:
--
-- > /           PaginateHomeR   GET
-- > /#Int       PaginateStartR  GET
-- > /#Int/#Int  PaginateR       GET
--
-- If no numbers are given, display the items starting from the beginning with the default count. If one number is given,
-- display the items starting from that index with the default count. If two numbers are given, the first one is the count
-- and the second is the starting index. (This is so the user can modify the end of the URL to move forward in the pagination.)
module Yesod.Paginate
  ( Paginate (..)
  , PaginateRoute (..)
  , defaultPaginate
  , prevLink
  , nextLink
  )
  where

import Yesod
import Control.Applicative
import Language.Haskell.TH.Syntax

data Paginate master rep a = Paginate
  { pgnDefaultCount :: Int                                                              -- ^ How many items to show per page by default
  , pgnGetItems     :: Int -> Int -> GHandler (Paginate master rep a) master [a]        -- ^ Get a certain count of items at a certain offset
  , pgnItemCount    :: GHandler (Paginate master rep a) master Int                      -- ^ How many items there are in all
  , pgnDisplayItems :: Int -> Int -> [a] -> GHandler (Paginate master rep a) master rep -- ^ Render the items on a page given the count and offset
  }

mkYesodSub
  "Paginate master rep a"
  [ ClassP ''Yesod [VarT $ mkName "master"]
  , ClassP ''HasReps [VarT $ mkName "rep"]
  ]
#if GHC7
  [parseRoutes|
#else
  [$parseRoutes|
#endif
/           PaginateHomeR   GET
/#Int       PaginateStartR  GET
/#Int/#Int  PaginateR       GET
|]

getPaginateHomeR :: GHandler (Paginate master rep a) master rep
getPaginateHomeR = do
  pgn <- getYesodSub
  toMaster <- getRouteToMaster
  redirect RedirectSeeOther (toMaster (PaginateR (pgnDefaultCount pgn) 0))

getPaginateStartR :: Int -> GHandler (Paginate master rep a) master rep
getPaginateStartR start = do
  pgn <- getYesodSub
  toMaster <- getRouteToMaster
  redirect RedirectSeeOther (toMaster (PaginateR (pgnDefaultCount pgn) start))

getPaginateR :: Int -> Int -> GHandler (Paginate master rep a) master rep
getPaginateR howmany start = do
  pgn <- getYesodSub
  xs <- pgnGetItems pgn howmany start
  pgnDisplayItems pgn howmany start xs

defaultPaginate
  :: (YesodPersist master, PersistBackend (YesodDB master (GHandler (Paginate master rep a) master)), PersistEntity a)
  => Int                                                                -- ^ Default number of items to show
  -> [Filter a]                                                         -- ^ Filters to apply
  -> [Order a]                                                          -- ^ Ordering to apply
  -> (Int -> Int -> [a] -> GHandler (Paginate master rep a) master rep)     -- ^ Display function
  -> Paginate master rep a
defaultPaginate x fs os d = Paginate
  { pgnDefaultCount = x
  , pgnGetItems = \y z -> map snd <$> runDB (selectList fs os y z)
  , pgnItemCount = runDB (count fs)
  , pgnDisplayItems = d
  }

-- | Link to the previous page.
prevLink :: Paginate master rep a -> Int -> Int -> Maybe (Route (Paginate master rep a))
prevLink p howmany start
  | start > 0 = Just (PaginateR howmany (max 0 (start-howmany)))
  | otherwise = Nothing

-- | Link to the next page.
nextLink :: Paginate master rep a -> Int -> Int -> GHandler (Paginate master rep a) master (Maybe (Route (Paginate master rep a)))
nextLink p howmany start = go <$> pgnItemCount p where
  go l | start < l-howmany-1 = Just (PaginateR howmany (start+howmany))
       | otherwise           = Nothing