{-# LANGUAGE CPP                        #-}
{-# LANGUAGE DeriveFunctor              #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE GADTs                      #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures             #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE RankNTypes                 #-}
{-# LANGUAGE RecordWildCards            #-}
{-# LANGUAGE ScopedTypeVariables        #-}
{-# LANGUAGE TypeFamilies               #-}
{-# LANGUAGE TypeOperators              #-}
{-# LANGUAGE UndecidableInstances       #-}
module Servant.Server.Internal.RoutingApplication where
import           Control.Applicative                (Alternative (..),
                                                     Applicative (..), (<$>))
import           Control.Monad                      (ap, liftM)
import           Control.Monad.IO.Class             (MonadIO, liftIO)
import           Control.Monad.Trans.Class
import qualified Data.ByteString                    as B
import qualified Data.ByteString.Builder            as Builder
import qualified Data.ByteString.Lazy               as BL
import           Data.CaseInsensitive               (CI)
import qualified Data.List                          as L
import           Data.Proxy                         (Proxy (..))
import           Network.HTTP.Types                 (Status (..))
import           Snap.Core
import           Snap.Internal.Http.Types           (setResponseBody)
import qualified System.IO.Streams                  as Streams
import           Servant.Server.Internal.ServantErr
import           Servant.Server.Internal.SnapShims
type RoutingApplication m =
     Request 
  -> (RouteResult Response -> m Response) -> m Response
data RouteResult a =
    Fail ServantErr           
                              
  | FailFatal !ServantErr     
  | Route !a
  deriving (Eq, Show, Read, Functor)
toApplication :: forall m. MonadSnap m => RoutingApplication m -> Application m
toApplication ra request respond = do
  r <- ra request routingRespond
  respond r
   where
     routingRespond (Fail err) = case errHTTPCode err of
       404 -> pass
       _   -> respond $ responseServantErr err
     routingRespond (FailFatal err) = respond $ responseServantErr err
     routingRespond (Route v) = respond v
responseLBS :: Status -> [(CI B.ByteString, B.ByteString)] -> BL.ByteString -> Response
responseLBS (Status code msg) hs body =
    setResponseStatus code msg
    . (\r -> L.foldl' (\r' (h,h') -> addHeader h h' r') r hs)
    . setResponseBody (\out -> do
       Streams.write (Just $ Builder.lazyByteString body) out
       return out)
    $ emptyResponse
runAction :: MonadSnap m
          => Delayed m env (m a)
          -> env
          -> Request
          -> (RouteResult Response -> m r)
          -> (a -> RouteResult Response)
          -> m r
runAction action env req respond k = do
  runDelayed action env req >>= go  >>= respond
  where
    go (Fail e) = return $ Fail e
    go (FailFatal e) = return $ FailFatal e
    go (Route a) = do
      e <- a
      return $ k e
data Delayed m env c where
  Delayed :: { capturesD :: env -> DelayedM m captures
             , methodD   :: DelayedM m ()
             , authD     :: DelayedM m auth
             , acceptD   :: DelayedM m ()
             , contentD  :: DelayedM m contentType
             , paramsD   :: DelayedM m params
             , headersD  :: DelayedM m headers
             , bodyD     :: contentType -> DelayedM m body
             , serverD   :: captures -> params -> headers -> auth -> body -> Request -> RouteResult c
             } -> Delayed m env c
instance Functor (Delayed m env) where
  fmap f Delayed{..} =
    Delayed
      { serverD = \ c p h a b req -> f <$> serverD c p h a b req
      , ..
      }
newtype DelayedM m a = DelayedM { runDelayedM :: Request -> m (RouteResult a) }
instance Monad m => Functor (DelayedM m) where
  fmap = liftM
instance Monad m => Applicative (DelayedM m) where
  pure = return
  (<*>) = ap
instance Monad m => Monad (DelayedM m) where
  return x = DelayedM (const $ return (Route x))
  DelayedM m >>= f =
    DelayedM $ \ req -> do
      r <- m req
      case r of
        Fail      e -> return $ Fail e
        FailFatal e -> return $ FailFatal e
        Route     a -> runDelayedM (f a) req
instance MonadIO m => MonadIO (DelayedM m) where
  liftIO m = DelayedM (const . liftIO $ Route <$> m)
instance (Monad m, MonadSnap m) => Alternative (DelayedM m) where
  empty   = DelayedM $ \_ -> return (Fail err404)
  a <|> b = DelayedM $ \req -> do
    respA <- runDelayedM a req
    case respA of
      Route a' -> return $ Route a'
      _        -> runDelayedM b req
instance MonadTrans DelayedM where
  lift f = DelayedM $ \_ -> do
    a <- f
    return $ Route a
emptyDelayed :: Monad m => Proxy (m :: * -> *) -> RouteResult a -> Delayed m env a
emptyDelayed (Proxy :: Proxy m) result =
  Delayed (const r) r r r r r r (const r) (\ _ _ _ _ _ _ -> result)
  where
    r :: DelayedM m ()
    r = return ()
delayedFail :: Monad m => ServantErr -> DelayedM m a
delayedFail err = DelayedM (const $ return $ Fail err)
delayedFailFatal :: Monad m => ServantErr -> DelayedM m a
delayedFailFatal err = DelayedM (const $ return $ FailFatal err)
withRequest :: (Request -> DelayedM m a) -> DelayedM m a
withRequest f = DelayedM (\ req -> runDelayedM (f req) req)
addCapture :: forall env a b captured m. Monad m => Delayed m env (a -> b)
           -> (captured -> DelayedM m a)
           -> Delayed m (captured, env) b
addCapture Delayed{..} new =
  Delayed
    { capturesD = \ (txt, env) -> (,) <$> capturesD env <*> new txt
    , serverD   = \ (x, v) p h a b req -> ($ v) <$> serverD x p h a b req
    , ..
    } 
addParameterCheck :: Monad m
               => Delayed m env (a -> b)
               -> DelayedM m a
               -> Delayed m env b
addParameterCheck Delayed {..} new =
  Delayed
    { paramsD = (,) <$> paramsD <*> new
    , serverD = \c (p, pNew) h a b req -> ($ pNew) <$> serverD c p h a b req
    , ..
    }
addHeaderCheck :: Monad m
               => Delayed m env (a -> b)
               -> DelayedM m a
               -> Delayed m env b
addHeaderCheck Delayed {..} new =
  Delayed
    { headersD = (,) <$> headersD <*> new
    , serverD = \c p (h, hNew) a b req -> ($ hNew) <$> serverD c p h a b req
    , ..
    }
addMethodCheck :: Monad m
               => Delayed m env a
               -> DelayedM m ()
               -> Delayed m env a
addMethodCheck Delayed{..} new =
  Delayed
    { methodD = methodD <* new
    , ..
    } 
addAuthCheck :: Monad m
             => Delayed m env (a -> b)
             -> DelayedM m a
             -> Delayed m env b
addAuthCheck Delayed{..} new =
  Delayed
    { authD   = (,) <$> authD <*> new
    , serverD = \ c p h (y, v) b req -> ($ v) <$> serverD c p h y b req
    , ..
    } 
addBodyCheck :: Monad m
             => Delayed m env (a -> b)
             -> DelayedM m c        
             -> (c -> DelayedM m a) 
             -> Delayed m env b
addBodyCheck Delayed{..} newContentD newBodyD =
  Delayed
    { contentD = (,) <$> contentD <*> newContentD
    , bodyD   = \(content, c) -> (,) <$> bodyD content <*> newBodyD c
    , serverD = \ c p h a (z, v) req -> ($ v) <$> serverD c p h a z req
    , ..
    } 
addAcceptCheck :: Monad m
               => Delayed m env a
               -> DelayedM m ()
               -> Delayed m env a
addAcceptCheck Delayed{..} new =
  Delayed
    { acceptD = acceptD *> new
    , ..
    } 
passToServer :: Delayed m env (a -> b) -> (Request -> a) -> Delayed m env b
passToServer Delayed{..} x =
  Delayed
    { serverD = \ c p h a b req -> ($ x req) <$> serverD c p h a b req
    , ..
    } 
runDelayed :: Monad m
           => Delayed m env a
           -> env
           -> Request
           -> m (RouteResult a)
runDelayed Delayed{..} env = runDelayedM $ do
  c <- capturesD env
  methodD
  a <- authD
  acceptD
  content <- contentD
  p <- paramsD  
  h <- headersD
  b <- bodyD content
  DelayedM (\ req -> return $ serverD c p h a b req)