module Data.Repa.Array.Auto.Unpack
        ( module Data.Repa.Convert.Format
        , packForeign
        , unpackForeign)
where
import Data.Repa.Array.Auto.Base                        as A
import Data.Repa.Array.Generic.Convert                  as A
import qualified Data.Repa.Array.Material.Auto          as A
import qualified Data.Repa.Array.Material.Foreign       as A
import qualified Data.Repa.Array.Internals.Target       as A
import qualified Data.Repa.Array.Internals.Bulk         as A
import Data.Repa.Convert.Format

import Foreign.ForeignPtr
import Foreign.Ptr

import System.IO.Unsafe
import Control.Monad
import Data.Word

import qualified Data.Vector.Storable.Mutable   as SM
#include "repa-array.h"


---------------------------------------------------------------------------------------------------
-- | Pack some array elements into a foreign buffer using the given binary
--   format.
packForeign    
        :: (Packable format, A.Bulk A.A (Value format))
        => format                       -- ^ Binary format for each element.
        -> Array (Value format)         -- ^ Source elements.
        -> Maybe (Array Word8)          -- ^ Packed binary data.

packForeign !format !arrElems
 | Just rowSize <- fixedSize format
 , lenElems     <- A.length arrElems
 , lenBytes     <- rowSize * lenElems
 
 = unsafePerformIO
 $ do   
        buf@(A.FBuffer mvec) :: A.Buffer A.F Word8
                <- A.unsafeNewBuffer (A.Foreign lenBytes)

        let (fptr, oStart, _)   = SM.unsafeToForeignPtr mvec 

        withForeignPtr fptr $ \ptr_
         -> do  let ptr = plusPtr ptr_ oStart

                let loop !ixSrc !ixDst
                     | ixSrc >= lenElems
                     = return $ Just ()

                     | otherwise
                     = Data.Repa.Convert.Format.pack   
                                (plusPtr ptr ixDst) format (A.index arrElems ixSrc)
                     $ \oElem   -> loop (ixSrc + 1) (ixDst + oElem)

                mFinal <- loop 0 0
                case mFinal of
                 Nothing       -> return Nothing
                 Just _        -> liftM (Just . A.convert) $ A.unsafeFreezeBuffer buf

 | otherwise
 = Nothing
{-# INLINE_ARRAY packForeign #-}


---------------------------------------------------------------------------------------------------
-- | Unpack an array of elements from a foreign buffer into their standard
--   in-memory representation.
--
--   The binary format of the elements in the buffer is given by the
--   format specififier, while the in-memory representation is chosen
--   automagically based on the type of the elements.
--
unpackForeign 
        :: (Packable format, A.Target A.A (Value format))
        => format                       -- ^ Binary format for each element.
        -> Array Word8                  -- ^ Packed binary data.
        -> Maybe (Array (Value format)) -- ^ Unpacked elements.

unpackForeign !format !arrBytes
 | Just rowSize <- fixedSize format
 , lenBytes     <- A.length arrBytes
 , lenBytes `mod` rowSize == 0
 , lenElems     <- lenBytes `div` rowSize
 = unsafePerformIO
 $ do   
        let (oStart, _, fptr :: ForeignPtr Word8) 
                = A.toForeignPtr $ A.convert arrBytes

        withForeignPtr fptr $ \ptr_
         -> do  let ptr =  plusPtr ptr_ oStart
                buf     <- A.unsafeNewBuffer (A.Auto lenElems)

                let loop !ixSrc !ixDst 
                     | ixDst >= lenElems
                     = return $ Just ixSrc

                     | otherwise
                     = Data.Repa.Convert.Format.unpack 
                                (plusPtr ptr ixSrc) format 
                     $ \(value, oElem) -> do
                        A.unsafeWriteBuffer buf ixDst value
                        loop (ixSrc + oElem) (ixDst + 1)

                mFinal <- loop 0 0
                case mFinal of
                 Nothing        -> return Nothing
                 Just _         -> liftM Just $ A.unsafeFreezeBuffer buf

 | otherwise
 = Nothing
{-# INLINE_ARRAY unpackForeign #-}