{-# LANGUAGE OverloadedStrings #-}

-- | 
--
-- > app req = do
-- >     liftIO $ somethingImportant -- like threadDelay 100000000
-- >     LBSResponse status200 [("Content-Type", "text/plain")] "Finished!"
-- >
-- > main = do
-- >     gs <- initGraceful
-- >
-- >     forkIO $ run 8000 (graceful gs app)
-- >
-- >     waitForTermination >> gracefulShutdown 15 gs
-- >     putStrLn "Bye!"

module Network.Wai.Graceful
    ( initGraceful, Graceful(..)
    , graceful
    , gracefulShutdown
    , waitForTermination
    ) where

import Control.Applicative
import Control.Monad.Trans (liftIO)
import Control.Monad.Trans.Resource (register)
import Control.Concurrent (forkIO, threadDelay)
import Control.Concurrent.MVar
import Network.Wai (Application, Response, responseLBS)
import Network.HTTP.Types (status503)

import System.Posix.Signals -- (installHandler, sigTERM)

data Graceful = Graceful
    { activeConnections :: MVar Int
    , shutdownTrigger :: MVar Bool
    }

initGraceful :: IO Graceful
initGraceful = Graceful <$> newMVar 0
                        <*> newMVar False

-- | WAI Middleware that keeps track of active connections
--   and blocks further requests with HTTP503.
graceful :: Graceful -> Application -> Application
graceful (Graceful active shutdown) app req = do
    halt <- liftIO $ readMVar shutdown
    if halt
        then return shuttingDown
        else do
            liftIO $ modifyMVar_ active (\c -> return $! c + 1)
            register $ liftIO $ modifyMVar_ active (\c -> return $! c - 1)
            app req

    where
        shuttingDown =
            responseLBS
                status503
                [("Content-Type", "text/plain")]
                "Shutdown in progress..."

-- | Trigger shutdown and monitor progress.
gracefulShutdown :: Int      -- ^ Timeout in seconds.
                 -> Graceful
                 -> IO ()
gracefulShutdown seconds (Graceful active shutdown) = do
    modifyMVar_ shutdown (const $ return True)

    done <- newEmptyMVar

    let waitActive = do count <- readMVar active
                        if count > 0
                            then threadDelay 100000 >> waitActive
                            else putMVar done ()

    let timeout = threadDelay (1000000 * seconds) >> putMVar done ()

    forkIO waitActive
    forkIO timeout
    takeMVar done

-- | Wait for TERM signal.
waitForTermination :: IO ()
waitForTermination = do
    mv <- newEmptyMVar
    installHandler softwareTermination (CatchOnce (putMVar mv ())) Nothing
    takeMVar mv