{-# LANGUAGE TypeOperators #-}
module Text.Formlets ( module Text.Formlets.Form
                     , input , password , inputF , passwordF,ensure
                     , FailingForm, validate, runFormState, inputIntegerF
                     , check, liftForm
                     )
                     where

import Text.Formlets.Form
import Control.Applicative
import Control.Applicative.Compose
import Control.Applicative.Error
import Control.Applicative.State
import Text.XHtml.Strict ((!))
import qualified Text.XHtml.Strict as X

-- | Apply a predicate to a value and return Success or Failure as appropriate
ensure :: Show a 
       => (a -> Bool) -- ^ The predicate
       -> String      -- ^ The error message, in case the predicate fails
       -> a           -- ^ The value
       -> Failing a
ensure p msg x | p x = Success x
               | otherwise = Failure [msg]

-- | Helper function for genereting input components based.
input' :: (String -> String -> X.Html) -> Maybe String -> Form String
input' i defaultValue = Form $ \env -> mkInput env <$> freshName
   where mkInput :: Env -> String -> (Collector String, Xml)
         mkInput env name = (`queryParam` name,
                             i name (value name env))
         value name env = maybe (maybe "" id defaultValue) id (lookup name env)

-- | A form whose output may fail
type FailingForm a = (Form :+: Failing) a

runFormState :: Env             -- ^ A previously filled environment (may be empty)
             -> FailingForm a   -- ^ The form
             -> FormState       -- ^ Initial form state
             -> ((Collector (Failing a), Xml), FormState)
runFormState e f s = (runState (deform (decompose f) e) s)

-- | Lifts a function on a Form to a function on a composed form.
liftForm :: (Form (f a) -> Form (f a)) -> (Form :+: f) a -> (Form :+: f) a
liftForm f = Compose . f . decompose

-- | Lift a form component to a failing form component
validate :: Form a -> FailingForm a
validate f = Compose $ pure Success <*> f

-- | Add additional validation to an already validated component
check :: FailingForm a -> (a -> Failing b) -> FailingForm b
check form f = decompose form `chk` checker f
   where chk :: Form a -> (a -> Failing b) -> FailingForm b
         chk form validator = Compose $ pure validator <*> form
         checker :: (a -> Failing b) -> (Failing a -> Failing b)
         checker f (Failure x) = Failure x
         checker f (Success x) = f x

-- | Component: an input field with an optional value
input :: Maybe String -> Form String
input = input' (\n v -> X.textfield n ! [X.value v])

-- | Component: a password field with an optional value
password :: Maybe String -> Form String
password = input' (\n v -> X.password n ! [X.value v])

-- | A trivially validated input component
inputF :: Maybe String -> FailingForm String
inputF = validate . input

-- | A trivially validated password component
passwordF :: Maybe String -> FailingForm String
passwordF = validate . password

-- | A validated integer component
inputIntegerF :: Maybe Integer -> FailingForm Integer
inputIntegerF x = validate (input $ fmap show x) `check` asInteger