{-# LANGUAGE CPP                      #-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE OverloadedStrings        #-}

module Snap.Internal.Http.Server.HttpPort
  ( bindHttp
  , createSession
  , endSession
  , recv
  , send
  ) where

------------------------------------------------------------------------------
import           Data.ByteString (ByteString)
import           Foreign
import           Foreign.C
import           Network.Socket hiding (recv, send)
import           Unsafe.Coerce
------------------------------------------------------------------------------
#ifdef PORTABLE
import qualified Data.ByteString as B
import qualified Network.Socket.ByteString as SB
#else
import qualified Data.ByteString.Internal as BI
import qualified Data.ByteString.Unsafe as BI
#endif
------------------------------------------------------------------------------
import           Snap.Internal.Debug
import           Snap.Internal.Http.Server.Backend
import           Snap.Internal.Http.Server.Address


------------------------------------------------------------------------------
bindHttp :: ByteString -> Int -> IO ListenSocket
bindHttp bindAddr bindPort = do
    (family, addr) <- getSockAddr bindPort bindAddr
    sock           <- socket family Stream 0

    debug $ "bindHttp: binding port " ++ show addr
    setSocketOption sock ReuseAddr 1
    bindSocket sock addr
    listen sock 150

    debug $ "bindHttp: bound socket " ++ show sock
    return $! ListenHttp sock


------------------------------------------------------------------------------
createSession :: Int -> CInt -> IO () -> IO NetworkSession
createSession buffSize s _ =
    return $! NetworkSession s (unsafeCoerce ()) $ fromIntegral buffSize


------------------------------------------------------------------------------
endSession :: NetworkSession -> IO ()
endSession _ = return ()


#ifdef PORTABLE
------------------------------------------------------------------------------
recv :: Socket -> IO () -> NetworkSession -> IO (Maybe ByteString)
recv sock _ (NetworkSession { _recvLen = s }) = do
    bs <- SB.recv sock (fromIntegral s)
    return $! if B.null bs then Nothing else Just bs


------------------------------------------------------------------------------
send :: Socket -> IO () -> IO () -> NetworkSession -> ByteString -> IO ()
send sock tickle _ _ bs = SB.sendAll sock bs >> tickle


#else
------------------------------------------------------------------------------
recv :: IO () -> NetworkSession -> IO (Maybe ByteString)
recv onBlock (NetworkSession s _ buffSize) = do
    fp <- BI.mallocByteString $ fromEnum buffSize
    sz <- withForeignPtr fp $ \p ->
              throwErrnoIfMinus1RetryMayBlock
                  "recv"
                  (c_read s p $ toEnum buffSize)
                  onBlock

    if sz == 0
      then return Nothing
      else return $! Just $! BI.fromForeignPtr fp 0 $! fromEnum sz


------------------------------------------------------------------------------
send :: IO () -> IO () -> NetworkSession -> ByteString -> IO ()
send tickleTimeout onBlock (NetworkSession s _ _) bs =
    BI.unsafeUseAsCStringLen bs $ uncurry loop

  where
    loop ptr len = do
        sent <- throwErrnoIfMinus1RetryMayBlock
                  "send"
                  (c_write s ptr $ toEnum len)
                  onBlock

        let sent' = fromIntegral sent
        if sent' < len
           then tickleTimeout >> loop (plusPtr ptr sent') (len - sent')
           else tickleTimeout


------------------------------------------------------------------------------
foreign import ccall unsafe "unistd.h read" c_read
    :: CInt -> Ptr a -> CSize -> IO (CSize)
foreign import ccall unsafe "unistd.h write" c_write
    :: CInt -> Ptr a -> CSize -> IO (CSize)

#endif
