module Happstack.Server.RqData
    ( 
      
      look
    , looks
    , lookText
    , lookText'
    , lookTexts
    , lookTexts'
    , lookBS
    , lookBSs
    , lookRead
    , lookReads
    , lookFile
    , lookPairs
    , lookPairsBS
    
    , lookCookie
    , lookCookieValue
    , readCookieValue
    
    , lookInput
    , lookInputs
    
    
    
    , body
    , queryString
    , bytestring
    
    , checkRq
    , checkRqM
    , readRq
    , unsafeReadRq
    
    , decodeBody
    
    , BodyPolicy(..)
    , defaultBodyPolicy
    
    , RqData
    , mapRqData
    , Errors(..)
    
    , getDataFn
    , withDataFn
    , FromData(..)
    , getData
    , withData
    
    , RqEnv
    , HasRqData(askRqEnv, localRqEnv,rqDataError)
    ) where
import Control.Applicative                      (Applicative((<*>), pure), Alternative((<|>), empty), WrappedMonad(WrapMonad, unwrapMonad), (<$>))
import Control.Monad                            (MonadPlus(mzero))
import Control.Monad.Reader                     (ReaderT(ReaderT, runReaderT), MonadReader(ask, local), mapReaderT)
import qualified Control.Monad.State.Lazy as Lazy      (StateT, mapStateT)
import qualified Control.Monad.State.Strict as Strict  (StateT, mapStateT)
import qualified Control.Monad.Writer.Lazy as Lazy     (WriterT, mapWriterT)
import qualified Control.Monad.Writer.Strict as Strict (WriterT, mapWriterT)
import qualified Control.Monad.RWS.Lazy as Lazy        (RWST, mapRWST)
import qualified Control.Monad.RWS.Strict as Strict    (RWST, mapRWST)
import Control.Monad.Error                      (Error(noMsg, strMsg), ErrorT, mapErrorT)
import Control.Monad.Trans                      (MonadIO(..), lift)
import Control.Monad.Trans.Except               (ExceptT, mapExceptT)
import qualified Data.ByteString.Char8          as P
import qualified Data.ByteString.Lazy.Char8     as L
import qualified Data.ByteString.Lazy.UTF8      as LU
import Data.Char                                (toLower)
import Data.Either                              (partitionEithers)
import Data.Generics                            (Data, Typeable)
import Data.Maybe                               (fromJust)
import Data.Monoid                              (Monoid(mempty, mappend, mconcat))
import qualified Data.Semigroup                 as SG
import           Data.Text                      (Text)
import qualified Data.Text.Lazy                 as LazyText
import qualified Data.Text.Lazy.Encoding        as LazyText
import Happstack.Server.Cookie                  (Cookie (cookieValue))
import Happstack.Server.Internal.Monads
import Happstack.Server.Types
import Happstack.Server.Internal.MessageWrap    (BodyPolicy(..), bodyInput, defaultBodyPolicy)
import Happstack.Server.Response                (requestEntityTooLarge, toResponse)
newtype ReaderError r e a = ReaderError { unReaderError :: ReaderT r (Either e) a }
    deriving (Functor, Monad, MonadPlus)
instance (Error e, Monoid e) => MonadReader r (ReaderError r e) where
    ask = ReaderError ask
    local f m = ReaderError $ local f (unReaderError m)
instance (Monoid e, Error e) => Applicative (ReaderError r e) where
    pure = return
    (ReaderError (ReaderT f)) <*> (ReaderError (ReaderT a))
        = ReaderError $ ReaderT $ \env -> (f env) `apEither` (a env)
instance (Monoid e, Error e) => Alternative (ReaderError r e) where
    empty = unwrapMonad empty
    f <|> g = unwrapMonad $ (WrapMonad f) <|> (WrapMonad g)
apEither :: (Monoid e) => Either e (a -> b) -> Either e a -> Either e b
apEither (Left errs1) (Left errs2) = Left (errs1 `mappend` errs2)
apEither (Left errs)  _            = Left errs
apEither _            (Left errs)  = Left errs
apEither (Right f)    (Right a)    = Right (f a)
newtype Errors a = Errors { unErrors :: [a] }
    deriving (Eq, Ord, Show, Read, Data, Typeable)
instance SG.Semigroup (Errors a) where
    (Errors x) <> (Errors y) = Errors (x ++ y)
instance Monoid (Errors a) where
    mempty = Errors []
    mappend = (SG.<>)
    mconcat errs = Errors $ concatMap unErrors errs
instance Error (Errors String) where
    noMsg = Errors []
    strMsg str = Errors [str]
runReaderError :: ReaderError r e a -> r -> Either e a
runReaderError = runReaderT . unReaderError
type RqEnv = ([(String, Input)], Maybe [(String, Input)], [(String, Cookie)])
newtype RqData a = RqData { unRqData :: ReaderError RqEnv (Errors String) a }
    deriving (Functor, Monad, MonadPlus, Applicative, Alternative, MonadReader RqEnv )
class HasRqData m where
    askRqEnv :: m RqEnv
    localRqEnv :: (RqEnv -> RqEnv) -> m a -> m a
    
    rqDataError :: Errors String -> m a
instance HasRqData RqData where
    askRqEnv    = RqData ask
    localRqEnv f (RqData re) = RqData $ local f re
    rqDataError e = mapRqData ((Left e) `apEither`) (return ())
instance (MonadIO m, MonadPlus m) => HasRqData (ServerPartT m) where
    askRqEnv = smAskRqEnv
    rqDataError _e = mzero
    localRqEnv = smLocalRqEnv
instance (Monad m, HasRqData m) => HasRqData (ReaderT s m) where
    askRqEnv      = lift askRqEnv
    localRqEnv f  = mapReaderT (localRqEnv f)
    rqDataError e = lift (rqDataError e)
instance (Monad m, HasRqData m) => HasRqData (Lazy.StateT s m) where
    askRqEnv      = lift askRqEnv
    localRqEnv f  = Lazy.mapStateT (localRqEnv f)
    rqDataError e = lift (rqDataError e)
instance (Monad m, HasRqData m) => HasRqData (Strict.StateT s m) where
    askRqEnv      = lift askRqEnv
    localRqEnv f  = Strict.mapStateT (localRqEnv f)
    rqDataError e = lift (rqDataError e)
instance (Monad m, HasRqData m, Monoid w) => HasRqData (Lazy.WriterT w m) where
    askRqEnv      = lift askRqEnv
    localRqEnv f  = Lazy.mapWriterT (localRqEnv f)
    rqDataError e = lift (rqDataError e)
instance (Monad m, HasRqData m, Monoid w) => HasRqData (Strict.WriterT w m) where
    askRqEnv      = lift askRqEnv
    localRqEnv f  = Strict.mapWriterT (localRqEnv f)
    rqDataError e = lift (rqDataError e)
instance (Monad m, HasRqData m, Monoid w) => HasRqData (Lazy.RWST r w s m) where
    askRqEnv      = lift askRqEnv
    localRqEnv f  = Lazy.mapRWST (localRqEnv f)
    rqDataError e = lift (rqDataError e)
instance (Monad m, HasRqData m, Monoid w) => HasRqData (Strict.RWST r w s m) where
    askRqEnv      = lift askRqEnv
    localRqEnv f  = Strict.mapRWST (localRqEnv f)
    rqDataError e = lift (rqDataError e)
instance (Monad m, Error e, HasRqData m) => HasRqData (ErrorT e m) where
    askRqEnv      = lift askRqEnv
    localRqEnv f  = mapErrorT (localRqEnv f)
    rqDataError e = lift (rqDataError e)
instance (Monad m, HasRqData m) => HasRqData (ExceptT e m) where
    askRqEnv      = lift askRqEnv
    localRqEnv f  = mapExceptT (localRqEnv f)
    rqDataError e = lift (rqDataError e)
runRqData :: RqData a -> RqEnv -> Either [String] a
runRqData rqData rqEnv =
    either (Left . unErrors) Right $ runReaderError (unRqData rqData) rqEnv
mapRqData :: (Either (Errors String) a -> Either (Errors String) b) -> RqData a -> RqData b
mapRqData f m = RqData $ ReaderError $ mapReaderT f (unReaderError (unRqData m))
unsafeReadRq :: (Read a) =>
          String 
       -> String 
       -> Either String a 
unsafeReadRq key val =
    case reads val of
      [(a,[])] -> Right a
      _        -> Left $ "readRq failed while parsing key: " ++ key ++ " which has the value: " ++ val
readRq :: (FromReqURI a) =>
          String 
       -> String 
       -> Either String a 
readRq key val =
    case fromReqURI val of
      (Just a) -> Right a
      _        -> Left $ "readRq failed while parsing key: " ++ key ++ " which has the value: " ++ val
checkRq :: (Monad m, HasRqData m) => m a -> (a -> Either String b) -> m b
checkRq rq f =
    do a <- rq
       case f a of
         (Left e)  -> rqDataError (strMsg e)
         (Right b) -> return b
checkRqM :: (Monad m, HasRqData m) => m a -> (a -> m (Either String b)) -> m b
checkRqM rq f =
    do a  <- rq
       eb <- f a
       case eb of
         (Left e)  -> rqDataError (strMsg e)
         (Right b) -> return b
class FromData a where
    fromData :: RqData a
instance (FromData a, FromData b) => FromData (a,b) where
    fromData = (,)   <$> fromData <*> fromData
instance (FromData a, FromData b, FromData c) => FromData (a,b,c) where
    fromData = (,,)  <$> fromData <*> fromData <*> fromData
instance (FromData a, FromData b, FromData c, FromData d) => FromData (a,b,c,d) where
    fromData = (,,,) <$> fromData <*> fromData <*> fromData <*> fromData
instance FromData a => FromData (Maybe a) where
    fromData = (Just <$> fromData) <|> (pure Nothing)
lookups :: (Eq a) => a -> [(a, b)] -> [b]
lookups a = map snd . filter ((a ==) . fst)
fromMaybeBody :: String -> String -> Maybe [(String, Input)] -> [(String, Input)]
fromMaybeBody funName fieldName mBody =
    case mBody of
      Nothing -> error $ funName ++ " " ++ fieldName ++ " failed because the request body has not been decoded yet. Try using 'decodeBody' to decode the body. Or the 'queryString' filter to ignore the body."
      (Just bdy) -> bdy
lookInput :: (Monad m, HasRqData m) => String -> m Input
lookInput name
    = do (query, mBody, _cookies) <- askRqEnv
         let bdy = fromMaybeBody "lookInput" name mBody
         case lookup name (query ++ bdy) of
           Just i  -> return $ i
           Nothing -> rqDataError (strMsg $ "Parameter not found: " ++ name)
lookInputs :: (Monad m, HasRqData m) => String -> m [Input]
lookInputs name
    = do (query, mBody, _cookies) <- askRqEnv
         let bdy = fromMaybeBody "lookInputs" name mBody
         return $ lookups name (query ++ bdy)
lookBS :: (Functor m, Monad m, HasRqData m) => String -> m L.ByteString
lookBS n =
    do i <- fmap inputValue (lookInput n)
       case i of
         (Left _fp) -> rqDataError $ (strMsg $ "lookBS: " ++ n ++ " is a file.")
         (Right bs) -> return bs
lookBSs :: (Functor m, Monad m, HasRqData m) => String -> m [L.ByteString]
lookBSs n =
    do is <- fmap (map inputValue) (lookInputs n)
       case partitionEithers is of
         ([], bs) -> return bs
         (_fp, _) -> rqDataError (strMsg $ "lookBSs: " ++ n ++ " is a file.")
look :: (Functor m, Monad m, HasRqData m) => String -> m String
look = fmap LU.toString . lookBS
looks :: (Functor m, Monad m, HasRqData m) => String -> m [String]
looks = fmap (map LU.toString) . lookBSs
lookText :: (Functor m, Monad m, HasRqData m) => String -> m LazyText.Text
lookText = fmap LazyText.decodeUtf8 . lookBS
lookText' :: (Functor m, Monad m, HasRqData m) => String -> m Text
lookText' = fmap LazyText.toStrict . lookText
lookTexts :: (Functor m, Monad m, HasRqData m) => String -> m [LazyText.Text]
lookTexts = fmap (map LazyText.decodeUtf8) . lookBSs
lookTexts' :: (Functor m, Monad m, HasRqData m) => String -> m [Text]
lookTexts' = fmap (map LazyText.toStrict) . lookTexts
lookCookie :: (Monad m, HasRqData m) => String -> m Cookie
lookCookie name
    = do (_query,_body, cookies) <- askRqEnv
         case lookup (map toLower name) cookies of 
           Nothing -> rqDataError $ strMsg $ "lookCookie: cookie not found: " ++ name
           Just c  -> return c
lookCookieValue :: (Functor m, Monad m, HasRqData m) => String -> m String
lookCookieValue = fmap cookieValue . lookCookie
readCookieValue :: (Functor m, Monad m, HasRqData m, FromReqURI a) => String -> m a
readCookieValue name = fmap cookieValue (lookCookie name) `checkRq` (readRq name)
lookRead :: (Functor m, Monad m, HasRqData m, FromReqURI a) => String -> m a
lookRead name = look name `checkRq` (readRq name)
lookReads :: (Functor m, Monad m, HasRqData m, FromReqURI a) => String -> m [a]
lookReads name =
    do vals <- looks name
       mapM (\v -> (return v) `checkRq` (readRq name)) vals
lookFile :: (Monad m, HasRqData m) =>
            String 
         -> m (FilePath, FilePath, ContentType) 
lookFile n =
    do i <- lookInput n
       case inputValue i of
         (Right _) -> rqDataError $ (strMsg $ "lookFile: " ++ n ++ " was found but is not a file.")
         (Left fp) -> return (fp, fromJust $ inputFilename i, inputContentType i)
lookPairs :: (Monad m, HasRqData m) => m [(String, Either FilePath String)]
lookPairs =
    do (query, mBody, _cookies) <- askRqEnv
       let bdy = fromMaybeBody "lookPairs" "" mBody
       return $ map (\(n,vbs)->(n, (\e -> case e of Left fp -> Left fp ; Right bs -> Right (LU.toString bs)) $ inputValue vbs)) (query ++ bdy)
lookPairsBS :: (Monad m, HasRqData m) => m [(String, Either FilePath L.ByteString)]
lookPairsBS =
    do (query, mBody, _cookies) <- askRqEnv
       let bdy = fromMaybeBody "lookPairsBS" "" mBody
       return $ map (\(n,vbs) -> (n, inputValue vbs)) (query ++ bdy)
decodeBody :: (ServerMonad m, MonadPlus m, MonadIO m, FilterMonad Response m, WebMonad Response m) => BodyPolicy -> m ()
decodeBody bp =
    do rq <- askRq
       (_, me) <- bodyInput bp rq
       case me of
         Nothing -> return ()
         Just e  -> escape $ requestEntityTooLarge (toResponse e) 
getDataFn :: (HasRqData m, ServerMonad m) =>
             RqData a 
          -> m (Either [String] a) 
getDataFn rqData =
    do rqEnv <- askRqEnv
       return (runRqData rqData rqEnv)
withDataFn :: (HasRqData m, MonadPlus m, ServerMonad m) => RqData a -> (a -> m r) -> m r
withDataFn fn handle = getDataFn fn >>= either (const mzero) handle
getData :: (HasRqData m, ServerMonad m, FromData a) => m (Either [String] a)
getData = getDataFn fromData
withData :: (HasRqData m, FromData a, MonadPlus m, ServerMonad m) => (a -> m r) -> m r
withData = withDataFn fromData
body :: (HasRqData m) => m a -> m a
body rqData = localRqEnv f rqData
    where
      f (_query, bdy, _cookies) = ([], bdy, [])
queryString ::  (HasRqData m) => m a -> m a
queryString rqData = localRqEnv f rqData
    where
      f (query, _body, _cookies) = (query, Just [], [])
bytestring :: (HasRqData m) => m a -> m a
bytestring rqData = localRqEnv f rqData
    where
      f (query, bdy, cookies) = (filter bsf query, filter bsf <$> bdy, cookies)
      bsf (_, i) =
          case inputValue i of
            (Left  _fp) -> False
            (Right _bs) -> True