-- | Raw Array IO.
module Data.Repa.Array.Auto.IO
        ( -- * Via File Names
          readFile
        , writeFile
        , appendFile

          -- * Via File Handles
        , hGetArray,   hGetArrayPre
        , hPutArray)
where
import Data.Repa.Array.Auto.Base
import Data.Repa.Array.Generic.Convert
import Data.Word
import qualified Data.Repa.Array.Material.Foreign       as A
import qualified Foreign.Ptr                            as F
import qualified Foreign.ForeignPtr                     as F
import qualified Foreign.Marshal.Alloc                  as F
import qualified Foreign.Marshal.Utils                  as F
import qualified System.IO                              as S
import qualified System.FileLock                        as S
import Prelude hiding (readFile, writeFile, appendFile)


---------------------------------------------------------------------------------------------------
-- | Read an entire file as an array of bytes.
readFile :: FilePath -> IO (Array Word8)
readFile path
 = S.withFileLock path S.Shared
 $ \_lock -> do
        h       <- S.openFile path S.ReadMode
        S.hSeek h S.SeekFromEnd  0
        len     <- S.hTell h
        S.hSeek h S.AbsoluteSeek 0
        !arr    <- hGetArray h (fromIntegral len)
        S.hClose h
        return arr
{-# NOINLINE readFile #-}


-- | Write an array of bytes to a file.
writeFile :: FilePath -> Array Word8 -> IO ()
writeFile path arr
 = S.withFileLock path S.Exclusive
 $ \_lock -> do
        h       <- S.openFile path S.WriteMode
        hPutArray h arr
        S.hClose h
{-# NOINLINE writeFile #-}


-- | Append an array of bytes to a file.
appendFile :: FilePath -> Array Word8 -> IO ()
appendFile path arr
 = S.withFileLock path S.Exclusive
 $ \_lock -> do
        h       <- S.openFile path S.AppendMode
        hPutArray h arr
        S.hClose h
{-# NOINLINE appendFile #-}


---------------------------------------------------------------------------------------------------
-- | Get data from a file handle, up to the given number of bytes.
hGetArray :: S.Handle -> Int -> IO (Array Word8)
hGetArray h len
 = do   buf :: F.Ptr Word8 <- F.mallocBytes len
        bytesRead          <- S.hGetBuf h buf len
        fptr               <- F.newForeignPtr F.finalizerFree buf
        return  $! convert $! A.fromForeignPtr bytesRead fptr
{-# NOINLINE hGetArray #-}


-- | Get data from a file handle, up to the given number of bytes, also
--   copying the given data to the front of the new buffer.
hGetArrayPre :: S.Handle -> Int -> Array Word8 -> IO (Array Word8)
hGetArrayPre h len arr
 | (offset, lenPre, fptrPre :: F.ForeignPtr Word8)   
        <- A.toForeignPtr $ convert arr
 = F.withForeignPtr fptrPre
 $ \ptrPre' -> do
        let ptrPre      = F.plusPtr ptrPre' offset
        ptrBuf :: F.Ptr Word8 <- F.mallocBytes (lenPre + len)
        F.copyBytes ptrBuf ptrPre lenPre
        lenRead         <- S.hGetBuf h (F.plusPtr ptrBuf lenPre) len
        let bytesTotal  = lenPre + lenRead
        fptrBuf         <- F.newForeignPtr F.finalizerFree ptrBuf
        return  $ convert $! A.fromForeignPtr bytesTotal fptrBuf
{-# NOINLINE hGetArrayPre #-}


-- | Write data into a file.
hPutArray :: S.Handle -> Array Word8 -> IO ()
hPutArray h arr
 | (offset, lenPre, fptrPre :: F.ForeignPtr Word8)     
        <- A.toForeignPtr $ convert arr
 = F.withForeignPtr fptrPre
 $ \ptr' -> do
        let ptr         = F.plusPtr ptr' offset
        S.hPutBuf h ptr lenPre
{-# NOINLINE hPutArray #-}