    -- |
    -- Module      : Foundation.IO.File
    -- License     : BSD-style
    -- Maintainer  : Foundation
    -- Stability   : experimental
    -- Portability : portable
    --
    {-# LANGUAGE OverloadedStrings #-}
    module Foundation.IO.File
        ( FilePath
        , openFile
        , closeFile
        , IOMode(..)
        , withFile
        , hGet
        , hGetNonBlocking
        , hGetSome
        , hPut
        , readFile
        ) where

    import           System.IO (Handle, IOMode)
    import           System.IO.Error
    import qualified System.IO as S
    import           Foundation.Collection
    import           Foundation.VFS
    import           Basement.Types.OffsetSize
    import           Basement.Imports
    import           Foundation.Array.Internal
    import           Foundation.Numerical
    import qualified Basement.UArray.Mutable as V
    import qualified Basement.UArray as V
    import           Control.Exception (bracket)
    import           Foreign.Ptr (plusPtr)

    -- | list the file name in the given FilePath directory
    --
    -- TODO: error management and not implemented yet
    --getDirectory :: FilePath -> IO [FileName]
    --getDirectory = undefined

    -- | Open a new handle on the file
    openFile :: FilePath -> IOMode -> IO Handle
    openFile filepath mode = do
        S.openBinaryFile (filePathToLString filepath) mode

    -- | Close a handle
    closeFile :: Handle -> IO ()
    closeFile = S.hClose

    -- | Read binary data directly from the specified 'Handle'.
    --
    -- First argument is the Handle to read from, and the second is the number of bytes to read.
    -- It returns the bytes read, up to the specified size, or an empty array if EOF has been reached.
    --
    -- 'hGet' is implemented in terms of 'hGetBuf'.
    hGet :: Handle -> Int -> IO (UArray Word8)
    hGet h size
        | size < 0   = invalidBufferSize "hGet" h size
        | otherwise  = V.createFromIO (CountOf size) $ \p -> (CountOf <$> S.hGetBuf h p size)

    -- | hGetNonBlocking is similar to 'hGet', except that it will never block
    -- waiting for data to become available, instead it returns only whatever data
    -- is available.  If there is no data available to be read, 'hGetNonBlocking'
    -- returns an empty array.
    --
    -- Note: on Windows, this function behaves identically to 'hGet'.
    hGetNonBlocking :: Handle -> Int -> IO (UArray Word8)
    hGetNonBlocking h size
        | size < 0  = invalidBufferSize "hGetNonBlocking" h size
        | otherwise = V.createFromIO (CountOf size) $ \p -> (CountOf <$> S.hGetBufNonBlocking h p size)

    -- | Like 'hGet', except that a shorter array may be returned
    -- if there are not enough bytes immediately available to satisfy the
    -- whole request.  'hGetSome' only blocks if there is no data
    -- available, and EOF has not yet been reached.
    --
    hGetSome :: Handle -> Int -> IO (UArray Word8)
    hGetSome h size
        | size < 0  = invalidBufferSize "hGetSome" h size
        | otherwise = V.createFromIO (CountOf size) $ \p -> (CountOf <$> S.hGetBufSome h p size)

    hPut :: Handle -> (UArray Word8) -> IO ()
    hPut h arr = withPtr arr $ \ptr -> S.hPutBuf h ptr (let (CountOf sz) = length arr in sz)

    invalidBufferSize :: [Char] -> Handle -> Int -> IO a
    invalidBufferSize functionName handle size =
        ioError $ mkIOError illegalOperationErrorType
                            (functionName <> " invalid array size: " <> toList (show size))
                            (Just handle)
                            Nothing

    -- | @'withFile' filepath mode act@ opens a file using the mode@
    -- and run act@. the by-product handle will be closed when act finish,
    -- either normally or through an exception.
    --
    -- The value returned is the result of act@
    withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
    withFile fp mode act = bracket (openFile fp mode) closeFile act

    -- | Read a binary file and return the whole content in one contiguous buffer.
    readFile :: FilePath -> IO (UArray Word8)
    readFile fp = withFile fp S.ReadMode $ \h -> do
        -- TODO filesize is an integer (whyyy ?!), and transforming to Int using
        -- fromIntegral is probably the wrong thing to do here..
        sz <- S.hFileSize h
        mv <- V.newPinned (CountOf $ fromInteger sz)
        V.withMutablePtr mv $ loop h (fromInteger sz)
        unsafeFreeze mv
      where
        loop h left dst
            | left == 0 = return ()
            | otherwise = do
                let toRead = min blockSize left
                r <- S.hGetBuf h dst toRead
                if r > 0 && r <= toRead
                    then loop h (left - r) (dst `plusPtr` r)
                    else error "readFile: " -- turn into proper error

    blockSize :: Int
    blockSize = 4096