{-# LANGUAGE BangPatterns        #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Main where

import           Control.Applicative               ((<$>))
import           Control.Concurrent                (MVar, ThreadId, forkIOWithUnmask, killThread, newEmptyMVar, putMVar, takeMVar)
import           Control.Exception                 (SomeException, bracketOnError, evaluate)
import qualified Control.Exception                 as E
import           Data.ByteString.Builder           (byteString, toLazyByteString)
import qualified Data.ByteString.Char8             as S
import qualified Data.ByteString.Lazy.Char8        as L
import qualified Network.Socket                    as N
import           Snap.Core
import           System.Environment                (getArgs)
import qualified System.IO.Streams                 as Streams
------------------------------------------------------------------------------
import           Snap.Internal.Http.Server.Session (httpAcceptLoop, snapToServerHandler)
import qualified Snap.Internal.Http.Server.Socket  as Sock
import qualified Snap.Internal.Http.Server.Types   as Types

------------------------------------------------------------------------------
-- | Returns the thread the server is running in as well as the port it is
-- listening on.
startTestSocketServer :: Int -> Snap a -> IO (ThreadId, MVar ())
startTestSocketServer portNum userHandler =
    bracketOnError getSock cleanup forkServer
  where
    getSock = Sock.bindSocket "127.0.0.1" (fromIntegral portNum)

    forkServer sock = do
        port <- fromIntegral <$> N.socketPort sock
        putStrLn $ "starting on " ++ show (port :: Int)
        let scfg = emptyServerConfig
        mv <- newEmptyMVar
        tid <- forkIOWithUnmask $ \unmask -> do
            putStrLn "server start"
            (unmask $ httpAcceptLoop (snapToServerHandler userHandler)
                                     scfg
                                     (Sock.httpAcceptFunc sock))
                  `E.finally` putMVar mv ()
        return (tid, mv)

    cleanup = N.close

    logAccess _ _ _             = return ()
    _logError !e                = L.putStrLn $ toLazyByteString e
    onStart _                   = return ()
    onParse _ _                 = return ()
    onUserHandlerFinished _ _ _ = return ()
    onDataFinished _ _ _        = return ()
    onExceptionHook _ _         = return ()
    onEscape _                  = return ()

    emptyServerConfig = Types.ServerConfig logAccess
                                           _logError
                                           onStart
                                           onParse
                                           onUserHandlerFinished
                                           onDataFinished
                                           onExceptionHook
                                           onEscape
                                           "localhost"
                                           6
                                           False
                                           1


main :: IO ()
main = do
    portNum <- (((read . head) <$> getArgs) >>= evaluate)
               `E.catch` \(_::SomeException) -> return 3000
    (tid, mv) <- startTestSocketServer portNum $ do
                   modifyResponse $ setContentLength 4 . setResponseBody output
    takeMVar mv
    killThread tid

  where
    output os = Streams.write (Just $ fromByteString "pong") os >> return os
