-- | Functions to construct common forms
--
module Text.Digestive.Common
    ( input
    , inputString
    , inputRead
    , inputBool
    , inputChoice
    , label
    , errors
    , childErrors
    ) where

import Control.Applicative ((<$>))
import Control.Monad (mplus)
import Data.Monoid (Monoid, mconcat)
import Data.Maybe (fromMaybe)

import Text.Digestive.Types
import Text.Digestive.Result
import Text.Digestive.Transform

input :: (Monad m, Functor m)
      => (Bool -> Maybe String -> d -> s)           -- ^ Get the viewed result
      -> (Maybe String -> FormRange -> Result e a)  -- ^ Get the returned result
      -> (FormId -> s -> v)                         -- ^ View constructor
      -> d                                          -- ^ Default value
      -> Form m String e v a                        -- ^ Resulting form
input toView toResult createView defaultInput = Form $ do
    isInput <- isFormInput
    inp <- getFormInput
    id' <- getFormId
    range <- getFormRange
    let view' = toView isInput inp defaultInput
        result' = toResult inp range
    return (View (const $ createView id' view'), result')

inputString :: (Monad m, Functor m)
            => (FormId -> Maybe String -> v)  -- ^ View constructor
            -> Maybe String                   -- ^ Default value
            -> Form m String e v String       -- ^ Resulting form
inputString = input toView toResult
  where
    toView = const mplus
    toResult = const . Ok . fromMaybe ""

inputRead :: (Monad m, Functor m, Read a, Show a)
          => (FormId -> Maybe String -> v)  -- ^ View constructor
          -> e                              -- ^ Error when no read
          -> Maybe a                        -- ^ Default input
          -> Form m String e v a            -- ^ Resulting form
inputRead cons' error' def = inputString cons' (fmap show def)
    `transform` transformRead error'

inputBool :: (Monad m, Functor m)
          => (FormId -> Bool -> v)   -- ^ View constructor
          -> Bool                    -- ^ Default input
          -> Form m String e v Bool  -- ^ Resulting form
inputBool = input toView toResult
  where
    toView isInput inp def = if isInput then readBool inp else def
    toResult inp _ = Ok $ readBool inp
    readBool (Just x) = not (null x)
    readBool Nothing  = False

inputChoice :: (Monad m, Functor m, Monoid v, Eq a)
            => (FormId -> String -> Bool -> a -> v)  -- ^ Choice constructor
            -> a                                     -- ^ Default option
            -> [a]                                   -- ^ Choices
            -> Form m String e v a                   -- ^ Resulting form
inputChoice toView defaultInput choices = Form $ do
    inputKey <- fromMaybe "" <$> getFormInput
    id' <- getFormId
    let -- Find the actual input, based on the key, or use the default input
        inp = fromMaybe defaultInput $ lookup inputKey $ zip (ids id') choices
        -- Apply the toView' function to all choices
        view' = mconcat $ zipWith (toView' id' inp) (ids id') choices
    return (View (const view'), Ok inp)
  where
    ids id' = map (((show id' ++ "-") ++) . show) [1 .. length choices]
    toView' id' inp key x = toView id' key (inp == x) x

label :: Monad m
      => (FormId -> v)
      -> Form m i e v ()
label f = Form $ do
    id' <- getFormId
    return (View (const $ f id'), Ok ())

errors :: Monad m
       => ([e] -> v)
       -> Form m i e v ()
errors f = Form $ do
    range <- getFormRange
    return (View (f . retainErrors range), Ok ())

childErrors :: Monad m
            => ([e] -> v)
            -> Form m i e v ()
childErrors f = Form $ do
    range <- getFormRange
    return (View (f . retainChildErrors range), Ok ())