module Yesod.Form.Generic where

import Yesod.Core
import Yesod.Form
import Data.Text (Text)
import Control.Monad
import Control.Applicative
import Data.Monoid
import Control.Monad.Trans.RWS (ask, get, put, runRWST, tell, evalRWST, local)
import Data.Maybe
import qualified Data.Map as Map

newtype GForm w m a = GForm 
  { unGForm :: (HandlerSite m, [Text])
            -> Maybe (Env, FileEnv)
            -> Ints
            -> m (FormResult a, w, Ints, Enctype)
  }

instance Monad m => Functor (GForm w m) where
  fmap f (GForm a) = GForm $ \x y z -> liftM go $ a x y z
    where go (w, x, y, z) = (fmap f w, x, y, z)

instance (Monad m, Monoid w) => Applicative (GForm w m) where
  pure x = GForm $ const $ const $ \ints -> return (FormSuccess x, mempty, ints, mempty)
  (GForm f) <*> (GForm g) = GForm $ \mr env ints -> do
    (a, b, ints', c) <- f mr env ints
    (x, y, ints'', z) <- g mr env ints'
    return (a <*> x, b <> y, ints'', c <> z)

instance Monoid w => MonadTrans (GForm w) where
  lift f = GForm $ \_ _ ints -> do
    x <- f
    return (FormSuccess x, mempty, ints, mempty)

mghelper :: MonadHandler m
         => Enctype 
         -> ([Text] -> [FileInfo] -> m (FormResult a)) -- ^ parser
         -> (Text -> [Text] -> FormResult a -> w) -- ^ function for building output, needs name and vals
         -> MForm m (FormResult a, w)
mghelper enctype parse buildOutput = do
  tell enctype
  mp <- askParams
  name <- newFormIdent 
  case mp of
    Nothing -> return (FormMissing, buildOutput name [] FormMissing)
    Just p -> do
      mfs <- askFiles
      let mvals = fromMaybe [] $ Map.lookup name p
          files = fromMaybe [] $ mfs >>= Map.lookup name
      res <- lift $ parse mvals files
      return (res, buildOutput name mvals res)

ghelper :: MonadHandler m
        => Enctype 
        -> ([Text] -> [FileInfo] -> m (FormResult a)) -- ^ parser
        -> (Text -> [Text] -> FormResult a -> w) -- ^ function for building output, needs name
        -> GForm w m a
ghelper a b c = formToGForm (mghelper a b c)

formToGForm :: (HandlerSite m ~ site, Monad m)
            => MForm m (FormResult a, w)
            -> GForm w m a
formToGForm form = GForm $ \(site, langs) env ints -> do
  ((a, w), ints', enc) <- runRWST form (env, site, langs) ints
  return (a, w, ints', enc)

gFormToForm :: (Monad m, HandlerSite m ~ site)
            => GForm w m a
            -> MForm m (FormResult a, w)
gFormToForm (GForm gform) = do
  ints <- get
  (env, site, langs) <- ask
  (a, w, ints', enc) <- lift $ gform (site, langs) env ints
  put ints'
  tell enc
  return (a, w)

gFormToFormCsrf :: (Monad m, HandlerSite m ~ site)
                => GForm (WidgetT site IO ()) m a
                -> Html
                -> MForm m (FormResult a, (WidgetT site IO ()))
gFormToFormCsrf g h = do
  (r,w) <- gFormToForm g
  return (r,toWidget h <> w)

monoidToGForm :: Monad m => w -> GForm w m ()
monoidToGForm w = formToGForm $ return (FormSuccess (), w)