    {- arch-tag: Generic Server Support
    Copyright (c) 2004-2011 John Goerzen <jgoerzen@complete.org>

    All rights reserved.

    For license and copyright information, see the file LICENSE
    -}

    {- |
       Module     : Network.SocketServer
       Copyright  : Copyright (C) 2004-2011 John Goerzen
       License    : BSD3

       Maintainer : John Goerzen <jgoerzen@complete.org>
       Stability  : experimental
       Portability: systems with networking

    This module provides an infrastructure to simplify server design.

    Written by John Goerzen, jgoerzen\@complete.org

    Please note: this module is designed to work with TCP, UDP, and Unix domain
    sockets, but only TCP sockets have been tested to date.

    This module is presently under-documented.  For an example of usage, please
    see the description of "Network.FTP.Server".
    -}

    module Network.SocketServer(-- * Generic Options and Types
                                         InetServerOptions(..),
                                         simpleTCPOptions,
                                         SocketServer(..),
                                         HandlerT,
                                         -- * TCP server convenient setup
                                         serveTCPforever,
                                         -- * Lower-Level Processing
                                         setupSocketServer,
                                         handleOne,
                                         serveForever,
                                         closeSocketServer,
                                         -- * Combinators
                                         loggingHandler,
                                         threadedHandler,
                                         handleHandler
                                        )
    where
    import Network.Socket
    import Network.BSD
    import Network.Utils
    import Control.Concurrent
    import System.IO
    import qualified System.Log.Logger

    {- | Options for your server. -}
    data InetServerOptions  = InetServerOptions {listenQueueSize :: Int,
                                                 portNumber :: PortNumber,
                                                 interface :: HostAddress,
                                                 reuse :: Bool,
                                                 family :: Family,
                                                 sockType :: SocketType,
                                                 protoStr :: String
                                                }
        deriving (Eq, Show)

    {- | The main handler type.

    The first parameter is the socket itself.

    The second is the address of the remote endpoint.

    The third is the address of the local endpoint.
    -}
    type HandlerT = Socket -> SockAddr -> SockAddr -> IO ()

    {- | Get Default options.  You can always modify it later. -}
    simpleTCPOptions :: Int                -- ^ Port Number
                     -> InetServerOptions
    simpleTCPOptions p = InetServerOptions {listenQueueSize = 5,
                                            portNumber = (fromIntegral p),
                                            interface = iNADDR_ANY,
                                            reuse = False,
                                            family = AF_INET,
                                            sockType = Stream,
                                            protoStr = "tcp"
                                           }

    data SocketServer = SocketServer {optionsSS :: InetServerOptions,
                                      sockSS :: Socket}
                      deriving (Eq, Show)

    {- | Takes some options and sets up the 'SocketServer'.  I will bind
    and begin listening, but will not accept any connections itself. -}
    setupSocketServer :: InetServerOptions -> IO SocketServer
    setupSocketServer opts =
        do proto <- getProtocolNumber (protoStr opts)
           s <- socket (family opts) (sockType opts) proto
           setSocketOption s ReuseAddr (case (reuse opts) of
                                        True -> 1
                                        False -> 0)
           bindSocket s (SockAddrInet (portNumber opts)
                         (interface opts))
           listen s (listenQueueSize opts)
           return $ SocketServer {optionsSS = opts, sockSS = s}

    {- | Close the socket server.  Does not terminate active
    handlers, if any. -}
    closeSocketServer :: SocketServer -> IO ()
    closeSocketServer ss =
        sClose (sockSS ss)

    {- | Handle one incoming request from the given 'SocketServer'. -}
    handleOne :: SocketServer -> HandlerT -> IO ()
    handleOne ss func =
        let opts = (optionsSS ss)
            in    do a <- accept (sockSS ss)
                     localaddr <- getSocketName (fst a)
                     func (fst a) (snd a) localaddr

    {- | Handle all incoming requests from the given 'SocketServer'. -}
    serveForever :: SocketServer -> HandlerT -> IO ()
    serveForever ss func =
        sequence_ (repeat (handleOne ss func))

    {- | Convenience function to completely set up a TCP
    'SocketServer' and handle all incoming requests.

    This function is literally this:

    >serveTCPforever options func =
    >    do sockserv <- setupSocketServer options
    >       serveForever sockserv func
     -}
    serveTCPforever :: InetServerOptions     -- ^ Server options
                    -> HandlerT              -- ^ Handler function
                    -> IO ()
    serveTCPforever options func =
        do sockserv <- setupSocketServer options
           serveForever sockserv func

    ----------------------------------------------------------------------
    -- Combinators
    ----------------------------------------------------------------------

    {- | Log each incoming connection using the interface in
    "System.Log.Logger".

    Log when the incoming connection disconnects.

    Also, log any failures that may occur in the child handler. -}

    loggingHandler :: String                -- ^ Name of logger to use
                   -> System.Log.Logger.Priority -- ^ Priority of logged messages
                   -> HandlerT              -- ^ Handler to call after logging
                   -> HandlerT              -- ^ Resulting handler
    loggingHandler hname prio nexth socket r_sockaddr l_sockaddr =
        do sockStr <- showSockAddr r_sockaddr
           System.Log.Logger.logM hname prio
                       ("Received connection from " ++ sockStr)
           System.Log.Logger.traplogging hname
                   System.Log.Logger.WARNING "" (nexth socket r_sockaddr
                                                       l_sockaddr)
           System.Log.Logger.logM hname prio
                       ("Connection " ++ sockStr ++ " disconnected")

    -- | Handle each incoming connection in its own thread to
    -- make the server multi-tasking.
    threadedHandler :: HandlerT             -- ^ Handler to call in the new thread
                    -> HandlerT             -- ^ Resulting handler
    threadedHandler nexth socket r_sockaddr l_sockaddr=
        do forkIO (nexth socket r_sockaddr l_sockaddr)
           return ()

    {- | Give your handler function a Handle instead of a Socket.

    The Handle will be opened with ReadWriteMode (you use one handle for both
    directions of the Socket).  Also, it will be initialized with LineBuffering.

    Unlike other handlers, the handle will be closed when the function returns.
    Therefore, if you are doing threading, you should to it before you call this
    handler.
    -}
    handleHandler :: (Handle -> SockAddr -> SockAddr -> IO ())      -- ^ Handler to call
                  -> HandlerT
    handleHandler func socket r_sockaddr l_sockaddr =
        do h <- socketToHandle socket ReadWriteMode
           hSetBuffering h LineBuffering
           func h r_sockaddr l_sockaddr
           hClose h