{-# LANGUAGE CPP           #-}
{-# LANGUAGE BangPatterns  #-}
{-# LANGUAGE TypeOperators #-}
-----------------------------------------------------------------------------
-- |
-- Module      : Data.Array.Vector.Strict.Basics
-- Copyright   : (c) [2001..2002] Manuel M T Chakravarty & Gabriele Keller
--               (c) 2006         Manuel M T Chakravarty & Roman Leshchinskiy
-- License     : see libraries/ndp/LICENSE
-- 
-- Maintainer  : Roman Leshchinskiy <rl@cse.unsw.edu.au>
-- Stability   : internal
-- Portability : portable
--
-- Description ---------------------------------------------------------------
--
--  Basic operations on flat unlifted arrays.
--
-- Todo ----------------------------------------------------------------------
--


#include "fusion-phases.h"

module Data.Array.Vector.Strict.Basics where

import Data.Array.Vector.Stream
import Data.Array.Vector.UArr hiding (lengthU, indexU)
import qualified Data.Array.Vector.UArr as Prim (lengthU, indexU)

import Data.Array.Vector.Strict.Stream

import Data.Array.Vector.Prim.Debug
import Data.Array.Vector.Prim.Hyperstrict
import GHC.ST

import Debug.Trace

------------------------------------------------------------------------

instance (Eq e, UA e) => Eq (UArr e) where (==) = eqU
   -- not really fusible

{-# INLINE_U eqU #-}
eqU :: (Eq e, UA e) => UArr e -> UArr e -> Bool
eqU a1 a2 = lengthU a1 == lengthU a2 && foldlU cmp True (zipU a1 a2)
   where
       cmp r (e1 :*: e2) = e1 == e2 && r

------------------------------------------------------------------------

-- XXX general rule:
-- If we have direct implementations with better complexity than streams:
--
--   length, index, take, drop
--
-- Then use the direct version


-- |/O(1)/. 'lengthU' returns the length of a 'UArr' as an 'Int'.
lengthU :: UA e => UArr e -> Int
lengthU = foldlU (const . (+1)) 0
-- lengthU = Prim.lengthU
{-# INLINE_U lengthU #-}

{-

Unfused version:

$ time ./length
100000000
./length  1.10s user 1.13s system 91% cpu 2.433 total

Fusible version:

100000000
./length  0.31s user 0.00s system 97% cpu 0.318 total

-}

-- lengthU_stream :: UA e => UArr e -> Int
-- {-# INLINE lengthU_stream #-}

-- lengthU is reexported from UArr

-- |/O(1)/. 'nullU' tests whether the given array is empty.
--
nullU :: UA e => UArr e -> Bool
nullU  = nullS . streamU  -- better code if we short circuit
{-# INLINE_U nullU #-}
-- nullU  = (== 0) . lengthU

-- |/O(1)/. 'emptyU' yields an empty array.
--
emptyU :: UA e => UArr e
emptyU = unstreamU emptyS
{-# INLINE_U emptyU #-}

-- emptyU = newU 0 (const $ return ())

-- |/O(1)/. 'singletonU' yields a singleton array containing the given element.
--
singletonU :: UA e => e -> UArr e
{-# INLINE_U singletonU #-}
singletonU = unstreamU . singletonS

-- |/O(n)/. 'consU' prepends the given element to an array.
--
consU :: UA e => e -> UArr e -> UArr e
{-# INLINE_U consU #-}
consU x = unstreamU . consS x . streamU

-- |/O(n)/. 'snocU' appends the given element to an array.
--
snocU :: UA e => UArr e -> e -> UArr e
{-# INLINE_U snocU #-}
snocU s x = unstreamU (snocS (streamU s) x)

-- unitsU is reexported from Loop

-- |/O(n)/. @'replicateU' n e@ yields an array containing @n@ repetitions of @e@.
--
replicateU :: UA e => Int -> e -> UArr e
{-# INLINE_U replicateU #-}
replicateU n e = unstreamU (replicateS n e)

-- |/O(n)/. @'replicateEachU' n r e@ yields an array such that each element in
-- @e@ is repeated as many times as the value contained at the corresponding
-- index in @r@. For example:
--
-- @replicateEachU 10 (toU [1..3]) (toU [3..5])@ yields @toU [3.0,4.0,4.0,5.0,5.0,5.0]@
--
-- /N.B/: the @n@ parameter specifies how many elements are /allocated/ for the
-- output array, but the function will happily overrun the allocated buffer for
-- all sorts of interesting effects! The caller is expected to ensure that 
-- @n <= sumU r@.
--
replicateEachU :: UA e => Int -> UArr Int -> UArr e -> UArr e
{-# INLINE_U replicateEachU #-}
replicateEachU n ns es = unstreamU
                       . replicateEachS n
                       $ zipS (streamU ns) (streamU es)

-- | 'indexU' extracts an element out of an immutable unboxed array.
--
-- TODO: use indexU, the non-streaming version.
--
indexU :: UA e => UArr e -> Int -> e
indexU arr n = indexS (streamU arr) n
{-# INLINE_U indexU #-}

-- |/O(1)/. 'headU' yields the first element of an array.
--
headU :: UA e => UArr e -> e
headU = headS . streamU
{-# INLINE_U headU #-}

-- |/O(n)/. 'lastU' yields the last element of an array.
--
lastU :: UA e => UArr e -> e
lastU = foldl1U (flip const)
{-# INLINE lastU #-}

-- |/O(n)/. 'appendU' concatenates two arrays.
--
appendU :: UA e => UArr e -> UArr e -> UArr e
{-# INLINE_U appendU #-}
a1 `appendU` a2 = unstreamU (streamU a1 +++ streamU a2)

-- |/O(n)/. Concatenate a list of arrays.
concatU :: UA e => [UArr e] -> UArr e
concatU as = unstreamU (foldr (+++) emptyS (map streamU as))
{-# INLINE concatU #-}

-- |/O(n)/. 'initU' yields the input array without its last element.
initU :: UA e => UArr e -> UArr e -- not unboxing
initU = unstreamU . initS . streamU
{-# INLINE initU #-}

-- |/O(n)/. @'repeatU' n u@ repeats an array @u@ @n@ times.
--
repeatU :: UA e => Int -> UArr e -> UArr e
repeatU n = unstreamU . repS n
{-# INLINE_U repeatU #-}

-- No work duplicated
repS :: UA e => Int -> UArr e -> Stream e
{-# INLINE_STREAM repS #-}
repS k xs = Stream next (0 :*: k) (k*n)
  where
    n = lengthU xs

    {-# INLINE next #-}
    next (i :*: 0) = Done
    next (i :*: k) | i == n    = Skip (0 :*: k-1)
                   | otherwise = Yield (xs `Prim.indexU` i) (i+1 :*: k)

-- *Indexing
-- ---------

-- |/O(n)/. 'indexedU' associates each element of the array with its index.
--
indexedU :: UA e => UArr e -> UArr (Int :*: e)
{-# INLINE_U indexedU #-}
indexedU = unstreamU . indexedS . streamU

-- *Conversion
-- -----------

-- |/O(n)/. 'toU' turns a list into a 'UArr'.
--
toU :: UA e => [e] -> UArr e
{-# INLINE_U toU #-}
toU = unstreamU . toStream

-- |/O(n)/. 'fromU' collects the elements of a 'UArr' into a list.
--
fromU :: UA e => UArr e -> [e]
{-# INLINE_U fromU #-}
fromU a = [a `Prim.indexU` i | i <- [0..lengthU a - 1]]

------------------------------------------------------------------------


here s = "Data.Array.Vector.Strict.Combinators." ++ s

-- |/O(n)/. @'iterateU' n f a@ constructs an array of size @n@ by iteratively 
-- applying @f@ to @a@.
--
iterateU :: (UA a) => Int -> (a -> a) -> a -> UArr a
iterateU n f = unfoldU n (\x -> JustS $ x :*: f x)

-- |/O(n)/. 'mapU' maps a function over an array.
--
mapU :: (UA e, UA e') => (e -> e') -> UArr e -> UArr e'
{-# INLINE_U mapU #-}
mapU f = unstreamU . mapS f . streamU

-- |/O(n)/. 'filterU' extracts all elements from an array that satisfy 
-- the given predicate.
--
filterU :: UA e => (e -> Bool) -> UArr e -> UArr e 
{-# INLINE_U filterU #-}
filterU p = unstreamU . filterS p . streamU

-- |/O(n)/. 'packU' extracts all elements from an array according to the 
-- provided flag array. For example:
--
-- @packU (toU [1..5]) (toU [True,False,False,False,True])@
--
-- yields @toU [1.0,5.0]@.
-- 
packU:: UA e => UArr e -> UArr Bool -> UArr e
{-# INLINE_U packU #-}
packU xs = fstU . filterU sndS . zipU xs


-- |/O(n)/. 'foldlU' reduces an array proceeding from the left.
--
foldlU :: UA a => (b -> a -> b) -> b -> UArr a -> b
{-# INLINE_U foldlU #-}
foldlU f z = foldS f z . streamU

-- |/O(n)/. 'foldl1U' is a variant of 'foldlU' that assumes a non-empty input
-- array, but requires no starting element. Throws an exception if the input
-- array is empty.
--

-- FIXME: Rewrite for 'Stream's.

-- foldl1U :: UA a => (a -> a -> a) -> UArr a -> a
-- {-# INLINE_U foldl1U #-}
-- foldl1U f arr = checkNotEmpty (here "foldl1U") (lengthU arr) $
--                 foldlU f (arr `Prim.indexU` 0) (sliceU arr 1 (lengthU arr - 1))

foldl1U :: UA a => (a -> a -> a) -> UArr a -> a
foldl1U f = foldl1S f . streamU
{-# INLINE foldl1U #-}

-- | /O(n)/ 'foldrU', applied to a binary operator, a starting value
-- (typically the right-identity of the operator), and a 'UArr a',
-- reduces the 'UArr a' using the binary operator, from right to left.
foldrU :: UA a => (a -> b -> b) -> b -> UArr a -> b
foldrU f z = foldrS f z . streamU
{-# INLINE foldrU #-}

-- | /O(n)/ A variant of 'foldr' that has no starting value argument,
-- and thus must be applied to a non-empty 'UArr a'.
foldr1U :: UA a => (a -> a -> a) -> UArr a -> a
foldr1U f = foldr1S f . streamU
{-# INLINE foldr1U #-}

-- |/O(n)/. 'foldl1MaybeU' behaves like 'foldl1U' but returns 'NothingS' if the
-- input array is empty.
foldl1MaybeU :: UA a => (a -> a -> a) -> UArr a -> MaybeS a
{-# INLINE_U foldl1MaybeU #-}
foldl1MaybeU f = fold1MaybeS f . streamU

-- |/O(n)/. 'foldU' reduces an array using an associative combination function
-- and its unit.
--
foldU :: UA a => (a -> a -> a) -> a -> UArr a -> a
{-# INLINE_U foldU #-}
foldU = foldlU

-- |/O(n)/. 'fold1MaybeU' behaves like 'fold1U' but returns 'NothingS' if the
-- input array is empty.
fold1MaybeU :: UA a => (a -> a -> a) -> UArr a -> MaybeS a
{-# INLINE_U fold1MaybeU #-}
fold1MaybeU = foldl1MaybeU

-- |/O(n)/. 'fold1U' is a variant of 'foldU' that requires a non-empty input
-- array. Throws an exception if its input array is empty.
--
fold1U :: UA a => (a -> a -> a) -> UArr a -> a
{-# INLINE_U fold1U #-}
fold1U = foldl1U

-- |/O(n)/. 'scanlU' is equivalent to 'foldlU' on all prefixes (except the
-- array itself) of the input array.
--
-- /N.B/: the behavior of this function differs from that of Data.List. Compare:
--
-- @scanl (+) 0.0 [1..5]@ gives @[0.0,1.0,3.0,6.0,10.0,15.0]@
--
-- @scanlU (+) 0.0 $ toU [1..5]@ gives @toU [0.0,1.0,3.0,6.0,10.0]@
--
-- To get behavior closer to the List counterpart, see 'scanResU'.
--
scanlU :: (UA a, UA b) => (b -> a -> b) -> b -> UArr a -> UArr b
{-# INLINE_U scanlU #-}
scanlU f z = unstreamU . scanS f z . streamU

-- |/O(n)/. 'scanl1U' is like 'scanlU', but requires no starting value.
--
scanl1U :: UA a => (a -> a -> a) -> UArr a -> UArr a
{-# INLINE_U scanl1U #-}
scanl1U f arr = checkNotEmpty (here "scanl1U") (lengthU arr) $
                unstreamU (scan1S f (streamU arr))

-- |/O(n)/. 'scanU' is equivalent to 'foldU' on all prefixes (except the array
-- itself) of the input array.
--
scanU :: UA a => (a -> a -> a) -> a -> UArr a -> UArr a
{-# INLINE_U scanU #-}
scanU = scanlU

-- |/O(n)/. 'scan1U' is like 'scanU', but requires no starting value.
--
scan1U :: UA a => (a -> a -> a) -> UArr a -> UArr a
{-# INLINE_U scan1U #-}
scan1U = scanl1U

-- |/O(n)/. 'scanResU' behaves like 'scanU', but yields a strict pair with the
-- 'scanU' result as its @fstS@ and the "missing" element ('foldU' on the same
-- arguments) as its @sndS@. Compare:
--
-- @scanl (+) 0.0 [1..5]@ gives @[0.0,1.0,3.0,6.0,10.0,15.0]@
--
-- @scanlU (+) 0.0 $ toU [1..5]@ gives @toU [0.0,1.0,3.0,6.0,10.0]@
--
-- @scanResU (+) 0.0 $ toU [1..5]@ gives @toU [0.0,1.0,3.0,6.0,10.0] :*: 15.0@.
--
scanResU :: UA a => (a -> a -> a) -> a -> UArr a -> UArr a :*: a
{-# INLINE_U scanResU #-}
scanResU f z = unstreamScan f z . streamU

unstreamScan :: UA a => (a -> a -> a) -> a -> Stream a -> UArr a :*: a
{-# INLINE_STREAM unstreamScan #-}
unstreamScan f z st@(Stream _ _ n)
  = newDynResU n (\marr -> unstreamScanM marr f z st)

unstreamScanM :: UA a => MUArr a s -> (a -> a -> a) -> a -> Stream a
                      -> ST s (Int :*: a)
{-# INLINE_U unstreamScanM #-}
unstreamScanM marr f z (Stream next s n) = fill s z 0
  where
    fill s !z !i = case next s of
                     Done       -> return (i :*: z)
                     Skip    s' -> s' `seq` fill s' z i
                     Yield x s' -> s' `seq`
                                   do
                                     writeMU marr i z
                                     fill s' (f z x) (i+1)

-- |/O(n)/. 'mapAccumLU' is an accumulating map from left to right. Unlike its
-- List counterpart, it does not return the accumulator.
--

-- FIXME: Naming inconsistent with lists.
--
mapAccumLU :: (UA a, UA b) => (c -> a -> c :*: b) -> c -> UArr a -> UArr b
{-# INLINE_U mapAccumLU #-}
mapAccumLU f z = unstreamU . mapAccumS f z . streamU

-- zipU is re-exported from UArr

-- |/O(1)/. 'zip3U' takes three arrays and returns an array of triples.
--
zip3U :: (UA e1, UA e2, UA e3) 
      => UArr e1 -> UArr e2 -> UArr e3 -> UArr (e1 :*: e2 :*: e3)
{-# INLINE_U zip3U #-}
zip3U a1 a2 a3 = (a1 `zipU` a2) `zipU` a3

-- |/O(n)/. 'zipWithU' applies a function to corresponding elements of two 
-- arrays, yielding an array containing the results.
--
zipWithU :: (UA a, UA b, UA c) 
         => (a -> b -> c) -> UArr a -> UArr b -> UArr c
{-# INLINE_U zipWithU #-}
zipWithU f a1 a2 = unstreamU (zipWithS f (streamU a1) (streamU a2))

-- |/O(n)/. 'zipWith3U' applies a function to corresponding elements of three
-- arrays, yielding an array with the results.
--
zipWith3U :: (UA a, UA b, UA c, UA d) 
          => (a -> b -> c -> d) -> UArr a -> UArr b -> UArr c -> UArr d
{-# INLINE_U zipWith3U #-}
zipWith3U f a1 a2 a3 = unstreamU (zipWith3S f (streamU a1)
                                              (streamU a2)
                                              (streamU a3))

-- unzipU is re-exported from UArr

-- |/O(1)/. 'unzip3U' unpairs an array of strict triples into three arrays.
unzip3U :: (UA e1, UA e2, UA e3) 
        => UArr (e1 :*: e2 :*: e3) -> (UArr e1 :*: UArr e2 :*: UArr e3)
{-# INLINE_U unzip3U #-}
unzip3U a = let (a12 :*: a3) = unzipU a
                (a1  :*: a2) = unzipU a12
            in
            (a1 :*: a2 :*: a3)

-- fstU and sndU reexported from UArr

-- |/O(n)/. @'combineU' f a1 a2@ yields an array by picking elements from @a1@
-- if @f@ is @True@ at the given position, and picking elements from @a2@ 
-- otherwise. For example:
--
-- @combineU (toU [True,True,False,True,False,False]) (toU [1..3]) (toU [4..6])@
--
-- yields @toU [1.0,2.0,4.0,3.0,5.0,6.0]@.
combineU :: UA a
         => UArr Bool -> UArr a -> UArr a -> UArr a
{-# INLINE_U combineU #-}
combineU f a1 a2 = checkEq (here "combineU") 
     ("flag length not equal to sum of arg length")
     (lengthU f) (lengthU a1 + lengthU a2) $ 
--  trace ("combineU:\n\t"  ++ show (lengthU f)  ++ "\n\t" ++ show (lengthU a1) ++ "\n\t" ++ show (lengthU a2) ++ "\n")
  unstreamU (combineS (streamU f) (streamU a1) (streamU a2))

------------------------------------------------------------------------

-- sliceU reexported from UArr

-- {-# INLINE_U extractU #-}
-- extractU :: UA a => UArr a -> Int -> Int -> UArr a
-- extractU arr i n = newU n $ \marr -> copyMU marr 0 (sliceU arr i n)

-- |/O(n)/. 'tailU' yields the given array without its initial element.
tailU :: UA e => UArr e -> UArr e
tailU = unstreamU . tailS . streamU
{-# INLINE_U tailU #-}

-- |/O(n)/. 'dropU' yields the suffix obtained by dropping the given number
-- of elements from an array.
dropU :: UA e=> Int -> UArr e -> UArr e
dropU n = unstreamU . dropS n . streamU
{-# INLINE dropU #-}

-- |/O(n)/. 'takeU' yields the prefix of the given length of an array.
takeU :: UA e=> Int -> UArr e -> UArr e
takeU n = unstreamU . takeS n . streamU
{-# INLINE takeU #-}

-- |/O(n)/. 'splitAtU' splits an array into two subarrays at the given index.
--
splitAtU :: UA e => Int -> UArr e -> (UArr e, UArr e)
splitAtU n a = (takeU n a, dropU n a)
{-# INLINE splitAtU #-}

{-

-- |Extract a prefix of an array
--
takeU :: UA e=> Int -> UArr e -> UArr e
{-# INLINE_U takeU #-}
takeU n a = extractU a 0 n

-- |Extract a suffix of an array
--
dropU :: UA e => Int -> UArr e -> UArr e
{-# INLINE_U dropU #-}
dropU n a = let len = lengthU a
            in
            extractU a n (len - n)

-- |Split an array into two halves at the given index
--
splitAtU :: UA e => Int -> UArr e -> (UArr e, UArr e)
{-# INLINE_U splitAtU #-}
splitAtU n a = (takeU n a, dropU n a)

-}

------------------------------------------------------------------------

-- |/O(n)/. 'takeWhileU', applied to a predicate @p@ and a UArr @xs@,
-- returns the longest prefix (possibly empty) of @xs@ of elements that satisfy @p@.
--
takeWhileU :: UA e => (e -> Bool) -> UArr e -> UArr e
takeWhileU f = unstreamU . takeWhileS f . streamU
{-# INLINE_U takeWhileU #-}

-- |/O(n)/. 'dropWhileU' @p xs@ returns the suffix remaining after 'takeWhileU' @p xs@.
dropWhileU :: UA e => (e -> Bool) -> UArr e -> UArr e
dropWhileU f = unstreamU . dropWhileS f . streamU
{-# INLINE_U dropWhileU #-}

------------------------------------------------------------------------
-- ** Searching with a predicate

-- |/O(n)/, /fusion/. The 'findU' function takes a predicate and an array
-- and returns the first element in the list matching the predicate, or
-- 'Nothing' if there is no such element.
findU :: UA a => (a -> Bool) -> UArr a -> Maybe a
{-# INLINE_U findU #-}
findU p = findS p . streamU

-- |/O(n)/, /fusion/. The 'findIndexU' function takes a predicate and an array and returns
-- the index of the first element in the array satisfying the predicate,
-- or 'Nothing' if there is no such element.
--
findIndexU :: UA a => (a -> Bool) -> UArr a -> Maybe Int
{-# INLINE_U findIndexU #-}
findIndexU p = findIndexS p . streamU

-- |/O(n)/, /fusion/. 'lookupU' @key assocs@ looks up a key in an array
-- of pairs treated as an association table.
--
lookupU :: (Eq a, UA a, UA b) => a -> UArr (a :*: b) -> Maybe b
{-# INLINE_U lookupU #-}
lookupU p = lookupS p . streamU

------------------------------------------------------------------------

-- |/O(n)/. @'unfoldU' n f z@ builds an array of size @n@ from a seed value
-- @z@ by iteratively applying @f@, stopping when @n@ elements have been 
-- generated or @f@ returns 'NothingS'.
--
unfoldU :: UA a => Int -> (b -> MaybeS (a :*: b)) -> b -> UArr a
{-# INLINE_U unfoldU #-}
unfoldU n f z = unstreamU (unfoldS n f z)