module Data.Yarr.Repr.Foreign (
    F, FS,
    -- | There are also @ForeignArray@  and @ForeignSlice@
    -- 'UArray' family constructors,
    -- which aren't presented in the docs because Haddock
    -- doesn't support associated family constructors.
    --
    -- See source of "Data.Yarr.Repr.Foreign" module.
    UArray(..),
    
    Storable, L,
    newEmpty,
    toForeignPtr, unsafeFromForeignPtr,
) where

import Foreign
import Foreign.ForeignPtr
import Foreign.Marshal.Alloc
import Foreign.Marshal.MissingAlloc

import Data.Yarr.Base as B
import Data.Yarr.Repr.Delayed
import Data.Yarr.Repr.Separate
import Data.Yarr.Shape

import Data.Yarr.Utils.Storable
import Data.Yarr.Utils.FixedVector as V

-- | Foreign representation is the heart of Yarr framework.
--
-- Internally it holds raw pointer ('Ptr'), which makes indexing
-- foreign arrays not slower than GHC's built-in primitive arrays,
-- but without freeze/thaw boilerplate.
--
-- Foreign arrays are very permissible, for example you can easily
-- use them as source and target of 'Data.Yarr.Eval.Load'ing operation simultaneously,
-- achieving old good in-place @C-@style array modifying:
--
-- @'Data.Yarr.Eval.loadS' 'fill' ('dmap' 'sqrt' arr) arr@
--
-- Foreign arrays are intented to hold all 'Storable' types and
-- vectors of them (because there is a conditional instance of 'Storalbe'
-- class for 'Vector's of 'Storable's too).
data F

instance Shape sh => Regular F L sh a where

    data UArray F L sh a =
        ForeignArray
            !sh              -- Extent
            {-# NOUNPACK #-}
            !(ForeignPtr a)  -- Foreign ptr for GC
            !(Ptr a)         -- Plain ptr for fast memory access
    
    extent (ForeignArray sh _ _) = sh
    touchArray (ForeignArray _ fptr _) = touchForeignPtr fptr
    
    {-# INLINE extent #-}
    {-# INLINE touchArray #-}    

instance Shape sh => NFData (UArray F L sh a) where
    rnf (ForeignArray sh fptr ptr) = sh `deepseq` fptr `seq` ptr `seq` ()

instance (Shape sh, Storable a) => USource F L sh a where
    linearIndex (ForeignArray _ _ ptr) i = peekElemOff ptr i
    {-# INLINE linearIndex #-}

instance DefaultFusion F D L

-- | Foreign Slice representation, /view/ slice representation
-- for 'F'oreign arrays.
--
-- To understand Foreign Slices,
-- suppose you have standard @image@ array of
-- @'UArray' 'F' 'Dim2' ('VecList' 'N3' Word8)@ type.
--
-- It's layout in memory (with array indices):
--
-- @
--  r g b | r g b | r g b | ...
-- (0, 0)  (0, 1)  (0, 2)   ...
-- @
--
-- @
-- let (VecList [reds, greens, blues]) = 'slices' image
-- -- reds, greens, blues :: UArray FS Dim2 Word8
-- @
--
-- Now @blues@ just indexes each third byte on the same underlying
-- memory block:
--
-- @
-- ... b | ... b | ... b | ...
--   (0, 0)  (0, 1)  (0, 2)...
-- @
data FS

instance Shape sh => Regular FS L sh e where

    data UArray FS L sh e =
        ForeignSlice
            !sh              -- Extent
            !Int             -- Size of a vector in the parent array (in bytes)
            {-# NOUNPACK #-}
            !(ForeignPtr e)  -- Foreign ptr for GC
            !(Ptr e)         -- Plain ptr for fast memory access
    
    extent (ForeignSlice sh _ _ _) = sh
    touchArray (ForeignSlice _ _ fptr _) = touchForeignPtr fptr
    
    {-# INLINE extent #-}
    {-# INLINE touchArray #-}

instance Shape sh => NFData (UArray FS L sh e) where
    rnf (ForeignSlice sh vsize fptr ptr) =
        sh `deepseq` vsize `seq` fptr `seq` ptr `seq` ()

instance (Shape sh, Storable e) => USource FS L sh e where
    linearIndex (ForeignSlice _ vsize _ ptr) i = peekByteOff ptr (i * vsize)
    {-# INLINE linearIndex #-}

instance DefaultFusion FS D L


instance (Shape sh, Vector v e, Storable e) => VecRegular F FS L sh v e where
    slices (ForeignArray sh fptr ptr) =
        let esize = sizeOf (undefined :: e)
            vsize = sizeOf (undefined :: (v e))
            eptr = castPtr ptr
            feptr = castForeignPtr fptr
        in V.generate $ \i ->
            ForeignSlice sh vsize feptr (eptr `plusPtr` (i * esize))
    {-# INLINE slices #-}

instance (Shape sh, Vector v e, Storable e) => UVecSource F FS L sh v e

instance (Shape sh, Vector v e, Storable e) => UVecSource (SE F) F L sh v e


instance (Shape sh, Storable a) => UTarget F L sh a where
    linearWrite (ForeignArray _ _ ptr) i x = pokeElemOff ptr i x
    {-# INLINE linearWrite #-}

instance (Shape sh, Storable a) => Manifest F F L sh a where
    new sh = do
        arr <- internalNew mallocBytes sh
        arr `deepseq` return ()
        return arr

    freeze = return
    thaw = return
    
    {-# INLINE new #-}
    {-# INLINE freeze #-}
    {-# INLINE thaw #-}

-- | /O(1)/ allocates zero-initialized foreign array.
-- 
-- Needed because common 'new' function allocates array with garbage.
newEmpty :: (Shape sh, Storable a, Integral a) => sh -> IO (UArray F L sh a)
{-# INLINE newEmpty #-}
newEmpty sh = do
    arr <- internalNew callocBytes sh
    arr `deepseq` return ()
    return arr

internalNew
    :: forall sh a. (Shape sh, Storable a)
    => (Int -> IO (Ptr a)) -> sh -> IO (UArray F L sh a)
{-# NOINLINE internalNew #-}
internalNew allocBytes sh = do
    let len = size sh
    ptr <- allocBytes (len * sizeOf (undefined :: a))
    fptr <- newForeignPtr finalizerFree (castPtr ptr)
    return $ ForeignArray sh fptr ptr


instance (Shape sh, Storable e) => UTarget FS L sh e where
    linearWrite (ForeignSlice _ vsize _ ptr) i x =
        pokeByteOff ptr (i * vsize) x
    {-# INLINE linearWrite #-}

instance (Shape sh, Vector v e, Storable e) => UVecTarget F FS L sh v e

-- | /O(1)/ Returns pointer to memory block used by the given foreign
-- array.
--
-- May be useful to reuse memory if you don't longer need the given array
-- in the program:
--
-- @
-- brandNewData <-
--    'unsafeFromForeignPtr' ext ('castForeignPtr' (toForeignPtr arr))
-- @
toForeignPtr :: Shape sh => UArray F L sh a -> ForeignPtr a
{-# INLINE toForeignPtr #-}
toForeignPtr (ForeignArray _ fptr _) = fptr

-- | /O(1)/ Wraps foreign ptr into foreign array.
-- 
-- The function is unsafe because it simply don't (and can't)
-- check anything about correctness of produced array.
unsafeFromForeignPtr :: Shape sh => sh -> ForeignPtr a -> IO (UArray F L sh a)
{-# INLINE unsafeFromForeignPtr #-}
unsafeFromForeignPtr sh fptr =
    withForeignPtr fptr (\ptr -> return $ ForeignArray sh fptr ptr)