{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE OverloadedStrings         #-}
{-# LANGUAGE RecordWildCards           #-}

module Snap.Extras.FormUtils
    (
    -- * Transformers
      maybeTrans
    , readMayTrans
    , readTrans
    -- * Compiled splices
    , editFormSplice

    -- -- * Form Elements
    -- , submitCancel
    -- , submitOnly
    -- -- * Formatting Form Elements
    -- , inputFormat
    -- , dateInput
    -- -- * Running/Displaying Forms
    -- , showForm
    -- , runViewForm'
    ) where

-------------------------------------------------------------------------------
import           Control.Monad.Trans.Maybe
import qualified Data.ByteString.Char8     as B
import           Data.Maybe
import           Data.String
import           Data.Text                 (Text)
import qualified Data.Text                 as T
import           Data.Text.Encoding
import           Heist
import           Safe
import           Snap.Core
import           Text.Digestive            as DF
import qualified Text.XmlHtml              as X
-------------------------------------------------------------------------------


                              ------------------
                              -- Transformers --
                              ------------------


-------------------------------------------------------------------------------
-- | Transform to Nothing if field is empty string
maybeTrans
    :: (Eq a, IsString a)
    => a -> Result v (Maybe a)
maybeTrans x = f x
    where f "" = Success Nothing
          f a  = Success $ Just a


-------------------------------------------------------------------------------
-- | Maybe read into target value
readMayTrans
    :: Read a
    => Text -> Result v (Maybe a)
readMayTrans x = Success $ readMay (T.unpack x)


-------------------------------------------------------------------------------
-- | Read into target value
readTrans
    :: (Read a, IsString v)
    => Text -> Result v a
readTrans a = maybe (DF.Error "Unrecognized input") Success $ readMay . T.unpack $ a



                              -------------------
                              -- Form Elements --
                              -------------------


-- -------------------------------------------------------------------------------
-- -- | Submit and cancel buttons with markup
-- submitCancel
--     :: (Monad m, Functor m)
--     => String
--     -- ^ Submit button text
--     -> Text
--     -- ^ Cancel button text
--     -> Text
--     -- ^ Cancel button HREF
--     -> Form m i e (FormHtml Html) ()
-- submitCancel s ct cl = mapViewHtml enc (submit s)
--   where enc x = do
--           H.div ! A.class_ "form-actions" $ do
--             x
--             " "
--             H.a (text ct) ! A.href (textValue cl) ! A.class_ "btn"

-- -------------------------------------------------------------------------------
-- -- | Just a submit button with markup
-- submitOnly
--     :: (Monad m, Functor m)
--     => String
--     -- ^ Submit button text
--     -> Form m i e (FormHtml Html) ()
-- submitOnly s = mapViewHtml enc (submit s)
--   where enc x = do
--           H.div ! A.class_ "form-actions" $ x


-- -------------------------------------------------------------------------------
-- -- | Input form element wrapper to add a class of 'input'
-- inputFormat
--     :: (Monad m, Functor m)
--     => Form m i e (FormHtml Html) a
--     -> Form m i e (FormHtml Html) a
-- inputFormat = mapViewHtml (H.div ! A.class_ "input")


-- -------------------------------------------------------------------------------
-- -- | Add a 'datepicker' classed div tag surrounding the form element
-- dateInput
--     :: (Monad m, Functor m)
--     => Form m i e (FormHtml Html) a
--     -> Form m i e (FormHtml Html) a
-- dateInput = mapViewHtml (H.div ! A.class_ "datepicker")


-- -------------------------------------------------------------------------------
-- -- | Render a form into a Heist splice
-- showForm
--     :: Monad m
--     => AttributeValue
--     -- ^ Form action target
--     -> AttributeValue
--     -- ^ Form submittion HTTP method
--     -> FormHtml (HtmlM a)
--     -- ^ Form to display
--     -> Splice m
-- showForm act mth frm =
--    let  (formHtml', enctype) = renderFormHtmlWith config frm
--         config = defaultHtmlConfig { htmlSubmitClasses = ["btn", "btn-primary"] }
--         frm' = H.form ! A.enctype (H.toValue $ show enctype) ! A.method mth
--                       ! A.action act $ formHtml' >> return ()
--    in return $ renderHtmlNodes frm'



-- -------------------------------------------------------------------------------
-- -- | Version of runView that will use defaults when no related
-- -- parameters are found in the environment. This is particularly
-- -- designed for filter forms that have to be rendered all the time on
-- -- the page (as opposed to record create/update forms).
-- --
-- -- This is a pretty smart combinator and will try to do the "Right
-- -- Thing" automagically. It will:
-- --
-- -- 1. Look for the presence of a form namespace in Snap's 'Params'
-- --
-- -- 2. If it's there, it'll try to run the form and the views
-- --
-- -- 3. If no params are there, it'll assume that the form has not been
-- -- submitted, so it should be a default form. It just renders the view
-- -- in its default mode.
-- runViewForm'
--   :: MonadSnap m
--   => SnapForm m e t a
--   -- ^ A digestive-functor form
--   -> String
--   -- ^ Namespace used when rendering the form
--   -> m (t, Maybe a)
--   -- ^ The form's view and a possible form result
-- runViewForm' form nm = do
--   ps <- M.keys `fmap` getParams
--   case find (B.isInfixOf $ B.pack nm) ps of
--     Just _ -> runViewSnapForm form nm
--     Nothing -> do
--       view' <- viewForm form nm
--       return $ (view', Nothing)

-------------------------------------------------------------------------------
-- | Constructs a generalized edit form splice that looks up an ID param
-- specified by the @by@ attribute.  You might use this splice as follows:
--
-- > <editFormSplice by="id">
--
-- If you don't specify the @by@ attribute, the default is @by=\"id\"@.
editFormSplice :: (Monad m, MonadSnap n)
               => (n (Maybe a) -> HeistT n m b)
               -- ^ Function for generating a splice from an optional default
               -- value calculated at runtime.
               -> (B.ByteString -> n (Maybe a))
               -- ^ Function for retrieving the form default by ID.
               -> HeistT n m b
editFormSplice formSplice getById = do
    node <- getParamNode
    let param = fromMaybe "id" $ X.getAttribute "by" node
    formSplice $ do
      runMaybeT $ do
        key <- MaybeT $ getParam $ encodeUtf8 param
        MaybeT (getById key)