{-# LANGUAGE RecordWildCards, RankNTypes #-}

module Web.Wheb.Internal where

import qualified Data.ByteString.Char8 as B
import Control.Monad (void)
import Control.Monad.Except (ExceptT, runExceptT)
import Control.Monad.Reader (ReaderT(runReaderT))
import Control.Monad.State (evalStateT, StateT(runStateT))
import qualified Data.Map as M (toList)
import qualified Data.Text.Lazy as T (fromStrict, Text, toStrict, pack)
import Network.HTTP.Types.Method (parseMethod, StdMethod(GET))
import Network.Wai (Application, Request(pathInfo, requestMethod), Response)
import Network.Wai.Parse (lbsBackEnd, parseRequestBody)
import Network.Wai.Handler.WebSockets (websocketsOr)
import qualified Network.WebSockets as W
import Web.Wheb.Routes (findUrlMatch, findSiteMatch, findSocketMatch)
import Web.Wheb.Types (EResponse, HandlerData(HandlerData, postData, routeParams),
                       HandlerResponse(HandlerResponse), InternalState(..), 
                       WhebContent(toResponse), WhebError(Error404), WhebHandlerT, 
                       WhebMiddleware, WhebOptions(..), WhebT(runWhebT))
import Web.Wheb.Utils (uhOh)

-- * Converting to WAI application
-- | Convert 'WhebOptions' to 'Application'                        
optsToApplication :: WhebOptions g s m ->
                     (forall a. m a -> IO a) ->
optsToApplication opts@(WhebOptions {..}) runIO r respond = do
  if ((length appWhebSockets) > 0)
    then websocketsOr W.defaultConnectionOptions socketHandler handleMain r respond
    else handleMain r respond

  where socketHandler pc = do
              case (findSocketMatch pathChunks appWhebSockets) of
                  Just (h, params) -> do
                      c <- W.acceptRequest pc
                      void $ runIO $ do
                            (mRes, st) <- runMiddlewares opts whebMiddlewares baseData
                            runDebugHandler (opts {startingState = st}) (h c) (baseData { routeParams = params })
                  Nothing -> W.rejectRequest pc (B.pack "No socket for path.")

        handleMain r respond = do
            pData <- parseRequestBody lbsBackEnd r
            res <- runIO $ do
                    let mwData = baseData { postData = pData }
                    (mRes, st) <- runMiddlewares opts whebMiddlewares mwData
                    case mRes of
                        Just resp -> return $ Right resp
                        Nothing -> do
                            case (findSiteMatch appSites pathChunks) of
                              Just h -> do
                                runWhebHandler opts h st mwData
                              Nothing -> do
                                  case (findUrlMatch stdMthd pathChunks appRoutes) of
                                        Just (h, params) -> do
                                            let hData = mwData { routeParams = params }
                                            runWhebHandler opts h st hData 
                                        Nothing          -> return $ Left Error404
            finished <- either handleError return res
            respond finished
        baseData   = HandlerData startingCtx r ([], []) [] opts
        pathChunks = fmap T.fromStrict $ pathInfo r
        stdMthd    = either (\_-> GET) id $ parseMethod $ requestMethod r
        runErrorHandler eh = runWhebHandler opts eh startingState baseData
        handleError err = do
          errRes <- runIO $ runErrorHandler (defaultErrorHandler err)
          either (return . (const uhOh)) return errRes

-- * Running Handlers

-- | Run all inner wheb monads to the top level.
runWhebHandler :: Monad m =>
                    WhebOptions g s m ->
                    WhebHandlerT g s m ->
                    InternalState s ->
                    HandlerData g s m ->
                    m EResponse
runWhebHandler (WhebOptions {..}) handler st hd = do
  (resp, InternalState {..}) <- flip runStateT st $ do
            flip runReaderT hd $
              runExceptT $
              runWhebT handler
  return $ fmap (convertResponse respHeaders) resp 
  where convertResponse hds (HandlerResponse status resp) =
                          toResponse status (M.toList hds) resp

-- | Same as above but returns arbitrary type for debugging.
runDebugHandler :: Monad m =>
                    WhebOptions g s m ->
                    WhebT g s m a  ->
                    HandlerData g s m ->
                    m (Either WhebError a)
runDebugHandler opts@(WhebOptions {..}) handler hd = do
  flip evalStateT startingState $ do
            flip runReaderT hd $
              runExceptT $
              runWhebT handler

-- * Running Middlewares
-- | Runs middlewares in order, stopping if one returns a response
runMiddlewares :: Monad m =>
                  WhebOptions g s m ->
                  [WhebMiddleware g s m] ->
                  HandlerData g s m ->
                  m (Maybe Response, InternalState s)
runMiddlewares opts mWs hd = loop mWs (startingState opts)
    where loop [] st = return (Nothing, st)
          loop (mw:mws) st = do
                  mwResult <-  (runWhebMiddleware opts st hd mw)
                  case mwResult of
                        (Just _, _) -> return mwResult
                        (Nothing, nst)   -> loop mws nst

runWhebMiddleware :: Monad m =>
                    WhebOptions g s m ->
                    InternalState s ->
                    HandlerData g s m ->
                    WhebMiddleware g s m ->
                    m (Maybe Response, InternalState s)
runWhebMiddleware (WhebOptions {..}) st hd mW = do
        (eresp, is@InternalState {..}) <- flip runStateT st $ do
                  flip runReaderT hd $
                    runExceptT $
                    runWhebT mW
        return $ (convertResponse respHeaders eresp, is)
  where convertResponse hds (Right (Just (HandlerResponse status resp))) =
                              Just (toResponse status (M.toList hds) resp)
        convertResponse _ _ = Nothing