    {-# LANGUAGE BangPatterns #-}
    module Network.Socket.ByteString.Lazy.Posix
        (
        -- * Send data to a socket
          send
        , sendAll
        ) where

    import Control.Monad (liftM)
    import Control.Monad (unless)
    import qualified Data.ByteString.Lazy as L
    import Data.ByteString.Lazy.Internal (ByteString(..))
    import Data.ByteString.Unsafe (unsafeUseAsCStringLen)
    import Data.Int (Int64)
    import Foreign.Marshal.Array (allocaArray)
    import Foreign.Ptr (plusPtr)
    import Foreign.Storable (Storable(..))

    import Network.Socket (Socket(..))
    import Network.Socket.ByteString.IOVec (IOVec(IOVec))
    import Network.Socket.ByteString.Internal (c_writev)
    import Network.Socket.Internal

    -- -----------------------------------------------------------------------------
    -- Sending

    send :: Socket      -- ^ Connected socket
         -> ByteString  -- ^ Data to send
         -> IO Int64    -- ^ Number of bytes sent
    send sock@(MkSocket fd _ _ _ _) s = do
      let cs  = take maxNumChunks (L.toChunks s)
          len = length cs
      liftM fromIntegral . allocaArray len $ \ptr ->
        withPokes cs ptr $ \niovs ->
          throwSocketErrorWaitWrite sock "writev" $
            c_writev (fromIntegral fd) ptr niovs
      where
        withPokes ss p f = loop ss p 0 0
          where loop (c:cs) q k !niovs
                    | k < maxNumBytes =
                        unsafeUseAsCStringLen c $ \(ptr,len) -> do
                          poke q $ IOVec ptr (fromIntegral len)
                          loop cs (q `plusPtr` sizeOf (undefined :: IOVec))
                                  (k + fromIntegral len) (niovs + 1)
                    | otherwise = f niovs
                loop _ _ _ niovs = f niovs
        maxNumBytes  = 4194304 :: Int  -- maximum number of bytes to transmit in one system call
        maxNumChunks = 1024    :: Int  -- maximum number of chunks to transmit in one system call

    sendAll :: Socket      -- ^ Connected socket
            -> ByteString  -- ^ Data to send
            -> IO ()
    sendAll sock bs = do
      sent <- send sock bs
      let bs' = L.drop sent bs
      unless (L.null bs') $ sendAll sock bs'