{-# LANGUAGE ScopedTypeVariables #-}

-- |
-- Module      : Control.Auto.Generate
-- Description : 'Auto's that act as generators or "producers", ignoring input.
-- Copyright   : (c) Justin Le 2015
-- License     : MIT
-- Maintainer  : justin@jle.im
-- Stability   : unstable
-- Portability : portable
--
-- This module contains various 'Auto's that act as "producing" streams;
-- they all ignore their input streams and produce output streams through
-- a pure or monadic process.
--

module Control.Auto.Generate (
  -- * From lists
    fromList
  , fromList_
  , fromLongList
  -- * Constant producers
  -- $constant
  , pure
  , effect
  -- * From functions
  -- ** Iterating
  , iterator
  , iterator_
  , iteratorM
  , iteratorM_
  -- ** Enumerating results of a function
  , discreteF
  , discreteF_
  -- ** Unfolding
  -- | "Iterating with state".
  , unfold
  , unfold_
  , unfoldM
  , unfoldM_
  -- * Enumerating
  , enumFromA
  , enumFromA_
  ) where

import Control.Applicative
import Control.Auto.Core
import Control.Auto.Interval
import Control.Category
import Data.Serialize
import Prelude hiding        ((.), id)

-- | An 'Interval' that ignores the input stream and just outputs items
-- from the given list.  Is "on" as long as there are still items in the
-- list left, and "off" after there is nothing left in the list to output.
--
-- Serializes itself by storing the entire rest of the list in binary, so
-- if your list is long, it might take up a lot of space upon
-- storage.  If your list is infinite, it makes an infinite binary, so be
-- careful!
--
-- 'fromLongList' can be used for longer lists or infinite lists; or, if
-- your list can be boild down to an 'unfoldr', you can use 'unfold'.
--
--   * Storing: O(n) time and space on length of remaining list
--   * Loading: O(1) time in the number of times the 'Auto' has been
--   stepped + O(n) time in the length of the remaining list.
--
fromList :: Serialize b
         => [b]                 -- ^ list to output element-by-element
         -> Interval m a b
fromList = mkState (const _uncons)

-- | A version of 'fromList' that is safe for long or infinite lists, or
-- lists with unserializable elements.
--
-- There is a small cost in the time of loading/resuming, which is @O(n)@
-- on the number of times the Auto had been stepped at the time of
-- saving.  This is because it has to drop the @n@ first elements in the
-- list, to "resume" to the proper position.
--
--   * Storing: O(1) time and space on the length of the remaining list
--   * Loading: O(n) time on the number of times the 'Auto' has been
--   stepped, maxing out at O(n) on the length of the entire input list.
--
fromLongList :: [b]                 -- ^ list to output element-by-element
             -> Interval m a b
fromLongList xs = go 0 xs
  where
    loader = do
      stopped <- get
      if stopped
        then return finished
        else do
          i <- get
          return (go i (drop i xs))
    finished = mkAuto loader
                      (put True)
                      (const (Nothing, finished))
    go i ys  = mkAuto loader
                      (put (False, i))
                    $ \_ -> case ys of
                              (y':ys') -> (Just y', go (i + 1) ys')
                              []       -> (Nothing, finished)

-- | The non-resuming/non-serializing version of 'fromList'.
fromList_ :: [b]                -- ^ list to output element-by-element
          -> Interval m a b
fromList_ = mkState_ (const _uncons)

_uncons :: [a] -> (Maybe a, [a])
_uncons []     = (Nothing, [])
_uncons (x:xs) = (Just x , xs)

-- | Analogous to 'unfoldr' from "Prelude".  Creates an 'Interval'
-- (that ignores its input) by maintaining an internal accumulator of type
-- @c@ and, at every step, applying to the unfolding function to the
-- accumulator.  If the result is 'Nothing', then the 'Interval' will turn
-- "off" forever (output 'Nothing' forever); if the result is @'Just' (y,
-- acc)@, then it will output @y@ and store @acc@ as the new accumulator.
--
-- Given an initial accumulator.
--
-- >>> let countFromTil n m = flip unfold n $ \i -> if i <= m
--                                                    then Just (i, i+1)
--                                                    else Nothing
-- >>> take 8 . streamAuto' (countFromTil 5 10) $ repeat ()
-- [Just 5, Just 6, Just 7, Just 8, Just 9, Just 10, Nothing, Nothing]
--
-- @'unfold' f c0@ behaves like @'overList' ('unfoldr' f c0)@.
--
unfold :: Serialize c
       => (c -> Maybe (b, c))     -- ^ unfolding function
       -> c                       -- ^ initial accumulator
       -> Interval m a b
unfold f = mkState (_unfoldF f) . Just

-- | Like 'unfold', but the unfolding function is monadic.
unfoldM :: (Serialize c, Monad m)
        => (c -> m (Maybe (b, c)))     -- ^ unfolding function
        -> c                           -- ^ initial accumulator
        -> Interval m a b
unfoldM f = mkStateM (_unfoldMF f) . Just

-- | The non-resuming & non-serializing version of 'unfold'.
unfold_ :: (c -> Maybe (b, c))     -- ^ unfolding function
        -> c                       -- ^ initial accumulator
        -> Interval m a b
unfold_ f = mkState_ (_unfoldF f) . Just

-- | The non-resuming & non-serializing version of 'unfoldM'.
unfoldM_ :: Monad m
         => (c -> m (Maybe (b, c)))     -- ^ unfolding function
         -> c                           -- ^ initial accumulator
         -> Interval m a b
unfoldM_ f = mkStateM_ (_unfoldMF f) . Just

_unfoldF :: (c -> Maybe (b, c))
         -> a
         -> Maybe c
         -> (Maybe b, Maybe c)
_unfoldF _ _ Nothing  = (Nothing, Nothing)
_unfoldF f _ (Just x) = case f x of
                          Just (y, x') -> (Just y, Just x')
                          Nothing      -> (Nothing, Nothing)

_unfoldMF :: Monad m
          => (c -> m (Maybe (b, c)))
          -> a
          -> Maybe c
          -> m (Maybe b, Maybe c)
_unfoldMF _ _ Nothing  = return (Nothing, Nothing)
_unfoldMF f _ (Just x) = do
    res <- f x
    return $ case res of
               Just (y, x') -> (Just y, Just x')
               Nothing      -> (Nothing, Nothing)


-- | Analogous to 'iterate' from "Prelude".  Keeps accumulator value and
-- continually applies the function to the accumulator at every step,
-- outputting the result.
--
-- The first result is the initial accumulator value.
--
-- >>> take 10 . streamAuto' (iterator (*2) 1) $ repeat ()
-- [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
iterator :: Serialize b
         => (b -> b)        -- ^ iterating function
         -> b               -- ^ starting value and initial output
         -> Auto m a b
iterator f = accumD (\x _ -> f x)

-- | Like 'iterator', but with a monadic function.
iteratorM :: (Serialize b, Monad m)
          => (b -> m b)     -- ^ (monadic) iterating function
          -> b              -- ^ starting value and initial output
          -> Auto m a b
iteratorM f = accumMD (\x _ -> f x)

-- | The non-resuming/non-serializing version of 'iterator'.
iterator_ :: (b -> b)        -- ^ iterating function
          -> b               -- ^ starting value and initial output
          -> Auto m a b
iterator_ f = accumD_ (\x _ -> f x)

-- | The non-resuming/non-serializing version of 'iteratorM'.
iteratorM_ :: Monad m
           => (b -> m b)     -- ^ (monadic) iterating function
           -> b              -- ^ starting value and initial output
           -> Auto m a b
iteratorM_ f = accumMD_ (\x _ -> f x)

-- | Continually enumerate from the starting value, using `succ`.
enumFromA :: (Serialize b, Enum b)
          => b                -- ^ initial value
          -> Auto m a b
enumFromA = iterator succ

-- | The non-serializing/non-resuming version of `enumFromA`.
enumFromA_ :: Enum b
           => b               -- ^ initial value
           -> Auto m a b
enumFromA_ = iterator_ succ

-- | Given a function from discrete enumerable inputs, iterates through all
-- of the results of that function.
--
-- >>> take 10 . streamAuto' (discreteF (^2) 0) $ repeat ()
-- [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
discreteF :: (Enum c, Serialize c)
          => (c -> b)       -- ^ discrete function
          -> c              -- ^ initial input
          -> Auto m a b
discreteF f = mkState $ \_ x -> (f x, succ x)

-- | The non-resuming/non-serializing version of `discreteF`.
discreteF_ :: Enum c
           => (c -> b)      -- ^ discrete function
           -> c             -- ^ initial input
           -> Auto m a b
discreteF_ f = mkState_ $ \_ x -> (f x, succ x)

-- $constant
--
-- Here we have the "constant producers": 'Auto's whose output is always
-- the same value, or the result of executing the same monadic action.
--
-- @
-- 'pure'   :: 'Monad' m => b   -> 'Auto' m a b
-- 'effect' :: 'Monad' m => m b -> 'Auto' m a b
-- @
--
-- 'pure' always outputs the same value, ignoring its input, and 'effect'
-- always outputs the result of executing the same monadic action, ignoring
-- its input.

-- | To get every output, executes the monadic action and returns the
-- result as the output.  Always ignores input.
--
-- This is basically like an "effectful" 'pure':
--
-- @
-- 'pure'   :: b   -> 'Auto' m a b
-- 'effect' :: m b -> 'Auto' m a b
-- @
--
-- The output of 'pure' is always the same, and the output of 'effect' is
-- always the result of the same monadic action.  Both ignore their inputs.
--
-- Fun times when the underling 'Monad' is, for instance, 'Reader'.
--
-- >>> let a = effect ask    :: Auto (Reader b) a b
-- >>> let r = evalAuto a () :: Reader b b
-- >>> runReader r "hello"
-- "hello"
-- >>> runReader r 100
-- 100
--
-- If your underling monad has effects ('IO', 'State', 'Maybe', 'Writer',
-- etc.), then it might be fun to take advantage of '*>' from
-- "Control.Applicative" to "tack on" an effect to a normal 'Auto':
--
-- >>> let a = effect (modify (+1)) *> sumFrom 0 :: Auto (State Int) Int Int
-- >>> let st = streamAuto a [1..10]
-- >>> let (ys, s') = runState st 0
-- >>> ys
-- [1,3,6,10,15,21,28,36,45,55]
-- >>> s'
-- 10
--
-- Out 'Auto' @a@ behaves exactly like @'sumFrom' 0@, except at each step,
-- it also increments the underlying/global state by one.  It is @'sumFrom'
-- 0@ with an "attached effect".
--
effect :: m b           -- ^ monadic action to contually execute.
       -> Auto m a b
effect = mkConstM
{-# INLINE effect #-}