{-# LANGUAGE ScopedTypeVariables, PackageImports #-}

-- | Conversions between Repa Arrays and ByteStrings.
module Data.Array.Repa.ByteString
	( toByteString
	, fromByteString
	, copyFromPtrWord8
	, copyToPtrWord8)
where
import Data.Word
import Data.Array.Repa
import Foreign.Marshal.Alloc
import Foreign.Ptr
import Foreign.Storable
import System.IO.Unsafe
import qualified Data.Vector.Unboxed	as V
import qualified Data.ByteString	as BS
import Data.ByteString			(ByteString)


-- | Copy an `Array` to a (strict) `ByteString`.
toByteString 
	:: Shape sh
	=> Array sh Word8
	-> ByteString

{-# NOINLINE toByteString #-}
toByteString arr
 =  withManifest' (force arr) $ \arr' 
 -> unsafePerformIO
 $ allocaBytes (size $ extent arr')	$ \(bufDest :: Ptr Word8) ->
   let 	vec	= toVector arr'
	len	= size $ extent arr'

	copy offset
	 | offset >= len
	 = return ()

	 | otherwise
	 = do	pokeByteOff bufDest offset (vec `V.unsafeIndex` offset)
		copy (offset + 1)

    in do
	copy 0
	BS.packCStringLen (castPtr bufDest, len)


-- | Copy a (strict) `ByteString` to a new `Array`.
--	The given array extent must match the length of the `ByteString`, else `error`.
fromByteString 
	:: Shape sh
	=> sh
	-> ByteString 
	-> Array sh Word8

-- TODO: Use an intermediate CString when we do this, to avoid
--	 the overhead from bounds checks in ByteString
fromByteString sh str
	| size sh /= BS.length str
	= error "Data.Array.Repa.fromByteString: given array size does not match length of string"
	
	| otherwise
	= fromFunction sh (\ix -> str `BS.index` toIndex sh ix)


-- Ptr utils ------------------------------------------------------------------
-- | Copy some data from somewhere into a new `Array`.
copyFromPtrWord8 
	:: Shape sh
	=> sh
	-> Ptr Word8
	-> IO (Array sh Word8)

{-# INLINE copyFromPtrWord8 #-}	
copyFromPtrWord8 sh ptr
 = do	return	$ fromFunction sh (\ix -> unsafePerformIO (peekElemOff ptr (toIndex sh ix)))


-- | Copy array data somewhere.s
copyToPtrWord8 
	:: Shape sh
	=> Ptr Word8
	-> Array sh Word8
	-> IO ()
	
{-# INLINE copyToPtrWord8 #-}
copyToPtrWord8 ptr arr
 = let	vec	= toVector arr
	len	= size $ extent arr
	
	copy offset
	 | offset >= len
	 = return ()
	
	 | otherwise
	 = do	pokeByteOff ptr offset (vec `V.unsafeIndex` offset)
		copy (offset + 1)

   in do
	copy 0
	return ()