{-# LANGUAGE
    BangPatterns
  , UnicodeSyntax
  #-}
-- | Some functions currently missing from
-- "Data.Vector.Fusion.Stream.Monadic".
module Data.Bitstream.Fusion.Monadic
    ( genericLength
    , genericTake
    , genericDrop
    , genericIndex
    , genericReplicate
    , genericReplicateM
    , genericUnfoldrN
    , genericUnfoldrNM
    , genericFindIndex
    , genericFindIndexM
    , genericIndexed
    )
    where
import Data.Vector.Fusion.Stream.Monadic
import Data.Vector.Fusion.Stream.Size
import Prelude hiding ((!!), drop, replicate, take)
import Prelude.Unicode

genericLength  (Monad m, Num n)  Stream m α  m n
{-# INLINE genericLength #-}
genericLength = foldl' (\n _  n+1) 0

genericTake  (Monad m, Integral n)  n  Stream m α  Stream m α
{-# INLINE [0] genericTake #-}
{-# RULES "genericTake → take" genericTake = take #-}
genericTake n (Stream step s0 sz) = Stream step' (s0, 0) (toMax sz)
    where
      {-# INLINE step' #-}
      step' (s, i)
          | i < n
              = do r  step s
                   case r of
                     Yield α s'  return $ Yield α (s', i+1)
                     Skip    s'  return $ Skip    (s', i  )
                     Done        return Done
          | otherwise
              = return Done

genericDrop  (Monad m, Integral n)  n  Stream m α  Stream m α
{-# INLINE [0] genericDrop #-}
{-# RULES "genericDrop → drop" genericDrop = drop #-}
genericDrop n0 (Stream step s0 sz) = Stream step' (s0, Just n0) (toMax sz)
    where
      {-# INLINE step' #-}
      step' (s, Just n)
          | n > 0
              = do r  step s
                   case r of
                     Yield _ s'  return $ Skip (s', Just (n-1))
                     Skip    s'  return $ Skip (s', Just n)
                     Done        return Done
          | otherwise
              = return $ Skip (s, Nothing)

      step' (s, Nothing)
          = do r  step s
               case r of
                 Yield α s'  return $ Yield α (s', Nothing)
                 Skip    s'  return $ Skip    (s', Nothing)
                 Done        return Done

genericIndex  (Monad m, Integral n, Show n)  Stream m α  n  m α
{-# INLINE [0] genericIndex #-}
{-# RULES "genericIndex → (!!)" genericIndex = (!!) #-}
genericIndex (Stream step s0 _) i0
    | i0 < 0    = fail ("genericIndex: out of range: "  show i0)
    | otherwise = index_loop s0 0
    where
      {-# INLINE index_loop #-}
      index_loop s i
          = do r  step s
               case r of
                 Yield α s'
                     | i  i0     return α
                     | otherwise  index_loop s' (i+1)
                 Skip    s'       index_loop s' i
                 Done             fail ("genericIndex: out of range: "  show i)

genericReplicate  (Monad m, Integral n)  n  α  Stream m α
{-# INLINE genericReplicate #-}
genericReplicate n = genericReplicateM n  return

genericReplicateM  (Monad m, Integral n)  n  m α  Stream m α
{-# INLINE [0] genericReplicateM #-}
{-# RULES "genericReplicateM → replicateM" genericReplicateM = replicateM #-}
genericReplicateM n0  = unfoldrM go n0
    where
      {-# INLINE go #-}
      go n | n  0     = return Nothing
           | otherwise = do α  
                            return $ Just (α, n-1)

genericUnfoldrN  (Monad m, Integral n)  n  (β  Maybe (α, β))  β  Stream m α
{-# INLINE genericUnfoldrN #-}
genericUnfoldrN n f = genericUnfoldrNM n (return  f)

genericUnfoldrNM  (Monad m, Integral n)  n  (β  m (Maybe (α, β)))  β  Stream m α
{-# INLINE [0] genericUnfoldrNM #-}
{-# RULES "genericUnfoldrNM → unfoldrNM" genericUnfoldrNM = unfoldrNM #-}
genericUnfoldrNM n0 f β0 = unfoldrM go (n0, β0)
    where
      {-# INLINE go #-}
      go (!n, β)
          | n  0     = return Nothing
          | otherwise = do r  f β
                           return $ do (α, β')  r
                                       return (α, (n-1, β'))

genericFindIndex  (Monad m, Integral n)  (α  Bool)  Stream m α  m (Maybe n)
{-# INLINE genericFindIndex #-}
genericFindIndex f = genericFindIndexM (return  f)

genericFindIndexM  (Monad m, Integral n)  (α  m Bool)  Stream m α  m (Maybe n)
{-# INLINE [0] genericFindIndexM #-}
{-# RULES "genericFindIndexM → findIndexM" genericFindIndexM = findIndexM #-}
genericFindIndexM f (Stream step s0 _) = findIndex_loop s0 0
    where
      {-# INLINE findIndex_loop #-}
      findIndex_loop s i
          = do r  step s
               case r of
                 Yield α s'  do b  f α
                                 if b then return $ Just i
                                      else findIndex_loop s' (i+1)
                 Skip    s'  findIndex_loop s' i
                 Done        return Nothing

genericIndexed  (Monad m, Integral n)  Stream m α  Stream m (n, α)
{-# INLINE [0] genericIndexed #-}
{-# RULES "genericIndexed → indexed" genericIndexed = indexed #-}
genericIndexed (Stream step s0 sz) = Stream step' (s0, 0) sz
    where
      {-# INLINE step' #-}
      step' (s, i)
          = do r  step s
               case r of
                 Yield α s'  return $ Yield (i, α) (s', i+1)
                 Skip    s'  return $ Skip         (s', i  )
                 Done        return Done