{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -fno-warn-unused-imports #-}
module Network.Socket.SendFile.Internal (
    sendFile,
    sendFile',
    sendFileMode,
    unsafeSendFile,
    unsafeSendFile',
    ) where

import Network.Socket (Socket(..), fdSocket)
import System.IO (
    Handle,
    IOMode(..),
    hFileSize,
    hFlush,
    withBinaryFile
    )
import System.Posix.Types (Fd(..))
import GHC.Handle (withHandle_)
import GHC.IOBase (haFD)

#if defined(WIN32_SENDFILE)
import Network.Socket.SendFile.Win32 (_sendFile)

sendFileMode :: String
sendFileMode = "WIN32_SENDFILE"
#endif

#if defined(LINUX_SENDFILE)
import Network.Socket.SendFile.Linux (_sendFile)

sendFileMode :: String
sendFileMode = "LINUX_SENDFILE"
#endif

#if defined(FREEBSD_SENDFILE)
import Network.Socket.SendFile.FreeBSD (_sendFile)

sendFileMode :: String
sendFileMode = "FREEBSD_SENDFILE"
#endif

#if defined(PORTABLE_SENDFILE)
import Data.ByteString.Char8 (hGet, hPutStr)
import Network.Socket.ByteString (sendAll)

sendFileMode :: String
sendFileMode = "PORTABLE_SENDFILE"

sendFile' :: Socket -> Handle -> Integer -> IO ()
sendFile' = wrapSendFile' $ \outs inp count -> do
    sendAll outs =<< hGet inp (fromIntegral count)
    return ()

unsafeSendFile' :: Handle -> Handle -> Integer -> IO ()
unsafeSendFile' = wrapSendFile' $ \outp inp count -> do
    hPutStr outp =<< hGet inp (fromIntegral count)
    hFlush outp -- match the behavior that all data is "flushed to the os" of native implementations
#else
sendFile' :: Socket -> Handle -> Integer -> IO ()
sendFile' outs inp count =
    withHandle_ "Network.Socket.SendFile.sendFile'" inp $ \inp' -> do
    let out_fd = Fd (fdSocket outs)
    let in_fd = Fd (haFD inp')
    wrapSendFile' _sendFile out_fd in_fd count

unsafeSendFile' :: Handle -> Handle -> Integer -> IO ()
unsafeSendFile' outp inp count = do
    hFlush outp -- flush outp before sending
    withHandle_ "Network.Socket.SendFile.unsafeSendFile'" outp $ \outp' -> do
    withHandle_ "Network.Socket.SendFile.unsafeSendFile'" inp $ \inp' -> do
    let out_fd = Fd (haFD outp')
    let in_fd = Fd (haFD inp')
    wrapSendFile' _sendFile out_fd in_fd count
#endif

-- | wraps sendFile' to check arguments
wrapSendFile' :: (a -> b -> Integer -> IO ()) -> a -> b -> Integer -> IO ()
wrapSendFile' fun outp inp count
    | count < 0  = error "SendFile - count must be a positive integer"
    | count == 0 = return () -- Send nothing -- why do the work? Also, Windows treats '0' as 'send the whole file'.
    | otherwise  = fun outp inp count

sendFile :: Socket -> FilePath -> IO ()
sendFile outs infp = withBinaryFile infp ReadMode $ \inp -> do
    count <- hFileSize inp
    sendFile' outs inp count

unsafeSendFile :: Handle -> FilePath -> IO ()
unsafeSendFile outp infp = withBinaryFile infp ReadMode $ \inp -> do
    count <- hFileSize inp
    unsafeSendFile' outp inp count