-- | Optionally failing transformers for forms
--
module Text.Digestive.Transform
    ( Transformer (..)
    , transform
    , transformFormlet
    , transformEither
    , transformEitherM
    , transformRead
    , required
    ) where

import Prelude hiding ((.), id)

import Control.Monad ((<=<))
import Control.Category (Category, (.), id)
import Control.Arrow (Arrow, arr, first)

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

-- | A transformer that transforms a value of type a to a value of type b
--
newtype Transformer m e a b = Transformer
    { unTransformer :: a -> m (Either [e] b)
    }

instance Monad m => Category (Transformer m e) where
    id = Transformer $ return . Right
    f . g = Transformer $   either (return . Left) (unTransformer f)
                        <=< unTransformer g

instance Monad m => Arrow (Transformer m e) where
    arr f = Transformer $ return . Right . f
    first t = Transformer $ \(x, y) -> unTransformer t x >>=
        return . either Left (Right . (flip (,) y))

-- | Apply a transformer to a form
--
transform :: Monad m => Form m i e v a -> Transformer m e a b -> Form m i e v b
transform form transformer = Form $ do
    (v, r) <- unForm form
    range <- getFormRange
    return (v, r >>= transform' range)
  where
    -- We already have an error, cannot continue
    transform' _     (Error e) = return (Error e)
    -- Apply transformer
    transform' range (Ok x)    = do
        ex <- unTransformer transformer x
        return $ case ex of
            -- Attach the range information to the errors
            Left e   -> Error $ map ((,) range) e
            -- All fine
            Right x' -> Ok x'

-- | Apply a transformer to a formlet
--
transformFormlet :: Monad m
                 => (b -> a)             -- ^ Needed to produce defaults
                 -> Formlet m i e v a    -- ^ Formlet to transform
                 -> Transformer m e a b  -- ^ Transformer
                 -> Formlet m i e v b    -- ^ Resulting formlet
transformFormlet f formlet transformer def =
    formlet (fmap f def) `transform` transformer

-- | Build a transformer from a simple function that returns an 'Either' result.
--
transformEither :: Monad m => (a -> Either e b) -> Transformer m e a b
transformEither f = transformEitherM $ return . f

-- | A monadic version of 'transformEither'
--
transformEitherM :: Monad m => (a -> m (Either e b)) -> Transformer m e a b
transformEitherM f = Transformer $ 
    return . either (Left . return) (Right . id) <=< f

-- | Create a transformer for any value of type a that is an instance of 'Read'
--
transformRead :: (Monad m, Read a)
              => e                         -- ^ Error given if read fails
              -> Transformer m e String a  -- ^ Resulting transformer
transformRead error' = transformEither $ \str -> case readsPrec 1 str of
    [(x, "")] -> Right x
    _ -> Left error'

-- | A transformer that converts 'Maybe a' to 'a'.
required :: (Monad m) => 
            e  -- ^ error to return if value is 'Nothing'
         -> Transformer m e (Maybe a) a
required err = transformEither $ maybe (Left err) Right