    {-# LANGUAGE CPP #-}
    module Network.Socket.SendFile.Portable
         ( sendFile
         , sendFileIterWith
         , sendFile'
         , sendFileIterWith'
         , sendFile''
         , sendFileIterWith''
         , unsafeSendFile
         , unsafeSendFileIterWith
         , unsafeSendFile'
         , unsafeSendFile''
         , unsafeSendFileIterWith'
         , unsafeSendFileIterWith''
         , sendFileMode
         )
        where

    import Data.ByteString.Char8 (hGet, hPut, length, ByteString)
    import qualified Data.ByteString.Char8 as C
    import Network.Socket.ByteString (send)
    import Network.Socket (Socket(..), fdSocket)
    import Network.Socket.SendFile.Iter (Iter(..), runIter)
    import Network.Socket.SendFile.Util (wrapSendFile')
    import Prelude hiding (length)
    import System.IO (Handle, IOMode(..), SeekMode(..), hFileSize, hFlush, hIsEOF, hSeek, withBinaryFile)
    import System.Posix.Types (Fd(..))
    #ifdef __GLASGOW_HASKELL__
    #if __GLASGOW_HASKELL__ >= 611
    import System.IO.Error
    #endif
    #endif

    sendFileMode :: String
    sendFileMode = "PORTABLE_SENDFILE"

    sendFileIterWith'' :: (IO Iter -> IO a) -> Socket -> Handle -> Integer -> Integer -> Integer -> IO a
    sendFileIterWith'' stepper =
        wrapSendFile' $ \outs inp blockSize off count ->
            do hSeek inp AbsoluteSeek off
               stepper (sendFileIterS outs inp blockSize {- off -} count Nothing)

    sendFile'' :: Socket -> Handle -> Integer -> Integer -> IO ()
    sendFile'' outs inh off count =
        do _ <- sendFileIterWith'' runIter outs inh count off count
           return ()

    unsafeSendFileIterWith'' :: (IO Iter -> IO a) -> Handle -> Handle -> Integer -> Integer -> Integer -> IO a
    unsafeSendFileIterWith'' stepper =
        wrapSendFile' $ \outp inp blockSize off count ->
            do hSeek inp AbsoluteSeek off
               a <- stepper (unsafeSendFileIter outp inp blockSize count Nothing)
               hFlush outp
               return a

    unsafeSendFile'' :: Handle -> Handle -> Integer -> Integer -> IO ()
    unsafeSendFile'' outh inh off count =
        do _ <- unsafeSendFileIterWith'' runIter outh inh count off count
           return ()

    sendFileIterS :: Socket  -- ^ output network socket
                 -> Handle  -- ^ input handle
                 -> Integer -- ^ maximum number of bytes to send at once
                 -> Integer -- ^ total number of bytes to send
                 -> Maybe ByteString
                 -> IO Iter
    sendFileIterS _socket _inh _blockSize {- _off -} 0        _    = return (Done 0)
    sendFileIterS socket   inh  blockSize {- off -} remaining mBuf =
        do buf <- nextBlock
           nsent <- send socket buf
           let leftOver =
                   if nsent < (C.length buf)
                      then Just (C.drop nsent buf)
                      else Nothing
           let cont = sendFileIterS socket inh blockSize {- (off + (fromIntegral nsent)) -} (remaining `safeMinus` (fromIntegral nsent)) leftOver
           if nsent < (length buf)
              then return (WouldBlock (fromIntegral nsent) (Fd $ fdSocket socket) cont)
              else return (Sent       (fromIntegral nsent)                        cont)
        where
       nextBlock =
              case mBuf of
                (Just b) -> return b
                Nothing ->
                    do eof <- hIsEOF inh
                       if eof
                        then ioError (mkIOError eofErrorType ("Reached EOF but was hoping to read " ++ show remaining ++ " more byte(s).") (Just inh) Nothing)
                        else do let bytes = min 32768 (min blockSize remaining)
                                hGet inh (fromIntegral bytes) -- we could check that we got fewer bytes than requested here, but we will send what we got and catch the EOF next time around

    safeMinus :: (Show a, Ord a, Num a) => a -> a -> a
    safeMinus x y
        | y > x = error $ "y > x " ++ show (y,x)
        | otherwise = x - y

    unsafeSendFileIter :: Handle  -- ^ output handle
                       -> Handle  -- ^ input handle
                       -> Integer -- ^ maximum number of bytes to send at once
    --                   -> Integer -- ^ offset into file
                       -> Integer -- ^ total number of bytes to send
                       -> Maybe ByteString
                       -> IO Iter
    unsafeSendFileIter _outh _inh _blockSize 0         _mBuf = return (Done 0)
    unsafeSendFileIter  outh  inh  blockSize remaining  mBuf =
        do buf <- nextBlock
           hPut outh buf -- eventually this should use a non-blocking version of hPut
           let nsent = length buf
    {-
               leftOver =
                   if nsent < (C.length buf)
                      then Just (C.drop nsent buf)
                      else Nothing
    -}
               cont = unsafeSendFileIter outh inh blockSize {- (off + (fromIntegral nsent)) -} (remaining - (fromIntegral nsent)) Nothing
           if nsent < (length buf)
              then do error "unsafeSendFileIter: internal error" -- return (WouldBlock (fromIntegral nsent) (Fd $ fdSocket socket) cont)
              else return (Sent (fromIntegral nsent) cont)
        where
          nextBlock =
              case mBuf of
                (Just b) -> return b
                Nothing ->
                    do eof <- hIsEOF inh
                       if eof
                        then ioError (mkIOError eofErrorType ("Reached EOF but was hoping to read " ++ show remaining ++ " more byte(s).") (Just inh) Nothing)
                        else do let bytes = min 32768 (min blockSize remaining)
                                hGet inh (fromIntegral bytes) -- we could check that we got fewer bytes than requested here, but we will send what we got and catch the EOF next time around

    -- copied from Internal.hs -- not sure how to avoid having two copies of this code yet

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

    sendFileIterWith :: (IO Iter -> IO a) -> Socket -> FilePath -> Integer -> IO a
    sendFileIterWith stepper outs infp blockSize =
        withBinaryFile infp ReadMode $ \inp -> do
          count <- hFileSize inp
          sendFileIterWith'' stepper outs inp blockSize 0 count

    sendFile' :: Socket -> FilePath -> Integer -> Integer -> IO ()
    sendFile' outs infp offset count =
        withBinaryFile infp ReadMode $ \inp ->
            sendFile'' outs inp offset count

    sendFileIterWith' :: (IO Iter -> IO a) -> Socket -> FilePath -> Integer -> Integer -> Integer -> IO a
    sendFileIterWith' stepper outs infp blockSize offset count =
        withBinaryFile infp ReadMode $ \inp ->
            sendFileIterWith'' stepper outs inp blockSize offset count

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

    unsafeSendFileIterWith :: (IO Iter -> IO a) -> Handle -> FilePath -> Integer -> IO a
    unsafeSendFileIterWith stepper outp infp blockSize =
        withBinaryFile infp ReadMode $ \inp -> do
          count <- hFileSize inp
          unsafeSendFileIterWith'' stepper outp inp blockSize 0 count

    unsafeSendFile'
        :: Handle    -- ^ The output handle
        -> FilePath  -- ^ The input filepath
        -> Integer    -- ^ The offset to start at
        -> Integer -- ^ The number of bytes to send
        -> IO ()
    unsafeSendFile' outp infp offset count =
        withBinaryFile infp ReadMode $ \inp -> do
          unsafeSendFile'' outp inp offset count

    unsafeSendFileIterWith'
        :: (IO Iter -> IO a)
        -> Handle    -- ^ The output handle
        -> FilePath  -- ^ The input filepath
        -> Integer   -- ^ maximum block size
        -> Integer   -- ^ The offset to start at
        -> Integer   -- ^ The number of bytes to send
        -> IO a
    unsafeSendFileIterWith' stepper outp infp blockSize offset count =
        withBinaryFile infp ReadMode $ \inp -> do
          unsafeSendFileIterWith'' stepper outp inp blockSize offset count