module Data.Array.Repa.Series.Series
        ( Series (..)
        , index
        , length
        , toVector
        , runSeries
        , runSeries2
        , runSeries3
        , runSeries4)
where
import qualified Data.Array.Repa.Series.Vector  as V
import Data.Array.Repa.Series.Vector            (Vector)

import qualified Data.Vector.Unboxed            as U
import qualified Data.Vector.Unboxed.Mutable    as UM
import Data.Vector.Unboxed                      (Unbox)
import System.IO.Unsafe
import GHC.Exts
import Prelude hiding (length)


-- Series ---------------------------------------------------------------------
-- | A `Series` is an abstract source of element data and is consumed
--   by series processes. The elements of a series must be consumed
--   sequentially, so they don't support random access indexing.
--
--   The rate parameter @k@ represents the abstract length of the series.
data Series k a
        = Series 
        { seriesLength  :: Int#
        , seriesVector  :: !(U.Vector a) }  


-- | Index into a series.
index :: Unbox a => Series k a -> Int# -> a
index s ix
        = U.unsafeIndex (seriesVector s) (I# ix)
{-# INLINE [1] index #-}


-- | Take the length of a series.
length :: Series k a -> Int#
length (Series len d) = len
{-# INLINE [1] length #-}


-- | Convert a series to a vector, discarding the rate information.
toVector :: Unbox a => Series k a -> Vector a
toVector (Series len vec) 
 = unsafePerformIO
 $ do   V.fromUnboxed vec
{-# INLINE [1] toVector #-}


-------------------------------------------------------------------------------
-- | Evaluate a series expression, feeding it an unboxed vector.
--
--   The rate variable @k@ represents the length of the series.
runSeries
        :: Unbox a 
        => Vector a 
        -> (forall k. Series k a -> b)                  -- ^ worker function
        -> b

runSeries vec f
 | len       <- V.length vec
 = unsafePerformIO
 $ do   uvec    <- V.toUnboxed vec
        return  $ f (Series len uvec)
{-# INLINE [1] runSeries #-}


-- | Evaluate a series expression, feeding it two unboxed vectors
--   of the same length.
runSeries2 
        :: (Unbox a, Unbox b)
        => Vector a
        -> Vector b
        -> (forall k. Series k a -> Series k b -> c)    -- ^ worker function
        -> Maybe c

runSeries2 vec1 vec2 f
 | len1      <- V.length vec1
 , len2      <- V.length vec2
 , len1 ==# len2
 = unsafePerformIO
 $ do   uvec1   <- V.toUnboxed vec1
        uvec2   <- V.toUnboxed vec2
        return  $ Just (f (Series len1 uvec1) (Series len2 uvec2))

 | otherwise
 = Nothing
{-# INLINE [1] runSeries2 #-}


-- | Three!
runSeries3 
        :: (Unbox a, Unbox b, Unbox c)
        => Vector a
        -> Vector b
        -> Vector c
        -> (forall k. Series k a -> Series k b -> Series k c -> d)    -- ^ worker function
        -> Maybe d

runSeries3 vec1 vec2 vec3 f
 | len1      <- V.length vec1
 , len2      <- V.length vec2
 , len3      <- V.length vec3
 , len1 ==# len2
 , len2 ==# len3
 = unsafePerformIO
 $ do   uvec1   <- V.toUnboxed vec1
        uvec2   <- V.toUnboxed vec2
        uvec3   <- V.toUnboxed vec3
        return  $ Just (f (Series len1 uvec1) (Series len2 uvec2) (Series len3 uvec3))

 | otherwise
 = Nothing
{-# INLINE [1] runSeries3 #-}


-- | Four!
runSeries4 
        :: (Unbox a, Unbox b, Unbox c, Unbox d)
        => Vector a
        -> Vector b
        -> Vector c
        -> Vector d
        -> (forall k. Series k a -> Series k b -> Series k c -> Series k d -> e)    -- ^ worker function
        -> Maybe e

runSeries4 vec1 vec2 vec3 vec4 f
 | len1      <- V.length vec1
 , len2      <- V.length vec2
 , len3      <- V.length vec3
 , len4      <- V.length vec4
 , len1 ==# len2
 , len2 ==# len3
 , len3 ==# len4
 = unsafePerformIO
 $ do   uvec1   <- V.toUnboxed vec1
        uvec2   <- V.toUnboxed vec2
        uvec3   <- V.toUnboxed vec3
        uvec4   <- V.toUnboxed vec4
        return  $ Just (f (Series len1 uvec1) (Series len2 uvec2) (Series len3 uvec3) (Series len4 uvec4))

 | otherwise
 = Nothing
{-# INLINE [1] runSeries4 #-}