module Web.Wheb.WhebT
  (
  
  
    getApp
  , getWithApp
  
  , getHandlerState
  , putHandlerState
  , modifyHandlerState
  , modifyHandlerState'
  
  
  , setHeader
  , setRawHeader
  , html
  , text
  , file
  , builder
  , redirect
  
  
  , getSetting
  , getSetting'
  , getSetting''
  , getSettings
  
  
  , getRouteParams
  , getRouteParam
  , getRoute
  , getRoute'
  , getRawRoute
  
  
  , getRequest
  , getRequestHeader
  , getWithRequest
  , getQueryParams
  , getPOSTParam
  , getPOSTParams
  , getRawPOST
  
  
  , runWhebServer
  , runWhebServerT
  , runRawHandler
  , runRawHandlerT
  ) where
import Blaze.ByteString.Builder (Builder)
import Control.Concurrent (forkIO, threadDelay)
import Control.Concurrent.STM (atomically, readTVar, newTVarIO, writeTVar)
import Control.Monad.Error (liftM, MonadError(throwError), MonadIO, void)
import Control.Monad.Reader (MonadReader(ask))
import Control.Monad.State (modify, MonadState(get))
import qualified Data.ByteString.Lazy as LBS (ByteString, empty)
import Data.CaseInsensitive (mk)
import Data.List (find)
import qualified Data.Map as M (insert, lookup)
import Data.Maybe (fromMaybe)
import qualified Data.Text.Lazy as T (pack, empty, Text)
import Data.Typeable (cast, Typeable)
import Network.HTTP.Types.Header (Header)
import Network.HTTP.Types.Status (serviceUnavailable503, status200, status302)
import Network.HTTP.Types.URI (Query)
import Network.Wai (defaultRequest, Request(queryString, requestHeaders), responseLBS)
import Network.Wai.Handler.Warp as W (runSettings, setPort)
import Network.Wai.Parse (File, Param)
import System.Posix.Signals (Handler(Catch), installHandler, sigINT, sigTSTP, sigTERM)
import Web.Wheb.Internal (optsToApplication, runDebugHandler)
import Web.Wheb.Routes (generateUrl, getParam)
import Web.Wheb.Types (CSettings, EResponse, HandlerData(HandlerData, globalCtx, globalSettings, postData, request, routeParams), 
                       HandlerResponse(HandlerResponse), InternalState(InternalState, reqState, respHeaders), 
                       Route(..), RouteParamList, SettingsValue(..), 
                       UrlBuildError(UrlNameNotFound), WhebError(RouteParamDoesNotExist, URLError), 
                       WhebFile(WhebFile), WhebHandlerT, WhebOptions(..), WhebT(WhebT))
import Web.Wheb.Utils (lazyTextToSBS, sbsToLazyText)
getApp :: Monad m => WhebT g s m g
getApp = WhebT $ liftM globalCtx ask
getWithApp :: Monad m => (g -> a) -> WhebT g s m a
getWithApp = flip liftM getApp
getHandlerState :: Monad m => WhebT g s m s
getHandlerState = WhebT $ liftM reqState get
putHandlerState :: Monad m => s -> WhebT g s m ()
putHandlerState s = WhebT $ modify (\is -> is {reqState = s})
modifyHandlerState :: Monad m => (s -> s) -> WhebT g s m s
modifyHandlerState f = do
    s <- liftM f getHandlerState
    putHandlerState s
    return s
modifyHandlerState' :: Monad m => (s -> s) -> WhebT g s m ()
modifyHandlerState' f = modifyHandlerState f >> (return ())
getSetting :: Monad m => T.Text -> WhebT g s m (Maybe T.Text)
getSetting = getSetting'
getSetting' :: (Monad m, Typeable a) => T.Text -> WhebT g s m (Maybe a)
getSetting' k = liftM (\cs -> (M.lookup k cs) >>= unwrap) getSettings
    where unwrap :: Typeable a => SettingsValue -> Maybe a
          unwrap (MkVal a) = cast a
getSetting'' :: (Monad m, Typeable a) => T.Text -> a -> WhebT g s m a
getSetting'' k d = liftM (fromMaybe d) (getSetting' k)
getSettings :: Monad m => WhebT g s m CSettings
getSettings = WhebT $ liftM (runTimeSettings . globalSettings) ask
getRouteParams :: Monad m => WhebT g s m RouteParamList
getRouteParams = WhebT $ liftM routeParams ask
getRouteParam :: (Typeable a, Monad m) => T.Text -> WhebT g s m a
getRouteParam t = do
  p <- getRouteParam' t
  maybe (throwError RouteParamDoesNotExist) return p
getRouteParam' :: (Typeable a, Monad m) => T.Text -> WhebT g s m (Maybe a)
getRouteParam' t = liftM (getParam t) getRouteParams
getRoute :: Monad m => T.Text -> RouteParamList ->  WhebT g s m T.Text
getRoute name l = do
        res <- getRoute' name l
        case res of
            Right t  -> return t
            Left err -> throwError $ URLError name err
getRoute' :: Monad m => T.Text -> 
             RouteParamList -> 
             WhebT g s m (Either UrlBuildError T.Text)
getRoute' n l = liftM buildRoute (getRawRoute n l)
    where buildRoute (Just (Route {..})) = generateUrl routeParser l
          buildRoute (Nothing)           = Left UrlNameNotFound
getRawRoute :: Monad m => T.Text -> 
             RouteParamList -> 
             WhebT g s m (Maybe (Route g s m))
getRawRoute n _ = WhebT $ liftM f ask  
    where findRoute (Route {..}) = fromMaybe False (fmap (==n) routeName)  
          f = ((find findRoute) . appRoutes . globalSettings)    
getRequest :: Monad m => WhebT g s m Request
getRequest = WhebT $ liftM request ask
getWithRequest :: Monad m => (Request -> a) -> WhebT g s m a
getWithRequest = flip liftM getRequest
getRawPOST :: MonadIO m => WhebT g s m ([Param], [File LBS.ByteString])
getRawPOST = WhebT $ liftM postData ask
getPOSTParams :: MonadIO m => WhebT g s m [(T.Text, T.Text)]
getPOSTParams = liftM (fmap f . fst) getRawPOST
  where f (a, b) = (sbsToLazyText a, sbsToLazyText b)
getPOSTParam :: MonadIO m => T.Text -> WhebT g s m (Maybe T.Text)
getPOSTParam k = liftM (lookup k) getPOSTParams 
getQueryParams :: Monad m => WhebT g s m Query
getQueryParams = getWithRequest queryString
getRequestHeader :: Monad m => T.Text -> WhebT g s m (Maybe T.Text)
getRequestHeader k = getRequest >>= f
  where hk = mk $ lazyTextToSBS k
        f = (return . (fmap sbsToLazyText) . (lookup hk) . requestHeaders)
setRawHeader :: Monad m => Header -> WhebT g s m ()
setRawHeader (hn, hc) = WhebT $ modify insertHeader 
    where insertHeader is@(InternalState {..}) = 
            is { respHeaders = M.insert hn hc respHeaders }
 
setHeader :: Monad m => T.Text -> T.Text -> WhebT g s m ()
setHeader hn hc = setRawHeader (mk $ lazyTextToSBS hn, lazyTextToSBS hc)
file :: Monad m => T.Text -> T.Text -> WhebHandlerT g s m
file fp ct = do
    setHeader (T.pack "Content-Type") (ct) 
    return $ HandlerResponse status200 (WhebFile fp)
html :: Monad m => T.Text -> WhebHandlerT g s m
html c = do
    setHeader (T.pack "Content-Type") (T.pack "text/html") 
    return $ HandlerResponse status200 c
text :: Monad m => T.Text -> WhebHandlerT g s m
text c = do
    setHeader (T.pack "Content-Type") (T.pack "text/plain") 
    return $ HandlerResponse status200 c
builder :: Monad m => T.Text -> Builder -> WhebHandlerT g s m
builder c b = do
    setHeader (T.pack "Content-Type") c 
    return $ HandlerResponse status200 b
redirect :: Monad m => T.Text -> WhebHandlerT g s m
redirect c = do
    setHeader (T.pack "Location") c
    return $ HandlerResponse status302 T.empty
    
runRawHandlerT :: WhebOptions g s m ->
             (m (Either WhebError a) -> IO (Either WhebError a)) ->
             Request ->
             WhebT g s m a ->
             IO (Either WhebError a)
runRawHandlerT opts@(WhebOptions {..}) runIO r h = 
    runIO $ runDebugHandler opts h baseData
    where baseData = HandlerData startingCtx r ([], []) [] opts
runRawHandler :: WhebOptions g s IO -> 
              WhebT g s IO a ->
              IO (Either WhebError a)
runRawHandler opts h = runRawHandlerT opts id defaultRequest h
runWhebServerT :: (forall a . m a -> IO a) ->
                  WhebOptions g s m ->
                  IO ()
runWhebServerT runIO opts@(WhebOptions {..}) = do
    putStrLn $ "Now running on port " ++ (show $ port)
    forceTVar <- newTVarIO False
    installHandler sigINT catchSig Nothing
    installHandler sigTERM catchSig Nothing
    installHandler sigTSTP (Catch (atomically $ writeTVar forceTVar True >> writeTVar shutdownTVar True)) Nothing
    forkIO $ runSettings rtSettings $
        gracefulExit $
        waiStack $
        optsToApplication opts runIO
    loop
    putStrLn $ "Waiting for connections to close..."
    waitForConnections forceTVar
    putStrLn $ "Shutting down server..."
    sequence_ cleanupActions
  where catchSig = (Catch (atomically $ writeTVar shutdownTVar True))
        loop = do
          shutDown <- atomically $ readTVar shutdownTVar
          if shutDown then return () else (threadDelay 100000) >> loop
        gracefulExit app r respond = do
          isExit <- atomically $ readTVar shutdownTVar
          case isExit of
              False -> app r respond
              True  -> respond $ responseLBS serviceUnavailable503 [] LBS.empty
        waitForConnections forceTVar = do
          openConnections <- atomically $ readTVar activeConnections
          force <- atomically $ readTVar forceTVar
          if (openConnections == 0 || force)
            then return ()
            else waitForConnections forceTVar
        port = fromMaybe 3000 $ 
          (M.lookup (T.pack "port") runTimeSettings) >>= (\(MkVal m) -> cast m)
        rtSettings = W.setPort port warpSettings
runWhebServer :: (WhebOptions g s IO) -> IO ()
runWhebServer = runWhebServerT id