    {-# LANGUAGE RankNTypes #-}
    ------------------------------------------------------------------------------
    -- | Types internal to the implementation of the Snap HTTP server.
    module Snap.Internal.Http.Server.Types
      ( ServerConfig(..)
      , PerSessionData(..)
      , DataFinishedHook
      , EscapeSnapHook
      , ExceptionHook
      , ParseHook
      , NewRequestHook
      , UserHandlerFinishedHook

      -- * Handlers
      , SendFileHandler
      , ServerHandler
      , AcceptFunc(..)

      -- * Socket types
      , SocketConfig(..)
      ) where

    ------------------------------------------------------------------------------
    import           Control.Exception                (SomeException)
    import           Data.ByteString                  (ByteString)
    import           Data.IORef                       (IORef)
    import           Data.Word                        (Word64)
    import           Network.Socket                   (Socket)
    ------------------------------------------------------------------------------
    import           Data.ByteString.Builder          (Builder)
    import           Data.ByteString.Builder.Internal (Buffer)
    import           System.IO.Streams                (InputStream, OutputStream)
    ------------------------------------------------------------------------------
    import           Snap.Core                        (Request, Response)

    ------------------------------------------------------------------------------
    -- | The 'NewRequestHook' is called once processing for an HTTP request begins,
    -- i.e. after the connection has been accepted and we know that there's data
    -- available to read from the socket. The IORef passed to the hook initially
    -- contains a bottom value that will throw an exception if evaluated.
    type NewRequestHook hookState = PerSessionData -> IO hookState

    -- | The 'ParseHook' is called after the HTTP Request has been parsed by the
    -- server, but before the user handler starts running.
    type ParseHook hookState = IORef hookState -> Request -> IO ()

    -- | The 'UserHandlerFinishedHook' is called once the user handler has finished
    -- running, but before the data for the HTTP response starts being sent to the
    -- client.
    type UserHandlerFinishedHook hookState =
        IORef hookState -> Request -> Response -> IO ()

    -- | The 'DataFinishedHook' is called once the server has finished sending the
    -- HTTP response to the client.
    type DataFinishedHook hookState =
        IORef hookState -> Request -> Response -> IO ()

    -- | The 'ExceptionHook' is called if an exception reaches the toplevel of the
    -- server, i.e. if an exception leaks out of the user handler or if an
    -- exception is raised during the sending of the HTTP response data.
    type ExceptionHook hookState = IORef hookState -> SomeException -> IO ()

    -- | The 'EscapeSnapHook' is called if the user handler escapes the HTTP
    -- session, e.g. for websockets.
    type EscapeSnapHook hookState = IORef hookState -> IO ()

                                 ---------------------
                                 -- data structures --
                                 ---------------------
    ------------------------------------------------------------------------------
    -- | Data and services that all HTTP response handlers share.
    --
    data ServerConfig hookState = ServerConfig
        { forall hookState.
    ServerConfig hookState -> Request -> Response -> Word64 -> IO ()
    _logAccess             :: !(Request -> Response -> Word64 -> IO ())
        , forall hookState. ServerConfig hookState -> Builder -> IO ()
    _logError              :: !(Builder -> IO ())
        , forall hookState.
    ServerConfig hookState -> NewRequestHook hookState
    _onNewRequest          :: !(NewRequestHook hookState)
        , forall hookState. ServerConfig hookState -> ParseHook hookState
    _onParse               :: !(ParseHook hookState)
        , forall hookState.
    ServerConfig hookState -> UserHandlerFinishedHook hookState
    _onUserHandlerFinished :: !(UserHandlerFinishedHook hookState)
        , forall hookState.
    ServerConfig hookState -> UserHandlerFinishedHook hookState
    _onDataFinished        :: !(DataFinishedHook hookState)
        , forall hookState. ServerConfig hookState -> ExceptionHook hookState
    _onException           :: !(ExceptionHook hookState)
        , forall hookState.
    ServerConfig hookState -> EscapeSnapHook hookState
    _onEscape              :: !(EscapeSnapHook hookState)

          -- | will be overridden by a @Host@ header if it appears.
        , forall hookState. ServerConfig hookState -> ByteString
    _localHostname         :: !ByteString
        , forall hookState. ServerConfig hookState -> Int
    _defaultTimeout        :: {-# UNPACK #-} !Int
        , forall hookState. ServerConfig hookState -> Bool
    _isSecure              :: !Bool

          -- | Number of accept loops to spawn.
        , forall hookState. ServerConfig hookState -> Int
    _numAcceptLoops        :: {-# UNPACK #-} !Int
        }

    ------------------------------------------------------------------------------
    -- | All of the things a session needs to service a single HTTP request.
    data PerSessionData = PerSessionData
        { -- | If the bool stored in this IORef becomes true, the server will close
          -- the connection after the current request is processed.
          PerSessionData -> IORef Bool
    _forceConnectionClose :: {-# UNPACK #-} !(IORef Bool)

          -- | An IO action to modify the current request timeout.
        , PerSessionData -> (Int -> Int) -> IO ()
    _twiddleTimeout       :: !((Int -> Int) -> IO ())

          -- | The value stored in this IORef is True if this request is the first
          -- on a new connection, and False if it is a subsequent keep-alive
          -- request.
        , PerSessionData -> IORef Bool
    _isNewConnection      :: !(IORef Bool)

          -- | The function called when we want to use @sendfile().@
        , PerSessionData -> SendFileHandler
    _sendfileHandler      :: !SendFileHandler

          -- | The server's idea of its local address.
        , PerSessionData -> ByteString
    _localAddress         :: !ByteString

          -- | The listening port number.
        , PerSessionData -> Int
    _localPort            :: {-# UNPACK #-} !Int

          -- | The address of the remote user.
        , PerSessionData -> ByteString
    _remoteAddress        :: !ByteString

          -- | The remote user's port.
        , PerSessionData -> Int
    _remotePort           :: {-# UNPACK #-} !Int

          -- | The read end of the socket connection.
        , PerSessionData -> InputStream ByteString
    _readEnd              :: !(InputStream ByteString)

          -- | The write end of the socket connection.
        , PerSessionData -> OutputStream ByteString
    _writeEnd             :: !(OutputStream ByteString)
        }

    ------------------------------------------------------------------------------
    newtype AcceptFunc = AcceptFunc {
      AcceptFunc
    -> (forall a. IO a -> IO a)
    -> IO
         (SendFileHandler, ByteString, Int, ByteString, Int,
          InputStream ByteString, OutputStream ByteString, IO ())
    runAcceptFunc :: (forall a . IO a -> IO a)         -- exception restore function
                        -> IO ( SendFileHandler          -- what to do on sendfile
                              , ByteString               -- local address
                              , Int                      -- local port
                              , ByteString               -- remote address
                              , Int                      -- remote port
                              , InputStream ByteString   -- socket read end
                              , OutputStream ByteString  -- socket write end
                              , IO ()                    -- cleanup action
                              )
      }

                                 --------------------
                                 -- function types --
                                 --------------------
    ------------------------------------------------------------------------------
    -- | This function, provided to the web server internals from the outside, is
    -- responsible for producing a 'Response' once the server has parsed the
    -- 'Request'.
    --
    type ServerHandler hookState =
            ServerConfig hookState     -- ^ global server config
         -> PerSessionData             -- ^ per-connection data
         -> Request                    -- ^ HTTP request object
         -> IO (Request, Response)

    ------------------------------------------------------------------------------
    -- | A 'SendFileHandler' is called if the user handler requests that a file be
    -- sent using @sendfile()@ on systems that support it (Linux, Mac OSX, and
    -- FreeBSD).
    type SendFileHandler =
           Buffer                   -- ^ builder buffer
        -> Builder                  -- ^ status line and headers
        -> FilePath                 -- ^ file to send
        -> Word64                   -- ^ start offset
        -> Word64                   -- ^ number of bytes
        -> IO ()

                            -------------------------------
                            -- types for server backends --
                            -------------------------------

    ------------------------------------------------------------------------------
    -- | Either the server should start listening on the given interface \/ port
    -- combination, or the server should start up with a 'Socket' that has already
    -- had @bind()@ and @listen()@ called on it.
    data SocketConfig = StartListening ByteString Int
                      | PreBound Socket