module Data.Yarr.Repr.Separate (
    -- * Separate representation
    SE, fromSlices, unsafeMapSlices,
    Data.Yarr.Repr.Separate.convert,

    -- * Element-wise fusion for arrays of vectors
    dmapElems, dmapElemsM,
    dzipElems2, dzipElems2M, dzipElems3, dzipElems3M,
    dzipElems, dzipElemsM,

    -- * Non-injective element-wise fusion
    fmapElems, fmapElemsM,
    fzipElems2, fzipElems2M, fzipElems3, fzipElems3M,
    fzipElems, fzipElemsM
) where

import Prelude as P
import Data.Functor ((<$>))

import Data.Yarr.Base as B
import Data.Yarr.Shape
import Data.Yarr.Repr.Delayed
import Data.Yarr.Utils.FixedVector as V

-- | SEparate meta array representation. Internally SEparate arrays
-- hold vector of it's slices (so, 'slices' is just getter for them).
--
-- Mostly useful for:
--
--  * Separate in memory manifest 'Data.Yarr.F'oreign arrays (\"Unboxed\" arrays
--    in @vector@/@repa@ libraries terms).
-- 
--  * Element-wise vector array fusion (see group of 'dmapElems' functions).
data SE r

instance (Regular r l sh e, Vector v e) => Regular (SE r) l sh (v e) where

    data UArray (SE r) l sh (v e) =
        Separate !sh (VecList (Dim v) (UArray r l sh e))

    extent (Separate sh _) = sh
    touchArray (Separate _ slices) = V.mapM_ touchArray slices
    force (Separate sh slices) = do
        sh `deepseq` return ()
        V.mapM_ force slices

    {-# INLINE extent #-}
    {-# INLINE touchArray #-}
    {-# INLINE force #-}

instance (NFData (UArray r l sh e), Shape sh, Vector v e) =>
        NFData (UArray (SE r) l sh (v e)) where
    rnf (Separate sh slices) = sh `deepseq` slices `deepseq` ()

instance (Regular r l sh e, Shape sh, Vector v e) =>
        VecRegular (SE r) r l sh v e where
    slices (Separate _ slices) = slices
    {-# INLINE slices #-}


instance (USource r l sh e, Vector v e) => USource (SE r) l sh (v e) where
    index (Separate _ slices) sh =
        V.convert <$> V.mapM (\el -> index el sh) slices
    linearIndex (Separate _ slices) i =
        V.convert <$> V.mapM (\el -> linearIndex el i) slices
    {-# INLINE index #-}
    {-# INLINE linearIndex #-}

instance (USource r l sh e, Vector v e) => UVecSource (SE r) r l sh v e

instance (DefaultFusion r D l, Fusion (SE r) D l) => DefaultFusion (SE r) D l


-- | Group of @f-...-Elems-@ functions is used internally to define
-- @d-...-Elems-@ functions.
fmapElems
    :: (VecRegular r slr l sh v a,
        USource slr l sh a, USource fslr l sh b, Fusion slr fslr l,
        Vector v2 b, Dim v ~ Dim v2)
    => VecList (Dim v) (a -> b) -- ^ .
    -> UArray r l sh (v a)
    -> UArray (SE fslr) l sh (v2 b)
fmapElems fs = fmapElemsM $ V.map (return .) fs

fmapElemsM
    :: (VecRegular r slr l sh v a,
        USource slr l sh a, USource fslr l sh b, Fusion slr fslr l,
        Vector v2 b, Dim v ~ Dim v2)
    => VecList (Dim v) (a -> IO b) -- ^ .
    -> UArray r l sh (v a)
    -> UArray (SE fslr) l sh (v2 b)
fmapElemsM fs arr = Separate (extent arr) $ V.zipWith fmapM fs (slices arr)


fzipElems2
    :: (VecRegular r slr l sh v a, USource slr l sh a,
        VecRegular r slr l sh v b, USource slr l sh b,
        USource fslr l sh c, Fusion slr fslr l, Vector v c)
    => VecList (Dim v) (a -> b -> c) -- ^ .
    -> UArray r l sh (v a)
    -> UArray r l sh (v b)
    -> UArray (SE fslr) l sh (v c)
fzipElems2 fs arr1 arr2 =
    let fMs = V.map (\f -> \x y -> return (f x y)) fs
    in fzipElems2M fMs arr1 arr2

fzipElems2M
    :: (VecRegular r slr l sh v a, USource slr l sh a,
        VecRegular r slr l sh v b, USource slr l sh b,
        USource fslr l sh c, Fusion slr fslr l, Vector v c)
    => VecList (Dim v) (a -> b -> IO c) -- ^ .
    -> UArray r l sh (v a)
    -> UArray r l sh (v b)
    -> UArray (SE fslr) l sh (v c)
fzipElems2M fs arr1 arr2 =
    let sh = intersect (vl_2 (extent arr1) (extent arr2))
        slices1 = slices arr1
        slices2 = slices arr2
        {-# INLINE makeSlice #-}
        makeSlice i f =
            let sl1 = slices1 V.! i
                sl2 = slices2 V.! i
            in fzip2M f sl1 sl2
    in Separate sh $ V.imap makeSlice fs

fzipElems3
    :: (VecRegular r slr l sh v a, USource slr l sh a,
        VecRegular r slr l sh v b, USource slr l sh b,
        VecRegular r slr l sh v c, USource slr l sh c,
        USource fslr l sh d, Fusion slr fslr l, Vector v d)
    => VecList (Dim v) (a -> b -> c -> d) -- ^ .
    -> UArray r l sh (v a)
    -> UArray r l sh (v b)
    -> UArray r l sh (v c)
    -> UArray (SE fslr) l sh (v d)
fzipElems3 fs arr1 arr2 arr3 =
    let fMs = V.map (\f -> \x y z -> return (f x y z)) fs
    in fzipElems3M fMs arr1 arr2 arr3

fzipElems3M
    :: (VecRegular r slr l sh v a, USource slr l sh a,
        VecRegular r slr l sh v b, USource slr l sh b,
        VecRegular r slr l sh v c, USource slr l sh c,
        USource fslr l sh d, Fusion slr fslr l, Vector v d)
    => VecList (Dim v) (a -> b -> c -> IO d) -- ^ .
    -> UArray r l sh (v a)
    -> UArray r l sh (v b)
    -> UArray r l sh (v c)
    -> UArray (SE fslr) l sh (v d)
fzipElems3M fs arr1 arr2 arr3 =
    let sh = intersect (vl_3 (extent arr1) (extent arr2) (extent arr3))
        slices1 = slices arr1
        slices2 = slices arr2
        slices3 = slices arr3
        {-# INLINE makeSlice #-}
        makeSlice i f =
            let sl1 = slices1 V.! i
                sl2 = slices2 V.! i
                sl3 = slices3 V.! i
            in fzip3M f sl1 sl2 sl3
    in Separate sh $ V.imap makeSlice fs


fzipElems
    :: (Vector v2 b, Arity m, m ~ S m0,
        VecRegular r slr l sh v a,
        USource slr l sh a, USource fslr l sh b, Fusion slr fslr l)
    => VecList (Dim v2) (Fun m a b) -- ^ .
    -> VecList m (UArray r l sh (v a))
    -> UArray (SE fslr) l sh (v2 b)
fzipElems funs arrs =
    let funMs = V.map (P.fmap return) funs
    in fzipElemsM funMs arrs

fzipElemsM
    :: (Vector v2 b, Arity m, m ~ S m0,
        VecRegular r slr l sh v a,
        USource slr l sh a, USource fslr l sh b, Fusion slr fslr l)
    => VecList (Dim v2) (Fun m a (IO b)) -- ^ .
    -> VecList m (UArray r l sh (v a))
    -> UArray (SE fslr) l sh (v2 b)
fzipElemsM funs arrs =
    let sh = intersect $ V.map extent arrs
        !allSlices = V.map slices arrs
        {-# INLINE makeSlice #-}
        makeSlice i fun =
            let slices = V.map (V.! i) allSlices
            in fzipM fun slices
    in Separate sh $ V.imap makeSlice funs

{-# INLINE fmapElems #-}
{-# INLINE fmapElemsM #-}
{-# INLINE fzipElems2 #-}
{-# INLINE fzipElems2M #-}
{-# INLINE fzipElems3 #-}
{-# INLINE fzipElems3M #-}
{-# INLINE fzipElems #-}
{-# INLINE fzipElemsM #-}

-- | /O(1)/ Injective element-wise fusion (mapping).
--
-- Example:
--
-- @
-- let domainHSVImage =
--         dmapElems ('vl_3' (* 360) (* 100) (* 100))
--                   normedHSVImage
-- @
--
-- Also, used internally to define 'Data.Yarr.Flow.mapElems' function.
dmapElems
    :: (VecRegular r slr l sh v a,
        USource slr l sh a, USource fslr l sh b, DefaultFusion slr fslr l,
        Vector v2 b, Dim v ~ Dim v2)
    => VecList (Dim v) (a -> b)     -- ^ Vector of mapper functions
    -> UArray r l sh (v a)          -- ^ Source array of vectors
    -> UArray (SE fslr) l sh (v2 b) -- ^ Fused array
dmapElems = fmapElems

-- | /O(1)/ Monadic vesion of 'dmapElems' function.
dmapElemsM
    :: (VecRegular r slr l sh v a,
        USource slr l sh a, USource fslr l sh b, DefaultFusion slr fslr l,
        Vector v2 b, Dim v ~ Dim v2)
    => VecList (Dim v) (a -> IO b)  -- ^ Elemen-wise vector of monadic mappers
    -> UArray r l sh (v a)          -- ^ Source array of vectors
    -> UArray (SE fslr) l sh (v2 b) -- ^ Result array
dmapElemsM = fmapElemsM


dzipElems2
    :: (VecRegular r slr l sh v a, USource slr l sh a,
        VecRegular r slr l sh v b, USource slr l sh b,
        USource fslr l sh c, DefaultFusion slr fslr l, Vector v c)
    => VecList (Dim v) (a -> b -> c) -- ^ .
    -> UArray r l sh (v a)
    -> UArray r l sh (v b)
    -> UArray (SE fslr) l sh (v c)
dzipElems2 = fzipElems2

dzipElems2M
    :: (VecRegular r slr l sh v a, USource slr l sh a,
        VecRegular r slr l sh v b, USource slr l sh b,
        USource fslr l sh c, DefaultFusion slr fslr l, Vector v c)
    => VecList (Dim v) (a -> b -> IO c) -- ^ .
    -> UArray r l sh (v a)
    -> UArray r l sh (v b)
    -> UArray (SE fslr) l sh (v c)
dzipElems2M = fzipElems2M

dzipElems3
    :: (VecRegular r slr l sh v a, USource slr l sh a,
        VecRegular r slr l sh v b, USource slr l sh b,
        VecRegular r slr l sh v c, USource slr l sh c,
        USource fslr l sh d, DefaultFusion slr fslr l, Vector v d)
    => VecList (Dim v) (a -> b -> c -> d) -- ^ .
    -> UArray r l sh (v a)
    -> UArray r l sh (v b)
    -> UArray r l sh (v c)
    -> UArray (SE fslr) l sh (v d)
dzipElems3 = fzipElems3

dzipElems3M
    :: (VecRegular r slr l sh v a, USource slr l sh a,
        VecRegular r slr l sh v b, USource slr l sh b,
        VecRegular r slr l sh v c, USource slr l sh c,
        USource fslr l sh d, DefaultFusion slr fslr l, Vector v d)
    => VecList (Dim v) (a -> b -> c -> IO d) -- ^ .
    -> UArray r l sh (v a)
    -> UArray r l sh (v b)
    -> UArray r l sh (v c)
    -> UArray (SE fslr) l sh (v d)
dzipElems3M = fzipElems3M



-- | /O(1)/ Generalized element-wise zipping of several arrays of vectors.
dzipElems
    :: (Vector v2 b, Arity m, m ~ S m0,
        VecRegular r slr l sh v a,
        USource slr l sh a, USource fslr l sh b, DefaultFusion slr fslr l)
    => VecList (Dim v2) (Fun m a b)    -- ^ Vector of wrapped @m-@ary element-wise zippers
    -> VecList m (UArray r l sh (v a)) -- ^ Vector of source arrays of vectors
    -> UArray (SE fslr) l sh (v2 b)    -- ^ Fused result array
dzipElems = fzipElems

-- | /O(1)/ Generalized monadic element-wise zipping of several arrays of vectors
dzipElemsM
    :: (Vector v2 b, Arity m, m ~ S m0,
        VecRegular r slr l sh v a,
        USource slr l sh a, USource fslr l sh b, DefaultFusion slr fslr l)
    => VecList (Dim v2) (Fun m a (IO b)) -- ^ Vector of wrapped @m-@ary
                                         -- element-wise monadic zippers
    -> VecList m (UArray r l sh (v a))   -- ^ Vector of source arrays of vectors
    -> UArray (SE fslr) l sh (v2 b)      -- ^ Result array
dzipElemsM = fzipElemsM

{-# INLINE dmapElems #-}
{-# INLINE dmapElemsM #-}
{-# INLINE dzipElems2 #-}
{-# INLINE dzipElems2M #-}
{-# INLINE dzipElems3 #-}
{-# INLINE dzipElems3M #-}
{-# INLINE dzipElems #-}
{-# INLINE dzipElemsM #-}




instance (UTarget tr tl sh e, Vector v e) => UTarget (SE tr) tl sh (v e) where
    write (Separate _ slices) sh v =
        V.zipWithM_ (\el x -> write el sh x) slices (V.convert v)
    linearWrite (Separate _ slices) i v =
        V.zipWithM_ (\el x -> linearWrite el i x) slices (V.convert v)
    {-# INLINE write #-}
    {-# INLINE linearWrite #-}

instance (Manifest r mr l sh e, Vector v e) =>
        Manifest (SE r) (SE mr) l sh (v e) where
    new sh = P.fmap (Separate sh) (V.replicateM (B.new sh))
    freeze (Separate sh mslices) = P.fmap (Separate sh) (V.mapM freeze mslices)
    thaw (Separate sh slices) = P.fmap (Separate sh) (V.mapM thaw slices)
    {-# INLINE new #-}
    {-# INLINE freeze #-}
    {-# INLINE thaw #-}

instance (UTarget tr tl sh e, Vector v e) => UVecTarget (SE tr) tr tl sh v e

-- | /O(1)/ Glues several arrays of the same type
-- into one separate array of vectors.
-- All source arrays must be of the same extent.
--
-- Example:
--
-- @let separateCoords = fromSlices ('vl_3' xs ys zs)@
fromSlices
    :: (Regular r l sh e, Vector v e, Dim v ~ S n0)
    => VecList (Dim v) (UArray r l sh e)
    -> UArray (SE r) l sh (v e)
{-# INLINE fromSlices #-}
fromSlices slices =
    let shapes = V.map extent slices
        sh0 = V.head shapes
    in if V.any (/= sh0) shapes
            then error "Separate Repr: all slices must be of the same extent"
            else Separate sh0 slices

-- | /O(depends on mapper function)/
-- Maps slices of separate array \"entirely\".
-- 
-- This function is useful when operation over slices is not
-- element-wise (in that case you should use 'Data.Yarr.Flow.mapElems'):
--
-- @let blurredImage = unsafeMapSlices blur image@
--
-- The function is unsafe because it doesn't check that slice mapper
-- translates extents uniformly (though it is pure).
unsafeMapSlices
    :: (USource r l sh a, Vector v a,
        USource r2 l2 sh2 b, Vector v b, Dim v ~ S n0)
    => (UArray r l sh a -> UArray r2 l2 sh2 b)
        -- ^ Slice mapper without restrictions
    -> UArray (SE r) l sh (v a)    -- ^ Source separate array
    -> UArray (SE r2) l2 sh2 (v b) -- ^ Result separate array
{-# INLINE unsafeMapSlices #-}
unsafeMapSlices f (Separate sh slices) =
    let slices' = V.map f slices
    in Separate (extent (V.head slices')) slices'

-- | /O(0)/ Converts separate vector between vector types of the same arity.
--
-- Example:
--
-- @
-- -- floatPairs :: 'UArray' ('SE' 'Data.Yarr.F') 'Dim1' ('VecList' 'N2' Float)
-- let cs :: 'UArray' ('SE' 'Data.Yarr.F') 'Dim1' ('Data.Complex.Complex' Float)
--     cs = convert floatPairs
-- @
convert
    :: (Regular r l sh e, Vector v e, Vector v2 e, Dim v ~ Dim v2)
    => UArray (SE r) l sh (v e) -> UArray (SE r) l sh (v2 e)
{-# INLINE convert #-}
convert (Separate sh slices) = Separate sh slices