module Yesod.Core.Dispatch
    ( 
      parseRoutes
    , parseRoutesNoCheck
    , parseRoutesFile
    , parseRoutesFileNoCheck
    , mkYesod
      
    , mkYesodData
    , mkYesodSubData
    , mkYesodDispatch
    , mkYesodSubDispatch
      
    , PathPiece (..)
    , PathMultiPiece (..)
    , Texts
      
    , toWaiApp
    , toWaiAppPlain
    , warp
    , warpDebug
    , warpEnv
    , mkDefaultMiddlewares
    , defaultMiddlewaresNoLogging
      
    , WaiSubsite (..)
    ) where
import Prelude hiding (exp)
import Yesod.Core.Internal.TH
import Language.Haskell.TH.Syntax (qLocation)
import Web.PathPieces
import qualified Network.Wai as W
import Data.ByteString.Lazy.Char8 ()
import Data.Text (Text, pack)
import Data.Monoid (mappend)
import qualified Data.ByteString as S
import qualified Data.ByteString.Char8 as S8
import qualified Blaze.ByteString.Builder
import Network.HTTP.Types (status301)
import Yesod.Routes.Parse
import Yesod.Core.Types
import Yesod.Core.Class.Yesod
import Yesod.Core.Class.Dispatch
import Yesod.Core.Internal.Run
import Safe (readMay)
import System.Environment (getEnvironment)
import Network.Wai.Middleware.Autohead
import Network.Wai.Middleware.AcceptOverride
import Network.Wai.Middleware.RequestLogger
import Network.Wai.Middleware.Gzip
import Network.Wai.Middleware.MethodOverride
import qualified Network.Wai.Handler.Warp
import System.Log.FastLogger
import Control.Monad.Logger
import Control.Monad (when)
import qualified Paths_yesod_core
import Data.Version (showVersion)
toWaiAppPlain :: YesodDispatch site => site -> IO W.Application
toWaiAppPlain site = do
    logger <- makeLogger site
    sb <- makeSessionBackend site
    return $ toWaiAppYre $ YesodRunnerEnv
            { yreLogger = logger
            , yreSite = site
            , yreSessionBackend = sb
            }
toWaiAppYre :: YesodDispatch site => YesodRunnerEnv site -> W.Application
toWaiAppYre yre req =
    case cleanPath site $ W.pathInfo req of
        Left pieces -> sendRedirect site pieces req
        Right pieces -> yesodDispatch yre req
            { W.pathInfo = pieces
            }
  where
    site = yreSite yre
    sendRedirect :: Yesod master => master -> [Text] -> W.Application
#if MIN_VERSION_wai(3, 0, 0)
    sendRedirect y segments' env sendResponse =
         sendResponse $ W.responseLBS status301
#else
    sendRedirect y segments' env =
         return $ W.responseLBS status301
#endif
                [ ("Content-Type", "text/plain")
                , ("Location", Blaze.ByteString.Builder.toByteString dest')
                ] "Redirecting"
      where
        dest = joinPath y (resolveApproot y env) segments' []
        dest' =
            if S.null (W.rawQueryString env)
                then dest
                else (dest `mappend`
                     Blaze.ByteString.Builder.fromByteString (W.rawQueryString env))
toWaiApp :: YesodDispatch site => site -> IO W.Application
toWaiApp site = do
    logger <- makeLogger site
    toWaiAppLogger logger site
toWaiAppLogger :: YesodDispatch site => Logger -> site -> IO W.Application
toWaiAppLogger logger site = do
    sb <- makeSessionBackend site
    let yre = YesodRunnerEnv
                { yreLogger = logger
                , yreSite = site
                , yreSessionBackend = sb
                }
    messageLoggerSource
        site
        logger
        $(qLocation >>= liftLoc)
        "yesod-core"
        LevelInfo
        (toLogStr ("Application launched" :: S.ByteString))
    middleware <- mkDefaultMiddlewares logger
    return $ middleware $ toWaiAppYre yre
warp :: YesodDispatch site => Int -> site -> IO ()
warp port site = do
    logger <- makeLogger site
    toWaiAppLogger logger site >>= Network.Wai.Handler.Warp.runSettings
        Network.Wai.Handler.Warp.defaultSettings
            { Network.Wai.Handler.Warp.settingsPort = port
            
            , Network.Wai.Handler.Warp.settingsOnException = const $ \e ->
                when (shouldLog' e) $
                messageLoggerSource
                    site
                    logger
                    $(qLocation >>= liftLoc)
                    "yesod-core"
                    LevelError
                    (toLogStr $ "Exception from Warp: " ++ show e)
            }
  where
    shouldLog' =
#if MIN_VERSION_warp(2,1,3)
        Network.Wai.Handler.Warp.defaultShouldDisplayException
#else
        const True
#endif
mkDefaultMiddlewares :: Logger -> IO W.Middleware
mkDefaultMiddlewares logger = do
    logWare <- mkRequestLogger def
#if MIN_VERSION_fast_logger(2, 0, 0)
        { destination = Network.Wai.Middleware.RequestLogger.Logger $ loggerSet logger
#else
        { destination = Logger logger
#endif
        , outputFormat = Apache FromSocket
        }
    return $ logWare . defaultMiddlewaresNoLogging
defaultMiddlewaresNoLogging :: W.Middleware
defaultMiddlewaresNoLogging = acceptOverride . autohead . gzip def . methodOverride
warpDebug :: YesodDispatch site => Int -> site -> IO ()
warpDebug = warp
warpEnv :: YesodDispatch site => site -> IO ()
warpEnv site = do
    env <- getEnvironment
    case lookup "PORT" env of
        Nothing -> error $ "warpEnv: no PORT environment variable found"
        Just portS ->
            case readMay portS of
                Nothing -> error $ "warpEnv: invalid PORT environment variable: " ++ show portS
                Just port -> warp port site