-- | Repa Array API that automatically choses an array layout based
--   on the element type.
--
--   This is a re-export of the module "Data.Repa.Array".
--
module Data.Repa.Array.Auto.Operator
        ( Array 
        , Elem, Build

        -- * Basics
        , index
        , (!)
        , length
        , first, last
        , head,  tail, init

        -- * Construction
        , empty
        , singleton
        , generateMaybeS,  mapMaybeS
        , generateEitherS, mapEitherS

        -- * Conversion
        , fromList
        , fromLists
        , fromListss

        , toList
        , toLists
        , toListss

        -- * Operators

        -- ** Replicating
        , replicates

        -- ** Mapping
        , map
        , map2
        , mapElems

        -- ** Folding
        , foldl
        , sum,  product
        , mean, std

        -- *** Special Folds
        , correlate
        , folds
        , foldsWith

        -- ** Filtering
        , filter
        , slices
        , trims
        , trimEnds
        , trimStarts

        -- ** Zipping
        , zip
        , unzip

        -- ** Sloshing
        , reverse
        , concat
        , concats
        , concatWith
        , unlines
        , intercalate
        , ragspose3

        -- ** Slicing
        , slice

        -- ** Inserting
        , insert

        -- ** Searching
        , findIndex

        -- ** Merging
        , merge
        , mergeMaybe

        -- ** Compacting
        , compact
        , compactIn

        -- ** Processing
        , process

        -- ** Grouping
        , groups
        , groupsWith

        -- ** Splitting
        , segment
        , segmentOn
        , dice
        , diceSep)
where
import Data.Repa.Array.Auto.Base
import Data.Repa.Array.Material.Auto                    (Name(..))
import Data.Repa.Array.Generic.Convert                  as A
import Control.Monad
import GHC.Exts                                         hiding (fromList, toList)
import qualified Data.Repa.Array.Generic                as G
import qualified Data.Repa.Array.Material.Auto          as A
import qualified Data.Repa.Array.Material.Nested        as N
import qualified Data.Repa.Array.Meta.Tuple             as A
import qualified Data.Repa.Array.Meta.Window            as A
import qualified Data.Repa.Array.Meta.Delayed           as A
import qualified Data.Repa.Array.Meta.Delayed2          as A
import qualified Data.Repa.Array.Internals.Bulk         as G
import qualified Data.Repa.Chain                        as C
import qualified Data.Vector.Unboxed                    as U
import Prelude 
       hiding   ( map,  length, reverse, filter, concat, unlines, foldl
                , sum,  product, zip, unzip
                , head, tail, init, last)


-- Basic ------------------------------------------------------------------------------------------
-- | O(1). Get an element from an array. 
--
--   If the provided index is outside the extent of the array then the
--   result depends on the layout.
index :: Elem a => Array a -> Int -> a
index  = (G.!)
{-# INLINE index #-}


-- | O(1). Alias for `index`
(!) :: Elem a => Array a -> Int -> a
(!)    = index
{-# INLINE (!) #-}


-- | O(1). Get the number of elements in an array.
length :: Elem a => Array a -> Int
length = G.length
{-# INLINE length #-}


-- | O(1). Take the first element of an array, or `Nothing` if it's empty.
first   :: Elem a => Array a -> Maybe a
first   = G.first
{-# INLINE first #-}


-- | O(1). Take the last element of an array, or `Nothing` if it's empty.
last    :: Elem a => Array a -> Maybe a
last    = G.last
{-# INLINE last #-}


-- | O(1). alias for `first`.
head    :: Elem a => Array a -> Maybe a
head    = G.first
{-# INLINE head #-}


-- | O(1). Take the tail of an array, or `Nothing` if it's empty.
tail    :: Elem a => Array a -> Maybe (Array a)
tail    = A.tail
{-# INLINE tail #-}


-- | O(1). Take the initial elements of an array, or `Nothing` if it's empty.
init    :: Elem a => Array a -> Maybe (Array a)
init    = A.init
{-# INLINE init #-}


-- Construction -----------------------------------------------------------------------------------
-- | O(1). An empty array of the given layout.
empty   :: Build a
        => Array a
empty = G.empty A
{-# INLINE empty #-}


-- | O(1). Create a new empty array containing a single element.
singleton 
        :: Build a
        => a -> Array a
singleton = G.singleton A
{-# INLINE singleton #-}


-- | Like `generateS` but use a function that produces Maybe an element.
--   If any element returns `Nothing`, then `Nothing` for the whole array.
generateMaybeS
        :: Build a
        => Int -> (Int -> Maybe a) 
        -> Maybe (Array a)
generateMaybeS = G.generateMaybeS A
{-# INLINE generateMaybeS #-}


-- | Apply a function to every element of an array, 
--   if any application returns `Nothing`, then `Nothing` for the whole result.
mapMaybeS 
        :: (Elem a, Build b)
        => (a -> Maybe b) 
        -> Array a
        -> Maybe (Array b)
mapMaybeS = G.mapMaybeS A
{-# INLINE mapMaybeS #-}


-- | Like `generateS` but use a function that produces Either some error
--   or an element. If any element returns `Nothing`, then `Nothing` for
--   the whole array.
generateEitherS
        :: Build a
        => Int -> (Int -> Either err a) 
        -> Either err (Array a)
generateEitherS = G.generateEitherS A
{-# INLINE generateEitherS #-}


-- | Apply a function to every element of an array, 
--   if any application returns `Left`, then `Left` for the whole result.
mapEitherS 
        :: (Elem a, Build b)
        => (a -> Either err b) 
        -> Array a
        -> Either err (Array b)
mapEitherS = G.mapEitherS A
{-# INLINE mapEitherS #-}


-- Conversion -------------------------------------------------------------------------------------
-- | Convert a list to an array.
fromList :: Build a 
         => [a] -> Array a
fromList = G.fromList A
{-# INLINE fromList #-}


-- | Convert a nested list to an array.
fromLists :: Build a
          => [[a]] -> Array (Array a)
fromLists xs  = convert $! N.fromLists A xs
{-# INLINE fromLists #-}


-- | Convert a triply nested list to a triply nested array.
fromListss :: Build a
           => [[[a]]] -> Array (Array (Array a))
fromListss xs = convert $! N.fromListss A xs
{-# INLINE fromListss #-}


-- | Convert an array to a list.
toList :: Elem a => Array a -> [a]
toList = G.toList
{-# INLINE toList #-}


-- | Convert a nested array to some lists.
toLists :: (Elem a, Elem (Array a)) 
        => Array (Array a) -> [[a]]
toLists = G.toLists
{-# INLINE toLists #-}


-- | Convert a triply nested array to a triply nested list.
toListss :: (Elem a, Elem (Array a), Elem (Array (Array a)))
         => Array (Array (Array a)) -> [[[a]]]
toListss = G.toListss
{-# INLINE toListss #-}


-- Index space ------------------------------------------------------------------------------------
-- | O(n). Reverse the elements of a list.
--
-- @
-- > toList $ reverse $ fromList [0 .. 10 :: Int]
-- [10,9,8,7,6,5,4,3,2,1,0]
-- @
--
reverse :: Build a => Array a -> Array a
reverse arr = G.computeS A $! A.reverse arr
{-# INLINE reverse #-}


-- Replicating ------------------------------------------------------------------------------------
-- | Segmented replicate.
replicates 
        :: (Elem a, Build a)
        => Array (Int, a) -> Array a

replicates arr
 = G.replicates A arr
{-# INLINE replicates #-}


-- Mapping ----------------------------------------------------------------------------------------
-- | Apply a function to all the elements of a list.
map     :: (Elem a, Build b)
        => (a -> b) -> Array a -> Array b
map f arr
 = G.computeS A $! A.map f arr
{-# INLINE map #-}


-- | Combine two arrays of the same length element-wise.
--
--   If the arrays don't have the same length then `Nothing`.
--
map2    :: (Elem a, Elem b, Build c)
        => (a -> b -> c) -> Array a -> Array b -> Maybe (Array c)
map2 f xs ys
 = liftM (G.computeS A) $! A.map2 f xs ys
{-# INLINE map2 #-}


-- | Apply a function to all the elements of a doubly nested
--   array, preserving the nesting structure. 
--
--   * This function has a non-standard time complexity.
--     As nested arrays use a segment descriptor based representation,
--     detatching and reattaching the nesting structure is a constant time
--     operation. However, the array passed to the worker function will
--     also contain any elements in the array representation that are 
--     not reachable from the segment descriptor. This matters if the 
--     source array was produced by a function that filters the segments
--     directly, like `slices`.
--
mapElems :: (Array a -> Array b)
         -> Array (Array a) -> (Array (Array b))
mapElems f (A.AArray_Array arr)
 = A.AArray_Array (N.mapElems f arr)
{-# INLINE mapElems #-}


-- Folding ----------------------------------------------------------------------------------------
-- | Left fold of all elements in an array.
foldl   :: Elem b
        => (a -> b -> a) -> a -> Array b -> a
foldl = G.foldl
{-# INLINE foldl #-}


-- | Yield the sum of the elements of an array.
sum    :: (Elem a, Num a) => Array a -> a
sum   = G.sum
{-# INLINE sum #-}


-- | Yield the product of the elements of an array.
product   :: (Elem a, Num a) => Array a -> a
product = G.product
{-# INLINE product #-}


-- | Yield the mean value of the elements of an array.
mean   :: (Elem a, Fractional a) 
        => Array a -> a
mean   = G.mean
{-# INLINE mean #-}


-- | Yield the standard deviation of the elements of an array
std    ::  (Elem a, Floating a)
        => Array a -> a
std     = G.std
{-# INLINE std #-}


-- | Compute the Pearson correlation of two arrays.
--
--   If the arrays differ in length then only the common
--   prefix is correlated.
--
correlate 
        :: (Elem a, Floating a)
        => Array a -> Array a -> a
correlate = G.correlate
{-# INLINE correlate #-}


-- | Segmented fold over vectors of segment lengths and input values.
--
--   * The total lengths of all segments need not match the length of the
--     input elements vector. The returned `C.Folds` state can be inspected
--     to determine whether all segments were completely folded, or the
--     vector of segment lengths or elements was too short relative to the
--     other.
--
folds   :: (Elem a, Build n, Build b)
        => (a -> b -> b)        -- ^ Worker function.
        -> b                    -- ^ Initial state when folding segments.
        -> Array (n, Int)       -- ^ Segment names and lengths.
        -> Array a              -- ^ Elements.
        -> (Array (n, b), C.Folds Int Int n a b)
folds f z lens vals
 = let  (arr', result) = G.folds A A f z lens vals
   in   (A.AArray_T2 arr', result)
{-# INLINE folds #-}


-- | Like `folds`, but take an initial state for the first segment.
--
foldsWith
        :: (Elem a, Build n, Build b)
        => (a -> b -> b)         -- ^ Worker function.
        -> b                     -- ^ Initial state when folding segments.
        -> Maybe (n, Int, b)     -- ^ Name, length and initial state for first segment.
        -> Array (n, Int)        -- ^ Segment names and lengths.
        -> Array a               -- ^ Elements.
        -> (Array (n, b), C.Folds Int Int n a b)
foldsWith f z start lens vals
 = let  (arr', result)  = G.foldsWith A A f z start lens vals
   in   (A.AArray_T2 arr', result)
{-# INLINE foldsWith #-}


-- Filtering --------------------------------------------------------------------------------------
-- | O(len src) Keep the elements of an array that match the given predicate.
filter  :: Build a
        => (a -> Bool) -> Array a -> Array a
filter = G.filter A
{-# INLINE filter #-}


-- | O(1). Produce a nested array by taking slices from some array of elements.
--   
--  * This is a constant time operation, as the representation for nested 
--    vectors just wraps the starts, lengths and elements vectors.
--
slices  :: Array Int            -- ^ Segment starting positions.
        -> Array Int            -- ^ Segment lengths.
        -> Array a              -- ^ Array elements.
        -> Array (Array a)

slices (A.AArray_Int starts) (A.AArray_Int lens) elems
        =  A.AArray_Array 
        $! N.slices starts lens elems
{-# INLINE slices #-}


-- | For each segment of a nested vector, trim elements off the start
--   and end of the segment that match the given predicate.
trims   :: Elem a
        => (a -> Bool)
        -> Array (Array a)
        -> Array (Array a)

trims f (A.AArray_Array arr)
        = A.AArray_Array $! N.trims f arr
{-# INLINE trims #-}


-- | For each segment of a nested array, trim elements off the end of 
--   the segment that match the given predicate.
trimEnds :: Elem a
         => (a -> Bool)
         -> Array (Array a)
         -> Array (Array a)

trimEnds f (A.AArray_Array arr)
        = A.AArray_Array $! N.trimEnds f arr
{-# INLINE trimEnds #-}


-- | For each segment of a nested array, trim elements off the start of
--   the segment that match the given predicate.
trimStarts :: Elem a
           => (a -> Bool)
           -> Array (Array a)
           -> Array (Array a)
trimStarts f (A.AArray_Array arr)
        = A.AArray_Array $! N.trimStarts f arr
{-# INLINE trimStarts #-}


-- Zipping ----------------------------------------------------------------------------------------
-- | O(1). Pack a pair of arrays to an array of pairs.
zip     :: (Elem a, Elem b) 
        => Array a -> Array b -> Array (a, b)
zip arr1 arr2
 = let  len     = max (length arr1) (length arr2)
        arr1'   = A.window 0 len arr1
        arr2'   = A.window 0 len arr2
   in   A.AArray_T2 (A.tup2 arr1' arr2')
{-# INLINE zip #-}


-- | O(1). Unpack an array of pairs to a pair of arrays.
unzip   :: (Elem a, Elem b)
        => Array (a, b) -> (Array a, Array b)
unzip arr@(A.AArray_T2 arr')
 = let  len     = length arr
        (arr1, arr2) = A.untup2 arr'
        arr1'   = A.window 0 len arr1
        arr2'   = A.window 0 len arr2
   in   (arr1', arr2')


-- Sloshing ---------------------------------------------------------------------------------------
-- | Concatenate nested arrays.
concat  :: (Elem a, Build a)
        => Array (Array a)      -- ^ Arrays to concatenate.
        -> Array a
concat arr 
        = (inline G.concat) A arr
{-# INLINABLE concat #-}


-- | O(len result) Concatenate the elements of some nested vector,
--   inserting a copy of the provided separator array between each element.
concatWith
        :: (Elem a, Build a)
        => Array a              -- ^ Separator array.
        -> Array (Array a)      -- ^ Arrays to concatenate.
        -> Array a
concatWith = G.concatWith A
{-# INLINE concatWith #-}


-- | O(len result) Concatenate the outer two layers of a triply nested array.
--   (Segmented concatenation).
--
--   * The operation is performed entirely on the segment descriptors of the 
--     array, and does not require the inner array elements to be copied.
--   * This version is faster than plain `concat` on triply nested arrays.
--
concats :: Array (Array (Array a))
        -> Array (Array a)

concats (A.AArray_Array (N.NArray starts1 lens1 (A.AArray_Array arr)))
 = A.AArray_Array $ N.concats (N.NArray starts1 lens1 arr)
{-# INLINE concats #-}


-- | O(len result) Perform a `concatWith`, adding a newline character to
--   the end of each inner array.
unlines :: Array (Array Char) -> Array Char
unlines = G.unlines A
{-# INLINE unlines #-}


-- | O(len result) Insert a copy of the separator array between the elements of
--   the second and concatenate the result.
intercalate 
        :: (Elem a, Build a)
        => Array a              -- ^ Separator array.
        -> Array (Array a)      -- ^ Arrays to concatenate.
        -> Array a
intercalate = G.intercalate A
{-# INLINE intercalate #-}


-- | Ragged transpose of a triply nested array.
-- 
--   * This operation is performed entirely on the segment descriptors
--     of the nested arrays, and does not require the inner array elements
--     to be copied.
--
ragspose3 :: Array (Array (Array a)) 
          -> Array (Array (Array a))

ragspose3 (A.AArray_Array (N.NArray starts0 lens0 (A.AArray_Array arr)))
 = let  N.NArray starts1 elems1 (N.NArray starts2 elems2 arr')
                = N.ragspose3 (N.NArray starts0 lens0 arr)

   in   A.AArray_Array 
                $! N.NArray starts1 elems1
                $! A.AArray_Array 
                $! N.NArray starts2 elems2 arr'
{-# INLINE ragspose3 #-}


-- Slicing ----------------------------------------------------------------------------------------
-- | Take a slice out of an array, given a starting position and length.
slice   :: Elem a => Int -> Int -> Array a -> Maybe (Array a)
slice from len arr
        | from >= 0, len >= 0
        , len  <= G.length arr - from
        = Just $ A.window from len arr

        | otherwise
        = Nothing
{-# INLINE slice #-}


-- Merging ----------------------------------------------------------------------------------------
-- | Merge two sorted key-value streams.
merge   :: (Ord k, Elem (k, a), Elem (k, b), Build (k, c))
        => (k -> a -> b -> c)   -- ^ Combine two values with the same key.
        -> (k -> a -> c)        -- ^ Handle a left value without a right value.
        -> (k -> b -> c)        -- ^ Handle a right value without a left value.
        -> Array (k, a)         -- ^ Array of keys and left values.
        -> Array (k, b)         -- ^ Array of keys and right values.
        -> Array (k, c)         -- ^ Array of keys and results.
merge = G.merge A
{-# INLINE merge #-}


-- | Like `merge`, but only produce the elements where the worker functions
--   return `Just`.
mergeMaybe 
        :: (Ord k, Elem (k, a), Elem (k, b), Build (k, c))
        => (k -> a -> b -> Maybe c) -- ^ Combine two values with the same key.
        -> (k -> a -> Maybe c)      -- ^ Handle a left value without a right value.
        -> (k -> b -> Maybe c)      -- ^ Handle a right value without a left value.
        -> Array (k, a)             -- ^ Array of keys and left values.
        -> Array (k, b)             -- ^ Array of keys and right values.
        -> Array (k, c)             -- ^ Array of keys and results.
mergeMaybe = G.mergeMaybe A
{-# INLINE mergeMaybe #-}


-- Splitting --------------------------------------------------------------------------------------
-- | Combination of `fold` and `filter`. 
--   
--   We walk over the stream front to back, maintaining an accumulator.
--   At each point we can chose to emit an element (or not)
--
compact :: (Elem a, Build b)
        => (s -> a -> (s, Maybe b))
        -> s
        -> Array a
        -> Array b
compact = G.compact A
{-# INLINE compact #-}


-- | Like `compact` but use the first value of the stream as the 
--   initial state, and add the final state to the end of the output.
compactIn
        :: Build a
        => (a -> a -> (a, Maybe a))
        -> Array a
        -> Array a
compactIn = G.compactIn A
{-# INLINE compactIn #-}


-- | Apply a generic stream process to an array.
process :: ( Build a, Build b, Elem b)
        => (s -> a -> (s, Array b))     -- ^ Worker function
        -> s                            -- ^ Initial state.
        -> Array a                      -- ^ Input array.
        -> (s, Array b)                 -- ^ Result state and array.
process   = G.process A
{-# INLINE process #-}


-- Inserting --------------------------------------------------------------------------------------
-- | Insert elements produced by the given function in to an array.
insert  :: Build a
        => (Int -> Maybe a) -> Array a -> Array a
insert = G.insert A
{-# INLINE insert #-}


-- Searching --------------------------------------------------------------------------------------
-- | O(len src) Yield `Just` the index of the first element matching the predicate
--   or `Nothing` if no such element exists.
findIndex :: Elem a => (a -> Bool) -> Array a -> Maybe Int
findIndex = G.findIndex 
{-# INLINE findIndex #-}


-- Splitting --------------------------------------------------------------------------------------
-- | O(len src). Given predicates which detect the start and end of a segment, 
--   split an vector into the indicated segments.
segment :: (Elem a, U.Unbox a)
        => (a -> Bool)  -- ^ Detect the start of a segment.
        -> (a -> Bool)  -- ^ Detect the end of a segment.
        -> Array a      -- ^ Array to segment.
        -> Array (Array a)  

segment pStart pEnd elems
        = A.AArray_Array $! N.segment pStart pEnd elems
{-# INLINE segment #-}


-- | O(len src). Given a terminating value, split an vector into segments.
--
--   The result segments do not include the terminator.
segmentOn 
        :: (Elem a, U.Unbox a)
        => (a -> Bool)  -- ^ Detect the end of a segment.
        -> Array a      -- ^ Array to segment.
        -> Array (Array a)

segmentOn pEnd arr
        = A.AArray_Array $! N.segmentOn pEnd arr
{-# INLINE segmentOn #-}


-- | O(len src). Like `segment`, but cut the source array twice.
dice    :: (Elem a, U.Unbox a)
        => (a -> Bool)  -- ^ Detect the start of an inner segment.
        -> (a -> Bool)  -- ^ Detect the end   of an inner segment.
        -> (a -> Bool)  -- ^ Detect the start of an outer segment.
        -> (a -> Bool)  -- ^ Detect the end   of an outer segment.
        -> Array a      -- ^ Array to dice.
        -> Array (Array (Array a))

dice pStart1 pEnd1 pStart2 pEnd2 arr
 = let  N.NArray starts1 elems1 (N.NArray starts2 elems2 arr')
                = N.dice pStart1 pEnd1 pStart2 pEnd2 arr
  
   in   A.AArray_Array 
                $! N.NArray starts1 elems1
                $! A.AArray_Array 
                $! N.NArray starts2 elems2 arr'
{-# INLINE dice #-}


-- | O(len src). Given field and row terminating values, 
--   split an array into rows and fields.
--
diceSep :: (Elem a, Eq a)
        => a            -- ^ Terminating element for inner segments.
        -> a            -- ^ Terminating element for outer segments.
        -> Array a      -- ^ Vector to dice.
        -> Array (Array (Array a))

diceSep xEndCol xEndRow arr
 = let  N.NArray starts1 elems1 (N.NArray starts2 elems2 arr')
                = N.diceSep xEndCol xEndRow arr
  
   in   A.AArray_Array 
                $! N.NArray starts1 elems1
                $! A.AArray_Array 
                $! N.NArray starts2 elems2 arr'
{-# INLINE diceSep #-}


-- Grouping ---------------------------------------------------------------------------------------
-- | From a stream of values which has consecutive runs of idential values,
--   produce a stream of the lengths of these runs.
groups  :: (Eq a, Build a)
        => Array a              -- ^ Input elements.
        -> (Array (a, Int), Maybe (a, Int))
                                -- ^ Completed and final segment lengths.
groups arr
 = let  (arr', result) = G.groups A A arr
   in   (A.AArray_T2 arr', result)
{-# INLINE groups #-}


-- | Like `groups`, but use the given function to determine whether two
--   consecutive elements should be in the same group. 
--   Also take an initial starting group and count.
groupsWith
        :: Build a
        => (a -> a -> Bool)     -- ^ Comparison function.
        -> Maybe  (a, Int)      -- ^ Starting element and count.
        -> Array  a             -- ^ Input elements.
        -> (Array (a, Int), Maybe (a, Int))     
                                -- ^ Completed and final segment lengths.
groupsWith f start arr
 = let  (arr', result) = G.groupsWith A A f start arr
   in   (A.AArray_T2 arr', result)
{-# INLINE groupsWith #-}