-- | Validators that can be attached to forms
--
module Text.Digestive.Validate
    ( Validator
    , validate
    , validateMany
    , check
    , checkM
    ) where

import Prelude hiding (id)

import Control.Monad (liftM2)
import Data.Monoid (Monoid (..))
import Control.Category (id)

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

-- | A validator. Invariant: the validator should not modify the result value,
-- only check it.
--
newtype Validator m e a = Validator {unValidator :: Transformer m e a a}

instance Monad m => Monoid (Validator m e a) where
    mempty = Validator id
    v1 `mappend` v2 = Validator $ Transformer $ \inp ->
        liftM2 eitherPlus (unTransformer (unValidator v1) inp)
                          (unTransformer (unValidator v2) inp)
      where
        eitherPlus (Left e) (Left i) = Left $ e ++ i
        eitherPlus (Left e) (Right _) = Left e
        eitherPlus (Right _) (Left e) = Left e
        eitherPlus (Right a) (Right _) = Right a

-- | Attach a validator to a form.
--
validate :: Monad m => Form m i e v a -> Validator m e a -> Form m i e v a
validate form = transform form . unValidator

-- | Attach multiple validators to a form.
--
validateMany :: Monad m => Form m i e v a -> [Validator m e a] -> Form m i e v a
validateMany form = validate form . mconcat

-- | Easy way to create a pure validator
--
check :: Monad m
      => e                -- ^ Error message
      -> (a -> Bool)      -- ^ Actual validation
      -> Validator m e a  -- ^ Resulting validator
check error' = checkM error' . (return .)

-- | Easy way to create a monadic validator
--
checkM :: Monad m
       => e                -- ^ Error message
       -> (a -> m Bool)    -- ^ Actual validation
       -> Validator m e a  -- ^ Resulting validator
checkM error' f = Validator $ Transformer $ \x -> do
    valid <- f x
    return $ if valid then Right x else Left [error']