-- |Generic building blocks for creating TCP Servers based on 'IterIO'
module Data.IterIO.Server.TCPServer (
  TCPServer(..),
  runTCPServer,
  defaultServerAcceptor,
  minimalTCPServer,
  simpleHttpServer,
  echoServer
) where

import Control.Concurrent.MonadIO
import Control.Monad
import qualified Data.ByteString.Lazy as L
import qualified Network.Socket as Net
import System.IO
import Data.IterIO
import Data.IterIO.Http
import Data.ListLike.IO

-- |Sets up a TCP socket to listen on the given port.
sockListenTCP :: Net.PortNumber -> IO Net.Socket
sockListenTCP pn = do
  sock <- Net.socket Net.AF_INET Net.Stream Net.defaultProtocol
  Net.setSocketOption sock Net.ReuseAddr 1
  Net.bindSocket sock (Net.SockAddrInet pn Net.iNADDR_ANY)
  Net.listen sock Net.maxListenQueue
  return sock

-- |'TCPServer' holds all the information necessary to run
-- bind to a sock and respond to TCP requests from the network.
data TCPServer inp m = TCPServer {
-- |The TCP port the server will listen for incomming connections on.
    serverPort :: Net.PortNumber
-- |This 'Inum' implements the actual functionality of the server. The input
--  and output of the 'Inum' correspond to the input and output of the socket.
  , serverHandler :: Inum inp inp m ()
-- |A function to transform an accept incomming connection into an iter and onum.
--  Most servers should just use 'defaultSocketAcceptor' but this can be used for
--  special cases, e.g. accepting SSL connections with 'Data.IterIO.SSL.iterSSL'.
  , serverAcceptor :: Net.Socket -> m (Iter inp m (), Onum inp m ())
-- |Must execute the monadic result. Servers operating in the 'IO' Monad can
--  use 'id'.
  , serverResultHandler :: m () -> IO ()
}

instance Show (TCPServer inp m) where
  show s = "TCPServer { serverPort: " ++ (show $ serverPort s) ++ " }"

-- |For convenience, a TCPServer in the 'IO' Monad with null defaults:
--
--    * Port 0 (next availabel port)
--
--    * Handler set to 'inumNop'
--
--    * Acceptor set to 'defaultServerAcceptor'
--
--    * Request handler set to 'id' (noop)
--
minimalTCPServer :: (ListLikeIO inp e, ChunkData inp) => TCPServer inp IO
minimalTCPServer = TCPServer 0 inumNop defaultServerAcceptor id

-- |This acceptor creates an 'Iter' and 'Onum' using 'handleI' and
--  'enumHandle' respectively.
defaultServerAcceptor ::  (ListLikeIO inp e,
                           ChunkData inp, MonadIO m)
                      => Net.Socket -> m (Iter inp m (), Onum inp m a)
defaultServerAcceptor sock = liftIO $ do
  h <- Net.socketToHandle sock ReadWriteMode
  hSetBuffering h NoBuffering
  return (handleI h, enumHandle h)

-- |Runs a 'TCPServer' in a loop.
runTCPServer :: (ListLikeIO inp e,
                 ChunkData inp, Monad m)
              => TCPServer inp m
              -> IO ()
runTCPServer server = do
  sock <- sockListenTCP $ serverPort server
  let handler = serverResultHandler server
  forever $ do
    (s, _) <- Net.accept sock
    _ <- forkIO $ handler $ do
      (iter, enum) <- (serverAcceptor server) s
      enum |$ serverHandler server .| iter
    return ()

-- |Creates a simple HTTP server from an 'HTTPRequestHandler'.
simpleHttpServer :: Net.PortNumber
                 -> HttpRequestHandler IO ()
                 -> TCPServer L.ByteString IO
simpleHttpServer port reqHandler = minimalTCPServer { serverPort = port, serverHandler = httpAppHandler }
  where httpAppHandler = inumHttpServer $ ioHttpServer reqHandler

-- |Creates a 'TCPServer' that echoes each line from the client until EOF.
echoServer :: Net.PortNumber -> TCPServer String IO
echoServer port = minimalTCPServer { serverPort = port, serverHandler = echoAppHandler }
  where echoAppHandler = mkInumM $ forever $ do
          input <- safeLineI
          case input of
            Just output -> irun $ enumPure $ output ++ "\r\n"
            Nothing -> irun $ enumPure []