{-# OPTIONS -cpp -fglasgow-exts #-}

module BinArray
  ( BinArray
  , newBinArray
  , sizeBinArray
  , zeroBinArray 
  , readWord8
  , writeWord8
  , readWord32
  , writeWord32
  , copy
  , getBinArrayRawBuffer 	-- :: BinArray -> MutableByteArray# RealWorld
  ) where

import GHC.Base
import GHC.IOBase
import GHC.Word
--import GHC.Int



-- This is a hack to prevent the profiling version from
-- being *significantly* slower than the unprofiling version.

#ifdef PROFILING
#define IOSCC(label) (\ fn -> IO $ \ s ->  {-# SCC label #-} case fn of { IO m -> m s })
#else
#define IOSCC(label) id
#endif

data BinArray = BinArray Int# 	-- size in *bytes*
     			 (MutableByteArray# RealWorld)
                                -- byte array, padded to 32 bits sized

getBinArrayRawBuffer :: BinArray -> MutableByteArray# RealWorld
getBinArrayRawBuffer (BinArray _ arr) = arr

newBinArray :: Int -> IO BinArray
newBinArray (I# sz) = IO $ \ s_in ->  
  case newByteArray# size s_in of { (# s, arr #) ->
  (# s, BinArray sz arr #) }
  where (I# size) = ((I# sz + 3) `div` 4) * 4


sizeBinArray :: BinArray -> Int
sizeBinArray (BinArray sz _) = I# sz

zeroBinArray :: BinArray -> IO ()
zeroBinArray arr@(BinArray sz _) = 
   sequence_ [ writeWord8 arr i 0x0 | i <- take (I# sz) [0..] ]


-- TODO: this copy needs reworked, to use the FFI.

-- | copy a BinArray onto another BinArray.
--   Requires that the target be the same size, or bigger.

copy :: BinArray -> BinArray -> IO ()
copy ba1@(BinArray i1 _) ba2@(BinArray i2 _) 
     | i1 <=# i2 
       && ((I# i1) `mod` 4) == 0
       && ((I# i2) `mod` 4) == 0 = IOSCC("copy") $ 
         do let loop i t | t == 0 = return ()
                         | otherwise =
                           do v <- unsafeReadWord32 ba1 (I# i)
                              unsafeWriteWord32 ba2 (I# i) v
                              loop (i +# 1#) (t - 1)
            loop 0# ((I# i1) `div` (4 :: Int))
     | otherwise = error "BinArray.copy: BinArray's need to be the same size (or bigger) for copy, and power of 4"


readWord8 :: BinArray -> Int -> IO Word8
readWord8 ba@(BinArray i1 _) (I# i) 
    | i <# i1 = unsafeReadWord8 ba (I# i)
    | otherwise = fail "Bad index value in readWord8"

{-# INLINE unsafeReadWord8 #-}
unsafeReadWord8 :: BinArray -> Int -> IO Word8
unsafeReadWord8 (BinArray _ arr) (I# i) =
    IO $ \ s_in -> case readWord8Array# arr i s_in of { (# s, v #) ->
                     (# s, W8# v #) }

writeWord8 :: BinArray -> Int -> Word8 -> IO ()
writeWord8 ba@(BinArray i1 _) (I# i) v
     | i <# i1 = unsafeWriteWord8 ba (I# i) v
     | otherwise = fail "Bad index value in writeWord8"

{-# INLINE unsafeWriteWord8 #-}
unsafeWriteWord8 :: BinArray -> Int -> Word8 -> IO ()
unsafeWriteWord8 (BinArray _ arr) (I# i) (W8# v) =
    IO $ \ s_in -> case writeWord8Array# arr i v s_in of { s ->
                (# s, () #) }
                 
-- | The addressing is done by Word32 blocks, not the byte address.

readWord32 :: BinArray -> Int -> IO Word32
readWord32 ba@(BinArray i1 _) (I# i) 
    | (i *# 4#) <# i1 = unsafeReadWord32 ba (I# i)
    | otherwise = fail "Bad index value in readWord32"

{-# INLINE unsafeReadWord32 #-}
unsafeReadWord32 :: BinArray -> Int -> IO Word32
unsafeReadWord32 (BinArray _ arr) (I# i) =
    IO $ \ s_in -> case readWord32Array# arr i s_in of { (# s, v #) ->
                (# s, W32# v #) }

-- | The addressing is done by Word32 blocks, not the byte address.

writeWord32 :: BinArray -> Int -> Word32 -> IO ()
writeWord32 ba@(BinArray i1 _) (I# i) v 
    | (i *# 4#) <# i1 = unsafeWriteWord32 ba (I# i) v
    | otherwise = fail "Bad index value in writeWord32"

{-# INLINE unsafeWriteWord32 #-}
unsafeWriteWord32 :: BinArray -> Int -> Word32 -> IO ()
unsafeWriteWord32 (BinArray _ arr) (I# i) (W32# v) =
    IO $ \ s_in -> case writeWord32Array# arr i v s_in of { s ->
                (# s, () #) }