-- | Simple module which provides the low-level socket handling in case you want
-- to write a stand-alone 'WebSockets' application.
{-# LANGUAGE OverloadedStrings #-}
module Network.WebSockets.Socket
    ( runServer
    , runWithSocket
    ) where

import Control.Concurrent (forkIO)
import Control.Monad (forever)
import Control.Monad.Trans (liftIO)

import Network.Socket ( Family (..), SockAddr (..), Socket
                      , SocketOption (ReuseAddr), SocketType (..)
                      , accept, bindSocket, defaultProtocol, inet_addr, listen
                      , sClose, setSocketOption, socket, withSocketsDo
                      )
import Network.Socket.ByteString (recv, sendMany)

import Data.ByteString (ByteString)
import Data.Enumerator ( Enumerator, Iteratee (..), Stream (..)
                       , checkContinue0, continue, run, yield, (>>==), ($$)
                       )

import Network.WebSockets.Monad

-- | Provides a simple server. This function blocks forever. Note that this
-- is merely provided for quick-and-dirty standalone applications, for real
-- applications, you should use a real server.
runServer :: String         -- ^ Address to bind to
          -> Int            -- ^ Port to listen on
          -> WebSockets ()  -- ^ Application to serve
          -> IO ()          -- ^ Never returns
runServer host port ws = withSocketsDo $ do
    sock <- socket AF_INET Stream defaultProtocol
    _ <- setSocketOption sock ReuseAddr 1
    host' <- inet_addr host
    bindSocket sock (SockAddrInet (fromIntegral port) host')
    listen sock 5
    forever $ do
        (conn, _) <- accept sock
        _ <- forkIO $ runWithSocket conn ws
        return ()

-- | This function wraps 'runWebSockets' in order to provide a simple API for
-- stand-alone servers.
runWithSocket :: Socket -> WebSockets a -> IO a
runWithSocket s ws = do
    r <- run $ receiveEnum s $$ runWebSockets ws (sendIter s)
    sClose s
    either (error . show) return r

receiveEnum :: Socket -> Enumerator ByteString IO a
receiveEnum s = checkContinue0 $ \loop f -> do
    b <- liftIO $ recv s 4096
    if b == ""
        then continue f
        else f (Chunks [b]) >>== loop

sendIter :: Socket -> Iteratee ByteString IO ()
sendIter s = continue go
  where
    go (Chunks []) = continue go
    go (Chunks cs) = liftIO (sendMany s cs) >> continue go
    go EOF         = yield () EOF