{-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses
  , ScopedTypeVariables, TypeFamilies, TypeSynonymInstances #-}
{-# OPTIONS_GHC -F -pgmFtrhsx #-}
module Main where

import Control.Applicative
import Control.Applicative.Indexed (IndexedFunctor(..), IndexedApplicative(..))
import Control.Monad               (msum)
import Happstack.Server
import Happstack.Server.HSP.HTML ()
import HSP.ServerPartT
import HSP
import Text.Reform ( CommonFormError(..), Form, FormError(..), Proof(..), (++>)
                   , (<++), commonFormErrorStr, decimal, prove
                   , transformEither, transform )
import Text.Reform.Happstack
import Text.Reform.HSP.String

type AppT m = XMLGenT (ServerPartT m)

appTemplate :: ( Functor m, Monad m
               , EmbedAsChild (ServerPartT m) headers
               , EmbedAsChild (ServerPartT m) body
               ) =>
               String     -- ^ contents of <title> tag
            -> headers    -- ^ extra content for <head> tag, use () for nothing
            -> body       -- ^ contents of <body> tag
            -> AppT m Response
appTemplate title headers body =
  toResponse <$>
    <html>
     <head>
      <title><% title %></title>
      <% headers %>
     </head>
     <body>
      <% body %>
     </body>
    </html>

type SimpleForm = Form (AppT IO) [Input] AppError [AppT IO (XMLType (ServerPartT IO))] ()

data AppError
    = Required
    | NotANatural String
    | AppCFE (CommonFormError [Input])
      deriving Show

instance (Monad m) => EmbedAsChild (ServerPartT m) AppError where
    asChild Required          = asChild $ "required"
    asChild (NotANatural str) = asChild $ "Could not decode as a positive integer: " ++
                                          str
    asChild (AppCFE cfe)      = asChild $ commonFormErrorStr show cfe

instance FormError AppError where
    type ErrorInputType AppError = [Input]
    commonFormError = AppCFE

data Message = Message
    { name    :: String -- ^ the author's name
    , title   :: String -- ^ the message title
    , message :: String -- ^ contents of the message
    } deriving (Eq, Ord, Read, Show)

renderMessage :: (Monad m) => Message -> AppT m XML
renderMessage msg =
    <dl>
      <dt>name:</dt>    <dd><% name msg    %></dd>
      <dt>title:</dt>   <dd><% title msg   %></dd>
      <dt>message:</dt> <dd><% message msg %></dd>
    </dl>

postForm :: SimpleForm Message
postForm =
    Message
     <$> label "name:"             ++> inputText ""       <++ br
     <*> label "title: "           ++> inputText ""       <++ br
     <*> (label "message:" <++ br) ++> textarea 80 40 ""  <++ br
     <*  inputSubmit "post"

postPage :: AppT IO Response
postPage =
    dir "post" $
        do result <- happstackEitherForm (form "/post") "post" postForm
           case result of
             (Left formHtml) -> appTemplate "post" () formHtml
             (Right msg)     -> appTemplate "Your Message" () $ renderMessage msg

postPage2 :: AppT IO Response
postPage2 =
    dir "post2" $
        appTemplate "post 2" () $
           <% reform (form "/post2") "post2" displayMessage Nothing postForm %>
    where
      displayMessage msg = appTemplate "Your Message" () $ renderMessage msg

required :: String -> Either AppError String
required []  = Left Required
required str = Right str

validPostForm :: SimpleForm Message
validPostForm =
    Message <$> name <*> title <*> msg <*  inputSubmit "post"
        where
          name  = errorList ++> label "name:"             ++>
                    (inputText ""     `transformEither` required)  <++ br

          title = errorList ++> label "title:"            ++>
                    (inputText ""      `transformEither` required) <++ br

          msg   = errorList ++> (label "message:" <++ br) ++>
                    (textarea 80 40 "" `transformEither` required) <++ br

validPage :: AppT IO Response
validPage =
    dir "valid" $
        appTemplate "valid post" () $
           <% reform (form "/valid") "valid" displayMessage Nothing validPostForm %>
    where
      displayMessage msg = appTemplate "Your Message" () $ renderMessage msg

type ProofForm proof =
  Form IO [Input] AppError [AppT IO (XMLType (ServerPartT IO))] proof

data NotNull = NotNull

assertNotNull :: (Monad m) => error -> [a] -> m (Either error [a])
assertNotNull errorMsg []  = return (Left errorMsg)
assertNotNull _        xs  = return (Right xs)

notNullProof :: (Monad m) =>
                error -- ^ error to return if list is empty
             -> Proof m error NotNull [a] [a]
notNullProof errorMsg =
    Proof { proofName     = NotNull
          , proofFunction = assertNotNull errorMsg
          }

data ValidMessage = ValidMessage

mkMessage :: ProofForm (NotNull -> NotNull -> NotNull -> ValidMessage)
                       (String -> String -> String -> Message)
mkMessage = ipure (\NotNull NotNull NotNull -> ValidMessage) Message

inputText' :: String -> ProofForm NotNull String
inputText' initialValue = inputText initialValue `prove` (notNullProof Required)

textarea' :: Int -> Int -> String -> ProofForm NotNull String
textarea' cols rows initialValue =
    textarea cols rows initialValue `prove` (notNullProof Required)

provenPostForm :: ProofForm ValidMessage Message
provenPostForm =
    mkMessage <<*>> errorList ++> label "name: "    ++> inputText' ""
              <<*>> errorList ++> label "title: "   ++> inputText' ""
              <<*>> errorList ++> label "message: " ++> textarea' 80 40 ""

inputInteger :: SimpleForm Integer
inputInteger = inputText "" `transform` (decimal NotANatural)

main :: IO ()
main =
  simpleHTTP nullConf $ unXMLGenT $
      do decodeBody (defaultBodyPolicy "/tmp/" 0 10000 10000)
         msum [ postPage
              , postPage2
              , validPage
              , do nullDir
                   appTemplate "forms" () $
                    <ul>
                     <li><a href="/post">Simple Form</a></li>
                     <li><a href="/post2">Simple Form (postPage2 implementation)</a></li>
                     <li><a href="/valid">Valid Form</a></li>
                    </ul>
              ]

