-- |The entry point of Lucu httpd.
module Network.HTTP.Lucu.Httpd
    ( FallbackHandler
    , runHttpd
    )
    where

import           Control.Concurrent
import           Control.Exception
import           Control.Monad
import           Data.Maybe
import           Network.BSD
import           Network.Socket
import           Network.HTTP.Lucu.Config
import           Network.HTTP.Lucu.Interaction
import           Network.HTTP.Lucu.RequestReader
import           Network.HTTP.Lucu.Resource.Tree
import           Network.HTTP.Lucu.ResponseWriter
import           Network.HTTP.Lucu.SocketLike as SL
import           System.Posix.Signals

-- |This is the entry point of Lucu httpd. It listens to a socket and
-- waits for clients. Computation of 'runHttpd' never stops by itself
-- so the only way to stop it is to raise an exception in the thread
-- computing it.
--
-- Note that 'runHttpd' automatically makes SIGPIPE be ignored by
-- computing @'System.Posix.Signals.installHandler'
-- 'System.Posix.Signals.sigPIPE' 'System.Posix.Signals.Ignore'
-- 'Prelude.Nothing'@. This can hardly cause a problem but it may do.
--
-- Example:
--
-- > module Main where
-- > import Network.HTTP.Lucu
-- > 
-- > main :: IO ()
-- > main = let config    = defaultConfig
-- >            resources = mkResTree [ ([], helloWorld) ]
-- >        in
-- >          runHttpd config resourcees []
-- >
-- > helloWorld :: ResourceDef
-- > helloWorld = ResourceDef {
-- >                resUsesNativeThread = False
-- >              , resIsGreedy         = False
-- >              , resGet
-- >                  = Just $ do setContentType $ read "text/plain"
-- >                              output "Hello, world!"
-- >              , resHead   = Nothing
-- >              , resPost   = Nothing
-- >              , resPut    = Nothing
-- >              , resDelete = Nothing
-- >              }
runHttpd :: Config -> ResTree -> [FallbackHandler] -> IO ()
runHttpd cnf tree fbs
    = withSocketsDo $
      do _ <- installHandler sigPIPE Ignore Nothing

         let launchers
                 = catMaybes
                   [ do scnf <- cnfSSLConfig    cnf
                        addr <- cnfServerV4Addr cnf
                        return ( do so <- listenOn AF_INET addr (sslServerPort scnf)
                                    launchListener (sslContext scnf, so)
                               )
                   , do scnf <- cnfSSLConfig    cnf
                        addr <- cnfServerV6Addr cnf
                        return ( do so <- listenOn AF_INET6 addr (sslServerPort scnf)
                                    launchListener (sslContext scnf, so)
                               )
                   , do addr <- cnfServerV4Addr cnf
                        return ( launchListener =<< listenOn AF_INET addr (cnfServerPort cnf)
                               )
                   , do addr <- cnfServerV6Addr cnf
                        return ( launchListener =<< listenOn AF_INET6 addr (cnfServerPort cnf)
                               )
                   ]

         sequence_ launchers
         waitForever
    where
      launchListener :: SocketLike s => s -> IO ()
      launchListener so
          = do p <- SL.socketPort so
               -- FIXME: Don't throw away the thread ID as we can't
               -- kill it later then. [1]
               _ <- forkIO $ httpLoop p so
               return ()

      listenOn :: Family -> HostName -> ServiceName -> IO Socket
      listenOn fam host srv
          = do proto <- getProtocolNumber "tcp"
               let hints = defaultHints {
                             addrFlags      = [AI_PASSIVE]
                           , addrFamily     = fam
                           , addrSocketType = Stream
                           , addrProtocol   = proto
                           }
               addrs <- getAddrInfo (Just hints) (Just host) (Just srv)
               let addr = head addrs
               bracketOnError
                   (socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr))
                   (sClose)
                   (\ sock ->
                        do setSocketOption sock ReuseAddr 1
                           bindSocket sock (addrAddress addr)
                           listen sock maxListenQueue
                           return sock
                   )

      httpLoop :: SocketLike s => PortNumber -> s -> IO ()
      httpLoop port so
          = do (h, addr)  <- SL.accept so
               tQueue     <- newInteractionQueue
               readerTID  <- forkIO $ requestReader cnf tree fbs h port addr tQueue
               _writerTID <- forkIO $ responseWriter cnf h tQueue readerTID
               httpLoop port so

      waitForever :: IO ()
      waitForever = forever (threadDelay 1000000)