-- | Input and output streams for files.
--
-- The functions in this file use \"with*\" or \"bracket\" semantics, i.e. they
-- open the supplied 'FilePath', run a user computation, and then close the
-- file handle. If you need more control over the lifecycle of the underlying
-- file descriptor resources, you are encouraged to use the functions from
-- "System.IO.Streams.Handle" instead.
module System.IO.Streams.File
  ( -- * File conversions
    withFileAsInput
  , withFileAsInputStartingAt
  , unsafeWithFileAsInputStartingAt
  , withFileAsOutput
  , withFileAsOutputExt
  ) where

------------------------------------------------------------------------------
import           Control.Monad              (unless)
import           Data.ByteString            (ByteString)
import           Data.Int                   (Int64)
import           System.IO                  (BufferMode (NoBuffering), IOMode (ReadMode, WriteMode), SeekMode (AbsoluteSeek), hSeek, hSetBuffering, withBinaryFile)
------------------------------------------------------------------------------
import           System.IO.Streams.Handle   (handleToInputStream, handleToOutputStream)
import           System.IO.Streams.Internal (InputStream, OutputStream)


------------------------------------------------------------------------------
-- | @'withFileAsInput' name act@ opens the specified file in \"read mode\" and
-- passes the resulting 'InputStream' to the computation @act@. The file will
-- be closed on exit from @withFileAsInput@, whether by normal termination or
-- by raising an exception.
--
-- If closing the file raises an exception, then /that/ exception will be
-- raised by 'withFileAsInput' rather than any exception raised by @act@.
withFileAsInput :: FilePath                          -- ^ file to open
                -> (InputStream ByteString -> IO a)  -- ^ function to run
                -> IO a
withFileAsInput = withFileAsInputStartingAt 0


------------------------------------------------------------------------------
-- | Like 'withFileAsInput', but seeks to the specified byte offset before
-- attaching the given file descriptor to the 'InputStream'.
withFileAsInputStartingAt
    :: Int64                             -- ^ starting index to seek to
    -> FilePath                          -- ^ file to open
    -> (InputStream ByteString -> IO a)  -- ^ function to run
    -> IO a
withFileAsInputStartingAt idx fp m = withBinaryFile fp ReadMode go
  where
    go h = do
        unless (idx == 0) $ hSeek h AbsoluteSeek $ toInteger idx
        handleToInputStream h >>= m


------------------------------------------------------------------------------
-- | Like 'withFileAsInputStartingAt', except that the 'ByteString' emitted by
-- the created 'InputStream' may reuse its buffer. You may only use this
-- function if you do not retain references to the generated bytestrings
-- emitted.
unsafeWithFileAsInputStartingAt
    :: Int64                             -- ^ starting index to seek to
    -> FilePath                          -- ^ file to open
    -> (InputStream ByteString -> IO a)  -- ^ function to run
    -> IO a
unsafeWithFileAsInputStartingAt = withFileAsInputStartingAt


------------------------------------------------------------------------------
-- | Open a file for writing and  attaches an 'OutputStream' for you to write
-- to. The file will be closed on error or completion of your action.
withFileAsOutput
    :: FilePath                           -- ^ file to open
    -> (OutputStream ByteString -> IO a)  -- ^ function to run
    -> IO a
withFileAsOutput f = withFileAsOutputExt f WriteMode NoBuffering


------------------------------------------------------------------------------
-- | Like 'withFileAsOutput', but allowing you control over the output file
-- mode and buffering behaviour.
withFileAsOutputExt
    :: FilePath                           -- ^ file to open
    -> IOMode                             -- ^ mode to write in
    -> BufferMode                         -- ^ should we buffer the output?
    -> (OutputStream ByteString -> IO a)  -- ^ function to run
    -> IO a
withFileAsOutputExt fp iomode buffermode m = withBinaryFile fp iomode $ \h -> do
    hSetBuffering h buffermode
    handleToOutputStream h >>= m
