{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE PackageImports #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-- | Parse forms (and query strings).
module Yesod.Form
    ( -- * Data types
      GForm (..)
    , Form
    , Formlet
    , FormField
    , FormletField
    , FormInput
    , FormResult (..)
    , Enctype (..)
    , FieldInfo (..)
      -- * Newtype wrappers
    , JqueryDay (..)
    , NicHtml (..)
    , Html'
      -- * Unwrapping functions
    , runFormGet
    , runFormPost
    , runFormGet'
    , runFormPost'
      -- * Type classes
    , ToForm (..)
    , ToFormField (..)
      -- * Field/form helpers
    , requiredFieldHelper
    , optionalFieldHelper
    , mapFormXml
    , newFormIdent
    , fieldsToTable
    , fieldsToPlain
    , fieldsToInput
      -- * Field profiles
    , FieldProfile (..)
    , stringFieldProfile
    , intFieldProfile
    , dayFieldProfile
    , jqueryDayFieldProfile
    , timeFieldProfile
    , htmlFieldProfile
    , emailFieldProfile
      -- * Pre-built fields
    , stringField
    , maybeStringField
    , intField
    , maybeIntField
    , doubleField
    , maybeDoubleField
    , dayField
    , maybeDayField
    , jqueryDayField
    , maybeJqueryDayField
    , timeField
    , maybeTimeField
    , htmlField
    , maybeHtmlField
    , nicHtmlField
    , maybeNicHtmlField
    , selectField
    , maybeSelectField
    , boolField
    , jqueryAutocompleteField
    , maybeJqueryAutocompleteField
    , emailField
    , maybeEmailField
      -- * Pre-built inputs
    , stringInput
    , maybeStringInput
    , boolInput
    , dayInput
    , maybeDayInput
    , emailInput
      -- * Template Haskell
    , share2
    , mkToForm
    ) where

import Text.Hamlet
import Yesod.Request
import Yesod.Handler
import Control.Applicative hiding (optional)
import Data.Time (Day, TimeOfDay (TimeOfDay))
import Data.Maybe (fromMaybe, isJust)
import "transformers" Control.Monad.IO.Class
import Control.Monad ((<=<), liftM, join)
import Data.Monoid (Monoid (..))
import Control.Monad.Trans.State
import Language.Haskell.TH.Syntax
import Database.Persist.Base (EntityDef (..), PersistField)
import Data.Char (toUpper, isUpper)
import Data.Int (Int64)
import qualified Data.ByteString.Lazy.UTF8 as U
import Yesod.Widget
import Control.Arrow ((&&&))
import qualified Text.Email.Validate as Email

-- | A form can produce three different results: there was no data available,
-- the data was invalid, or there was a successful parse.
--
-- The 'Applicative' instance will concatenate the failure messages in two
-- 'FormResult's.
data FormResult a = FormMissing
                  | FormFailure [String]
                  | FormSuccess a
    deriving Show
instance Functor FormResult where
    fmap _ FormMissing = FormMissing
    fmap _ (FormFailure errs) = FormFailure errs
    fmap f (FormSuccess a) = FormSuccess $ f a
instance Applicative FormResult where
    pure = FormSuccess
    (FormSuccess f) <*> (FormSuccess g) = FormSuccess $ f g
    (FormFailure x) <*> (FormFailure y) = FormFailure $ x ++ y
    (FormFailure x) <*> _ = FormFailure x
    _ <*> (FormFailure y) = FormFailure y
    _ <*> _ = FormMissing

-- | The encoding type required by a form. The 'Show' instance produces values
-- that can be inserted directly into HTML.
data Enctype = UrlEncoded | Multipart
instance Show Enctype where
    show UrlEncoded = "application/x-www-form-urlencoded"
    show Multipart = "multipart/form-data"
instance Monoid Enctype where
    mempty = UrlEncoded
    mappend UrlEncoded UrlEncoded = UrlEncoded
    mappend _ _ = Multipart

-- | A generic form, allowing you to specifying the subsite datatype, master
-- site datatype, a datatype for the form XML and the return type.
newtype GForm sub y xml a = GForm
    { deform :: Env -> FileEnv -> StateT Int (GHandler sub y) (FormResult a, xml, Enctype)
    }
type Form sub y = GForm sub y (GWidget sub y ())
type Formlet sub y a = Maybe a -> Form sub y a
type FormField sub y = GForm sub y [FieldInfo sub y]
type FormletField sub y a = Maybe a -> FormField sub y a
type FormInput sub y = GForm sub y [GWidget sub y ()]

-- | Convert the XML in a 'GForm'.
mapFormXml :: (xml1 -> xml2) -> GForm s y xml1 a -> GForm s y xml2 a
mapFormXml f (GForm g) = GForm $ \e fe -> do
    (res, xml, enc) <- g e fe
    return (res, f xml, enc)

-- | Using this as the intermediate XML representation for fields allows us to
-- write generic field functions and then different functions for producing
-- actual HTML. See, for example, 'fieldsToTable' and 'fieldsToPlain'.
data FieldInfo sub y = FieldInfo
    { fiLabel :: Html ()
    , fiTooltip :: Html ()
    , fiIdent :: String
    , fiInput :: GWidget sub y ()
    , fiErrors :: Maybe (Html ())
    }

type Env = [(String, String)]
type FileEnv = [(String, FileInfo)]

instance Monoid xml => Functor (GForm sub url xml) where
    fmap f (GForm g) =
        GForm $ \env fe -> liftM (first3 $ fmap f) (g env fe)
      where
        first3 f' (x, y, z) = (f' x, y, z)

instance Monoid xml => Applicative (GForm sub url xml) where
    pure a = GForm $ const $ const $ return (pure a, mempty, mempty)
    (GForm f) <*> (GForm g) = GForm $ \env fe -> do
        (f1, f2, f3) <- f env fe
        (g1, g2, g3) <- g env fe
        return (f1 <*> g1, f2 `mappend` g2, f3 `mappend` g3)

-- | Display only the actual input widget code, without any decoration.
fieldsToPlain :: [FieldInfo sub y] -> GWidget sub y ()
fieldsToPlain = mapM_ fiInput

fieldsToInput :: [FieldInfo sub y] -> [GWidget sub y ()]
fieldsToInput = map fiInput

-- | Display the label, tooltip, input code and errors in a single row of a
-- table.
fieldsToTable :: [FieldInfo sub y] -> GWidget sub y ()
fieldsToTable = mapM_ go
  where
    go fi = do
        wrapWidget (fiInput fi) $ \w -> [$hamlet|
%tr
    %td
        %label!for=$fiIdent.fi$ $fiLabel.fi$
        .tooltip $fiTooltip.fi$
    %td
        ^w^
    $maybe fiErrors.fi err
        %td.errors $err$
|]

class ToForm a where
    toForm :: Maybe a -> Form sub y a
class ToFormField a where
    toFormField :: Html () -> Html () -> Maybe a -> FormField sub y a

-- | Create a required field (ie, one that cannot be blank) from a
-- 'FieldProfile'.
requiredFieldHelper :: FieldProfile sub y a -> Maybe a -> FormField sub y a
requiredFieldHelper (FieldProfile parse render mkXml w name' label tooltip) orig =
  GForm $ \env _ -> do
    name <- maybe newFormIdent return name'
    let (res, val) =
            if null env
                then (FormMissing, maybe "" render orig)
                else case lookup name env of
                        Nothing -> (FormMissing, "")
                        Just "" -> (FormFailure ["Value is required"], "")
                        Just x ->
                            case parse x of
                                Left e -> (FormFailure [e], x)
                                Right y -> (FormSuccess y, x)
    let fi = FieldInfo
            { fiLabel = label
            , fiTooltip = tooltip
            , fiIdent = name
            , fiInput = w name >> addBody (mkXml (string name) (string val) True)
            , fiErrors = case res of
                            FormFailure [x] -> Just $ string x
                            _ -> Nothing
            }
    return (res, [fi], UrlEncoded)

-- | Create an optional field (ie, one that can be blank) from a
-- 'FieldProfile'.
optionalFieldHelper :: FieldProfile sub y a -> Maybe (Maybe a)
                    -> FormField sub y (Maybe a)
optionalFieldHelper (FieldProfile parse render mkXml w name' label tooltip) orig' =
  GForm $ \env _ -> do
    let orig = join orig'
    name <- maybe newFormIdent return name'
    let (res, val) =
            if null env
                then (FormSuccess Nothing, maybe "" render orig)
                else case lookup name env of
                        Nothing -> (FormSuccess Nothing, "")
                        Just "" -> (FormSuccess Nothing, "")
                        Just x ->
                            case parse x of
                                Left e -> (FormFailure [e], x)
                                Right y -> (FormSuccess $ Just y, x)
    let fi = FieldInfo
            { fiLabel = label
            , fiTooltip = tooltip
            , fiIdent = name
            , fiInput = w name >> addBody (mkXml (string name) (string val) False)
            , fiErrors = case res of
                            FormFailure [x] -> Just $ string x
                            _ -> Nothing
            }
    return (res, [fi], UrlEncoded)

-- | A generic definition of a form field that can be used for generating both
-- required and optional fields. See 'requiredFieldHelper and
-- 'optionalFieldHelper'.
data FieldProfile sub y a = FieldProfile
    { fpParse :: String -> Either String a
    , fpRender :: a -> String
    , fpHamlet :: Html () -> Html () -> Bool -> Hamlet (Route y)
    , fpWidget :: String -> GWidget sub y ()
    , fpName :: Maybe String
    , fpLabel :: Html ()
    , fpTooltip :: Html ()
    }

--------------------- Begin prebuilt forms

stringField :: Html () -> Html () -> FormletField sub y String
stringField label tooltip = requiredFieldHelper stringFieldProfile
    { fpLabel = label
    , fpTooltip = tooltip
    }

maybeStringField :: Html () -> Html () -> FormletField sub y (Maybe String)
maybeStringField label tooltip = optionalFieldHelper stringFieldProfile
    { fpLabel = label
    , fpTooltip = tooltip
    }

stringFieldProfile :: FieldProfile sub y String
stringFieldProfile = FieldProfile
    { fpParse = Right
    , fpRender = id
    , fpHamlet = \name val isReq -> [$hamlet|
%input#$name$!name=$name$!type=text!:isReq:required!value=$val$
|]
    , fpWidget = \_name -> return ()
    , fpName = Nothing
    , fpLabel = mempty
    , fpTooltip = mempty
    }
instance ToFormField String where
    toFormField = stringField
instance ToFormField (Maybe String) where
    toFormField = maybeStringField

intField :: Integral i => Html () -> Html () -> FormletField sub y i
intField l t = requiredFieldHelper intFieldProfile
    { fpLabel = l
    , fpTooltip = t
    }

maybeIntField :: Integral i =>
              Html () -> Html () -> FormletField sub y (Maybe i)
maybeIntField l t = optionalFieldHelper intFieldProfile
    { fpLabel = l
    , fpTooltip = t
    }

intFieldProfile :: Integral i => FieldProfile sub y i
intFieldProfile = FieldProfile
    { fpParse = maybe (Left "Invalid integer") Right . readMayI
    , fpRender = showI
    , fpHamlet = \name val isReq -> [$hamlet|
%input#$name$!name=$name$!type=number!:isReq:required!value=$val$
|]
    , fpWidget = \_name -> return ()
    , fpName = Nothing
    , fpLabel = mempty
    , fpTooltip = mempty
    }
  where
    showI x = show (fromIntegral x :: Integer)
    readMayI s = case reads s of
                    (x, _):_ -> Just $ fromInteger x
                    [] -> Nothing
instance ToFormField Int where
    toFormField = intField
instance ToFormField (Maybe Int) where
    toFormField = maybeIntField
instance ToFormField Int64 where
    toFormField = intField
instance ToFormField (Maybe Int64) where
    toFormField = maybeIntField

doubleField :: Html () -> Html () -> FormletField sub y Double
doubleField l t = requiredFieldHelper doubleFieldProfile
    { fpLabel = l
    , fpTooltip = t
    }

maybeDoubleField :: Html () -> Html () -> FormletField sub y (Maybe Double)
maybeDoubleField l t = optionalFieldHelper doubleFieldProfile
    { fpLabel = l
    , fpTooltip = t
    }

doubleFieldProfile :: FieldProfile sub y Double
doubleFieldProfile = FieldProfile
    { fpParse = maybe (Left "Invalid number") Right . readMay
    , fpRender = show
    , fpHamlet = \name val isReq -> [$hamlet|
%input#$name$!name=$name$!type=number!:isReq:required!value=$val$
|]
    , fpWidget = \_name -> return ()
    , fpName = Nothing
    , fpLabel = mempty
    , fpTooltip = mempty
    }
instance ToFormField Double where
    toFormField = doubleField
instance ToFormField (Maybe Double) where
    toFormField = maybeDoubleField

dayField :: Html () -> Html () -> FormletField sub y Day
dayField l t = requiredFieldHelper dayFieldProfile
    { fpLabel = l
    , fpTooltip = t
    }

maybeDayField :: Html () -> Html () -> FormletField sub y (Maybe Day)
maybeDayField l t = optionalFieldHelper dayFieldProfile
    { fpLabel = l
    , fpTooltip = t
    }

dayFieldProfile :: FieldProfile sub y Day
dayFieldProfile = FieldProfile
    { fpParse = maybe (Left "Invalid day, must be in YYYY-MM-DD format") Right
              . readMay
    , fpRender = show
    , fpHamlet = \name val isReq -> [$hamlet|
%input#$name$!name=$name$!type=date!:isReq:required!value=$val$
|]
    , fpWidget = const $ return ()
    , fpName = Nothing
    , fpLabel = mempty
    , fpTooltip = mempty
    }
instance ToFormField Day where
    toFormField = dayField
instance ToFormField (Maybe Day) where
    toFormField = maybeDayField

jqueryDayField :: Html () -> Html () -> FormletField sub y Day
jqueryDayField l t = requiredFieldHelper jqueryDayFieldProfile
    { fpLabel = l
    , fpTooltip = t
    }

maybeJqueryDayField :: Html () -> Html () -> FormletField sub y (Maybe Day)
maybeJqueryDayField l t = optionalFieldHelper jqueryDayFieldProfile
    { fpLabel = l
    , fpTooltip = t
    }

jqueryDayFieldProfile :: FieldProfile sub y Day
jqueryDayFieldProfile = FieldProfile
    { fpParse = maybe
                  (Left "Invalid day, must be in YYYY-MM-DD format")
                  Right
              . readMay
    , fpRender = show
    , fpHamlet = \name val isReq -> [$hamlet|
%input#$name$!name=$name$!type=date!:isReq:required!value=$val$
|]
    , fpWidget = \name -> do
        addScriptRemote urlJqueryJs
        addScriptRemote urlJqueryUiJs
        addStylesheetRemote urlJqueryUiCss
        addJavaScript [$hamlet|
$$(function(){$$("#$name$").datepicker({dateFormat:'yy-mm-dd'})});
|]
    , fpName = Nothing
    , fpLabel = mempty
    , fpTooltip = mempty
    }

-- | A newtype wrapper around 'Day', using jQuery UI date picker for the
-- 'ToFormField' instance.
newtype JqueryDay = JqueryDay { unJqueryDay :: Day }
    deriving PersistField
instance ToFormField JqueryDay where
    toFormField = applyFormTypeWrappers JqueryDay unJqueryDay jqueryDayField
instance ToFormField (Maybe JqueryDay) where
    toFormField = applyFormTypeWrappers (fmap JqueryDay) (fmap unJqueryDay)
                  maybeJqueryDayField

parseTime :: String -> Either String TimeOfDay
parseTime (h2:':':m1:m2:[]) = parseTimeHelper ('0', h2, m1, m2, '0', '0')
parseTime (h1:h2:':':m1:m2:[]) = parseTimeHelper (h1, h2, m1, m2, '0', '0')
parseTime (h1:h2:':':m1:m2:':':s1:s2:[]) =
    parseTimeHelper (h1, h2, m1, m2, s1, s2)
parseTime _ = Left "Invalid time, must be in HH:MM[:SS] format"

parseTimeHelper :: (Char, Char, Char, Char, Char, Char)
                -> Either [Char] TimeOfDay
parseTimeHelper (h1, h2, m1, m2, s1, s2)
    | h < 0 || h > 23 = Left $ "Invalid hour: " ++ show h
    | m < 0 || m > 59 = Left $ "Invalid minute: " ++ show m
    | s < 0 || s > 59 = Left $ "Invalid second: " ++ show s
    | otherwise = Right $ TimeOfDay h m s
  where
    h = read [h1, h2]
    m = read [m1, m2]
    s = fromInteger $ read [s1, s2]

timeField :: Html () -> Html () -> FormletField sub y TimeOfDay
timeField label tooltip = requiredFieldHelper timeFieldProfile
    { fpLabel = label
    , fpTooltip = tooltip
    }

maybeTimeField :: Html () -> Html () -> FormletField sub y (Maybe TimeOfDay)
maybeTimeField label tooltip = optionalFieldHelper timeFieldProfile
    { fpLabel = label
    , fpTooltip = tooltip
    }

timeFieldProfile :: FieldProfile sub y TimeOfDay
timeFieldProfile = FieldProfile
    { fpParse = parseTime
    , fpRender = show
    , fpHamlet = \name val isReq -> [$hamlet|
%input#$name$!name=$name$!:isReq:required!value=$val$
|]
    , fpWidget = const $ return ()
    , fpName = Nothing
    , fpLabel = mempty
    , fpTooltip = mempty
    }
instance ToFormField TimeOfDay where
    toFormField = timeField
instance ToFormField (Maybe TimeOfDay) where
    toFormField = maybeTimeField

boolField :: Html () -> Html () -> Maybe Bool -> FormField sub y Bool
boolField label tooltip orig = GForm $ \env _ -> do
    name <- newFormIdent
    let (res, val) =
            if null env
                then (FormMissing, fromMaybe False orig)
                else case lookup name env of
                        Nothing -> (FormSuccess False, False)
                        Just _ -> (FormSuccess True, True)
    let fi = FieldInfo
            { fiLabel = label
            , fiTooltip = tooltip
            , fiIdent = name
            , fiInput = addBody [$hamlet|
%input#$name$!type=checkbox!name=$name$!:val:checked
|]
            , fiErrors = case res of
                            FormFailure [x] -> Just $ string x
                            _ -> Nothing
            }
    return (res, [fi], UrlEncoded)
instance ToFormField Bool where
    toFormField = boolField

htmlField :: Html () -> Html () -> FormletField sub y (Html ())
htmlField label tooltip = requiredFieldHelper htmlFieldProfile
    { fpLabel = label
    , fpTooltip = tooltip
    }

maybeHtmlField :: Html () -> Html () -> FormletField sub y (Maybe (Html ()))
maybeHtmlField label tooltip = optionalFieldHelper htmlFieldProfile
    { fpLabel = label
    , fpTooltip = tooltip
    }

htmlFieldProfile :: FieldProfile sub y (Html ())
htmlFieldProfile = FieldProfile
    { fpParse = Right . preEscapedString
    , fpRender = U.toString . renderHtml
    , fpHamlet = \name val _isReq -> [$hamlet|
%textarea.html#$name$!name=$name$ $val$
|]
    , fpWidget = const $ return ()
    , fpName = Nothing
    , fpLabel = mempty
    , fpTooltip = mempty
    }
instance ToFormField (Html ()) where
    toFormField = htmlField
instance ToFormField (Maybe (Html ())) where
    toFormField = maybeHtmlField

newtype NicHtml = NicHtml { unNicHtml :: Html () }
    deriving PersistField

type Html' = Html ()

nicHtmlField :: Html () -> Html () -> FormletField sub y (Html ())
nicHtmlField label tooltip = requiredFieldHelper nicHtmlFieldProfile
    { fpLabel = label
    , fpTooltip = tooltip
    }

maybeNicHtmlField :: Html () -> Html () -> FormletField sub y (Maybe (Html ()))
maybeNicHtmlField label tooltip = optionalFieldHelper nicHtmlFieldProfile
    { fpLabel = label
    , fpTooltip = tooltip
    }

nicHtmlFieldProfile :: FieldProfile sub y (Html ())
nicHtmlFieldProfile = FieldProfile
    { fpParse = Right . preEscapedString
    , fpRender = U.toString . renderHtml
    , fpHamlet = \name val _isReq -> [$hamlet|
%textarea.html#$name$!name=$name$ $val$
|]
    , fpWidget = \name -> do
        addScriptRemote "http://js.nicedit.com/nicEdit-latest.js"
        addJavaScript [$hamlet|bkLib.onDomLoaded(function(){new nicEditor({fullPanel:true}).panelInstance("$name$")});|]
    , fpName = Nothing
    , fpLabel = mempty
    , fpTooltip = mempty
    }
instance ToFormField NicHtml where
    toFormField = applyFormTypeWrappers NicHtml unNicHtml nicHtmlField
instance ToFormField (Maybe NicHtml) where
    toFormField = applyFormTypeWrappers (fmap NicHtml) (fmap unNicHtml)
                  maybeNicHtmlField

applyFormTypeWrappers :: (a -> b) -> (b -> a)
                      -> (f -> g -> FormletField s y a)
                      -> (f -> g -> FormletField s y b)
applyFormTypeWrappers wrap unwrap field l t orig =
    fmap wrap $ field l t $ fmap unwrap orig

readMay :: Read a => String -> Maybe a
readMay s = case reads s of
                (x, _):_ -> Just x
                [] -> Nothing

selectField :: Eq x => [(x, String)]
            -> Html () -> Html ()
            -> Maybe x -> FormField sub master x
selectField pairs label tooltip initial = GForm $ \env _ -> do
    i <- newFormIdent
    let pairs' = zip [1 :: Int ..] pairs
    let res = case lookup i env of
                Nothing -> FormMissing
                Just "none" -> FormFailure ["Field is required"]
                Just x ->
                    case reads x of
                        (x', _):_ ->
                            case lookup x' pairs' of
                                Nothing -> FormFailure ["Invalid entry"]
                                Just (y, _) -> FormSuccess y
                        [] -> FormFailure ["Invalid entry"]
    let isSelected x =
            case res of
                FormSuccess y -> x == y
                _ -> Just x == initial
    let input = [$hamlet|
%select#$i$!name=$i$
    %option!value=none
    $forall pairs' pair
        %option!value=$show.fst.pair$!:isSelected.fst.snd.pair:selected $snd.snd.pair$
|]
    let fi = FieldInfo
            { fiLabel = label
            , fiTooltip = tooltip
            , fiIdent = i
            , fiInput = addBody input
            , fiErrors = case res of
                            FormFailure [x] -> Just $ string x
                            _ -> Nothing
            }
    return (res, [fi], UrlEncoded)

maybeSelectField :: Eq x => [(x, String)]
                 -> Html () -> Html ()
                 -> Maybe x -> FormField sub master (Maybe x)
maybeSelectField pairs label tooltip initial = GForm $ \env _ -> do
    i <- newFormIdent
    let pairs' = zip [1 :: Int ..] pairs
    let res = case lookup i env of
                Nothing -> FormMissing
                Just "none" -> FormSuccess Nothing
                Just x ->
                    case reads x of
                        (x', _):_ ->
                            case lookup x' pairs' of
                                Nothing -> FormFailure ["Invalid entry"]
                                Just (y, _) -> FormSuccess $ Just y
                        [] -> FormFailure ["Invalid entry"]
    let isSelected x =
            case res of
                FormSuccess y -> Just x == y
                _ -> Just x == initial
    let input = [$hamlet|
%select#$i$!name=$i$
    %option!value=none
    $forall pairs' pair
        %option!value=$show.fst.pair$!:isSelected.fst.snd.pair:selected $snd.snd.pair$
|]
    let fi = FieldInfo
            { fiLabel = label
            , fiTooltip = tooltip
            , fiIdent = i
            , fiInput = addBody input
            , fiErrors = case res of
                            FormFailure [x] -> Just $ string x
                            _ -> Nothing
            }
    return (res, [fi], UrlEncoded)

--------------------- End prebuilt forms

--------------------- Begin prebuilt inputs

stringInput :: String -> FormInput sub master String
stringInput n =
    mapFormXml fieldsToInput $
    requiredFieldHelper stringFieldProfile
    { fpName = Just n
    } Nothing

maybeStringInput :: String -> FormInput sub master (Maybe String)
maybeStringInput n =
    mapFormXml fieldsToInput $
    optionalFieldHelper stringFieldProfile
    { fpName = Just n
    } Nothing

boolInput :: String -> FormInput sub master Bool
boolInput n = GForm $ \env _ -> return
    (FormSuccess $ isJust $ lookup n env, return $ addBody [$hamlet|
%input#$n$!type=checkbox!name=$n$
|], UrlEncoded)

dayInput :: String -> FormInput sub master Day
dayInput n =
    mapFormXml fieldsToInput $
    requiredFieldHelper dayFieldProfile
    { fpName = Just n
    } Nothing

maybeDayInput :: String -> FormInput sub master (Maybe Day)
maybeDayInput n =
    mapFormXml fieldsToInput $
    optionalFieldHelper dayFieldProfile
    { fpName = Just n
    } Nothing

--------------------- End prebuilt inputs

-- | Get a unique identifier.
newFormIdent :: Monad m => StateT Int m String
newFormIdent = do
    i <- get
    let i' = i + 1
    put i'
    return $ "f" ++ show i'

runFormGeneric :: Env
               -> FileEnv
               -> GForm sub y xml a
               -> GHandler sub y (FormResult a, xml, Enctype)
runFormGeneric env fe f = evalStateT (deform f env fe) 1

-- | Run a form against POST parameters.
runFormPost :: GForm sub y xml a
            -> GHandler sub y (FormResult a, xml, Enctype)
runFormPost f = do
    rr <- getRequest
    (pp, files) <- liftIO $ reqRequestBody rr
    runFormGeneric pp files f

-- | Run a form against POST parameters, disregarding the resulting HTML and
-- returning an error response on invalid input.
runFormPost' :: GForm sub y xml a -> GHandler sub y a
runFormPost' = helper <=< runFormPost

-- | Run a form against GET parameters, disregarding the resulting HTML and
-- returning an error response on invalid input.
runFormGet' :: GForm sub y xml a -> GHandler sub y a
runFormGet' = helper <=< runFormGet

helper :: (FormResult a, b, c) -> GHandler sub y a
helper (FormSuccess a, _, _) = return a
helper (FormFailure e, _, _) = invalidArgs e
helper (FormMissing, _, _) = invalidArgs ["No input found"]

-- | Run a form against GET parameters.
runFormGet :: GForm sub y xml a
           -> GHandler sub y (FormResult a, xml, Enctype)
runFormGet f = do
    gs <- reqGetParams `fmap` getRequest
    runFormGeneric gs [] f

-- | This function allows two different monadic functions to share the same
-- input and have their results concatenated. This is particularly useful for
-- allowing 'mkToForm' to share its input with mkPersist.
share2 :: Monad m => (a -> m [b]) -> (a -> m [b]) -> a -> m [b]
share2 f g a = do
    f' <- f a
    g' <- g a
    return $ f' ++ g'

-- | Create 'ToForm' instances for the entities given. In addition to regular 'EntityDef' attributes understood by persistent, it also understands label= and tooltip=.
mkToForm :: [EntityDef] -> Q [Dec]
mkToForm = mapM derive
  where
    getLabel (x, _, z) = fromMaybe (toLabel x) $ getLabel' z
    getLabel' [] = Nothing
    getLabel' (('l':'a':'b':'e':'l':'=':x):_) = Just x
    getLabel' (_:x) = getLabel' x
    getTooltip (_, _, z) = fromMaybe "" $ getTooltip' z
    getTooltip' (('t':'o':'o':'l':'t':'i':'p':'=':x):_) = Just x
    getTooltip' (_:x) = getTooltip' x
    getTooltip' [] = Nothing
    derive :: EntityDef -> Q Dec
    derive t = do
        let cols = map (getLabel &&& getTooltip) $ entityColumns t
        ap <- [|(<*>)|]
        just <- [|pure|]
        nothing <- [|Nothing|]
        let just' = just `AppE` ConE (mkName $ entityName t)
        string' <- [|string|]
        mfx <- [|mapFormXml|]
        ftt <- [|fieldsToTable|]
        let go_ = go ap just' string' mfx ftt
        let c1 = Clause [ ConP (mkName "Nothing") []
                        ]
                        (NormalB $ go_ $ zip cols $ map (const nothing) cols)
                        []
        xs <- mapM (const $ newName "x") cols
        let xs' = map (AppE just . VarE) xs
        let c2 = Clause [ ConP (mkName "Just") [ConP (mkName $ entityName t)
                            $ map VarP xs]]
                        (NormalB $ go_ $ zip cols xs')
                        []
        return $ InstanceD [] (ConT ''ToForm
                              `AppT` ConT (mkName $ entityName t))
            [FunD (mkName "toForm") [c1, c2]]
    go ap just' string' mfx ftt a =
        let x = foldl (ap' ap) just' $ map (go' string') a
         in mfx `AppE` ftt `AppE` x
    go' string' ((label, tooltip), ex) =
        let label' = string' `AppE` LitE (StringL label)
            tooltip' = string' `AppE` LitE (StringL tooltip)
         in VarE (mkName "toFormField") `AppE` label'
                `AppE` tooltip' `AppE` ex
    ap' ap x y = InfixE (Just x) ap (Just y)

toLabel :: String -> String
toLabel "" = ""
toLabel (x:rest) = toUpper x : go rest
  where
    go "" = ""
    go (c:cs)
        | isUpper c = ' ' : c : go cs
        | otherwise = c : go cs

jqueryAutocompleteField ::
    Route y -> Html () -> Html () -> FormletField sub y String
jqueryAutocompleteField src l t =
    requiredFieldHelper $ (jqueryAutocompleteFieldProfile src)
        { fpLabel = l
        , fpTooltip = t
        }

maybeJqueryAutocompleteField ::
    Route y -> Html () -> Html () -> FormletField sub y (Maybe String)
maybeJqueryAutocompleteField src l t =
    optionalFieldHelper $ (jqueryAutocompleteFieldProfile src)
        { fpLabel = l
        , fpTooltip = t
        }

jqueryAutocompleteFieldProfile :: Route y -> FieldProfile sub y String
jqueryAutocompleteFieldProfile src = FieldProfile
    { fpParse = Right
    , fpRender = id
    , fpHamlet = \name val isReq -> [$hamlet|
%input.autocomplete#$name$!name=$name$!type=text!:isReq:required!value=$val$
|]
    , fpWidget = \name -> do
        addScriptRemote urlJqueryJs
        addScriptRemote urlJqueryUiJs
        addStylesheetRemote urlJqueryUiCss
        addJavaScript [$hamlet|
$$(function(){$$("#$name$").autocomplete({source:"@src@",minLength:2})});
|]
    , fpName = Nothing
    , fpLabel = mempty
    , fpTooltip = mempty
    }

emailFieldProfile :: FieldProfile s y String
emailFieldProfile = FieldProfile
    { fpParse = \s -> if Email.isValid s
                        then Right s
                        else Left "Invalid e-mail address"
    , fpRender = id
    , fpHamlet = \name val isReq -> [$hamlet|
%input#$name$!name=$name$!type=email!:isReq:required!value=$val$
|]
    , fpWidget = const $ return ()
    , fpName = Nothing
    , fpLabel = mempty
    , fpTooltip = mempty
    }

emailField :: Html () -> Html () -> FormletField sub y String
emailField label tooltip = requiredFieldHelper emailFieldProfile
    { fpLabel = label
    , fpTooltip = tooltip
    }

maybeEmailField :: Html () -> Html () -> FormletField sub y (Maybe String)
maybeEmailField label tooltip = optionalFieldHelper emailFieldProfile
    { fpLabel = label
    , fpTooltip = tooltip
    }

emailInput :: String -> FormInput sub master String
emailInput n =
    mapFormXml fieldsToInput $
    requiredFieldHelper emailFieldProfile
    { fpName = Just n
    } Nothing