-- |
-- Module:     Yesod.TableView
-- Copyright:  (c) 2010 Ertugrul Soeylemez
-- License:    BSD3
-- Maintainer: Ertugrul Soeylemez <es@ertes.de>
-- Stability:  experimental
--
-- Table-like view for tabular data.

{-# LANGUAGE
  FlexibleContexts,
  ScopedTypeVariables,
  TemplateHaskell #-}

module Yesod.TableView
    ( -- Table view
      TableView(..),
      defTableView,
      tableView,

      -- * Reexports
      module Yesod.TableView.Widget
    )
    where

import Control.Monad
import Text.Cassius
import Text.Hamlet
import Yesod
import Yesod.TableView.NumEntriesForm
import Yesod.TableView.Widget


-- | Table view settings.  Defaults are given in parentheses.

data TableView s val =
    TableView {
      -- Filter to use (@[]@).
      tableFilter   :: [Filter val],           -- ^ Table filter.
      tableId       :: String,                 -- ^ HTML table id.
      tableOrder    :: [Order val],            -- ^ Table sorting order.
      tableRoute    :: Int -> Int -> Route s,  -- ^ Table route.
      tableShowHead :: Bool,                   -- ^ Show table header?
      tableStyled   :: Bool,                   -- ^ Add CSS styles?

      -- Limits.
      tableCurrentLimit  :: Int,        -- ^ Current pager limit.
      tableCurrentOffset :: Int,        -- ^ Current offset.
      tableLimitPrompt   :: String,     -- ^ Prompt in limit form.
      tableLimits        :: [Int],      -- ^ Selectable limits (@[10, 20, 50, 100]@).
      tableMinLimit      :: Maybe Int,  -- ^ Minimum pager limit (@Just 10@).
      tableMaxLimit      :: Maybe Int   -- ^ Maximum pager limit (@Just 100@).
    }


-- | Default values for most fields.  The following fields will be left
-- undefined:  'tableId', 'tableRoute', 'tableCurrentLimit' and
-- 'tableCurrentOffset'.

defTableView :: TableView s val
defTableView =
    TableView { tableFilter   = [],
                tableId       = undefined,
                tableOrder    = [],
                tableRoute    = undefined,
                tableShowHead = True,
                tableStyled   = True,

                tableCurrentLimit  = undefined,
                tableCurrentOffset = undefined,
                tableLimitPrompt   = "Number of entries per page:",
                tableLimits        = [10, 20, 50, 100],
                tableMinLimit      = Just 10,
                tableMaxLimit      = Just 100 }


clamp :: Ord a => Maybe a -> Maybe a -> a -> a
clamp mMin mMax = maybe id max mMin . maybe id min mMax


tableView :: forall s sub val.
             ( PersistBackend (YesodDB s (GGHandler sub s IO)),
               PersistEntity val,
               TableViewWidget val,
               YesodPersist s) =>
             TableView s val -> GHandler sub s (GWidget sub s ())
tableView cfg'@(TableView {
                  tableFilter = filters,
                  tableOrder = orders,
                  tableLimitPrompt = limitPrompt,
                  tableLimits = limitOpts,
                  tableRoute = thisRoute,
                  tableShowHead = showHead,
                  tableStyled = styled,

                  tableCurrentLimit = limit',
                  tableCurrentOffset = offset',
                  tableMinLimit = minLimit,
                  tableMaxLimit = maxLimit
                }) = do

    let minOffset = Just 0
        maxOffset = Nothing

    let limit = clamp minLimit maxLimit limit'
        offset = clamp minOffset maxOffset offset'
        prevOffset = clamp minOffset maxOffset (offset - limit)
        nextOffset = clamp minOffset maxOffset (offset + limit)

    (entries, numEntries) <-
        runDB $ liftM2 (,) (selectList filters orders limit offset) (count filters)

    numEntriesWidget <- runNumEntriesForm
                        styled limitPrompt limitOpts limit (`thisRoute` offset)

    let lastOffset = (numEntries - 1) `div` limit * limit

    return $ do
        let entryRows = mapM_ row (zip [0..] entries)
            firstRoute = guard (offset > 0) >> Just (thisRoute limit 0)
            lastRoute = guard (offset < lastOffset) >>
                        Just (thisRoute limit lastOffset)
            forwardRoute = guard (nextOffset < numEntries) >>
                           Just (thisRoute limit nextOffset)
            backRoute = guard (offset > 0) >>
                        Just (thisRoute limit prevOffset)
            mHeader = guard showHead >> Just (tableHeader (undefined :: val))
        when styled $ addCassius $(cassiusFile "templates/tableview.cassius")
        addWidget $(hamletFile "templates/tableview.hamlet")

    where
    row :: (Int, (Key val, val)) -> GWidget sub s ()
    row (ix, (entryId, entry)) =
        let rowClass    = if even ix then "even-row" else "odd-row"
            entryWidget = tableRecord ix entryId entry
        in addWidget $(hamletFile "templates/tableview-row.hamlet")