content: {-# LANGUAGE BangPatterns #-}
  
  -- | List conversions and utilities.
  
  module System.IO.Streams.List
   ( -- * List conversions
     fromList
   , toList
   , outputToList
   , writeList
  
     -- * Utility
   , chunkList
   , chunkListWith
   , concatLists
   , listOutputStream
   ) where
  
  ------------------------------------------------------------------------------
  import           Control.Concurrent.MVar    (modifyMVar, modifyMVar_, newMVar)
  import           Control.Monad.IO.Class     (MonadIO (..))
  import           Data.IORef                 (newIORef, readIORef, writeIORef)
  import           Prelude                    hiding (read)
  ------------------------------------------------------------------------------
  import           System.IO.Streams.Internal (InputStream, OutputStream, await, connect, fromConsumer, fromGenerator, makeInputStream, read, write, yield)
  
  
  ------------------------------------------------------------------------------
  -- | Transforms a list into an 'InputStream' that produces no side effects.
  --
  -- @
  -- ghci> is <- Streams.'fromList' [1, 2]
  -- ghci> 'replicateM' 3 (Streams.'read' is)
  -- [Just 1, Just 2, Nothing]
  -- @
  fromList :: [c] -> IO (InputStream c)
  fromList inp = newIORef inp >>= makeInputStream . f
    where
      f ref = readIORef ref >>= \l ->
              case l of
                []     -> return Nothing
                (x:xs) -> writeIORef ref xs >> return (Just x)
  {-# INLINE fromList #-}
  
  
  ------------------------------------------------------------------------------
  -- | 'listOutputStream' returns an 'OutputStream' which stores values fed into
  -- it and an action which flushes all stored values to a list.
  --
  -- The flush action resets the store.
  --
  -- Note that this function /will/ buffer any input sent to it on the heap.
  -- Please don't use this unless you're sure that the amount of input provided
  -- is bounded and will fit in memory without issues.
  --
  -- @
  -- ghci> (os, flush) <- Streams.'listOutputStream' :: IO ('OutputStream' Int, IO [Int])
  -- ghci> Streams.'writeList' [1, 2] os
  -- ghci> flush
  -- [1, 2]
  -- ghci> Streams.'writeList' [3, 4] os
  -- ghci> flush
  -- [3, 4]
  -- @
  listOutputStream :: IO (OutputStream c, IO [c])
  listOutputStream = do
      r <- newMVar id
      c <- fromConsumer $ consumer r
      return (c, flush r)
  
    where
      consumer r = go
        where
          go = await >>= (maybe (return $! ()) $ \c -> do
                              liftIO $ modifyMVar_ r $ \dl -> return (dl . (c:))
                              go)
  
      flush r = modifyMVar r $ \dl -> return (id, dl [])
  {-# INLINE listOutputStream #-}
  
  
  ------------------------------------------------------------------------------
  -- | Drains an 'InputStream', converting it to a list. N.B. that this function
  -- reads the entire 'InputStream' strictly into memory and as such is not
  -- recommended for streaming applications or where the size of the input is not
  -- bounded or known.
  --
  -- @
  -- ghci> is <- Streams.'fromList' [1, 2]
  -- ghci> Streams.'toList' is
  -- [1, 2]
  -- @
  toList :: InputStream a -> IO [a]
  toList is = outputToList (connect is)
  {-# INLINE toList #-}
  
  
  ------------------------------------------------------------------------------
  -- | Given an IO action that requires an 'OutputStream', creates one and
  -- captures all the output the action sends to it as a list.
  --
  -- Example:
  --
  -- @
  -- ghci> import "Control.Applicative"
  -- ghci> ('connect' <$> 'fromList' [\"a\", \"b\", \"c\"]) >>= 'outputToList'
  -- [\"a\",\"b\",\"c\"]
  -- @
  outputToList :: (OutputStream a -> IO b) -> IO [a]
  outputToList f = do
      (os, getList) <- listOutputStream
      _ <- f os
      getList
  {-# INLINE outputToList #-}
  
  
  ------------------------------------------------------------------------------
  -- | Feeds a list to an 'OutputStream'. Does /not/ write an end-of-stream to
  -- the stream.
  --
  -- @
  -- ghci> os \<- Streams.'unlines' Streams.'System.IO.Streams.stdout' >>= Streams.'System.IO.Streams.contramap' (S.pack . show) :: IO ('OutputStream' Int)
  -- ghci> Streams.'writeList' [1, 2] os
  -- 1
  -- 2
  -- ghci> Streams.'writeList' [3, 4] os
  -- 3
  -- 4
  -- @
  writeList :: [a] -> OutputStream a -> IO ()
  writeList xs os = mapM_ (flip write os . Just) xs
  {-# INLINE writeList #-}
  
  
  ------------------------------------------------------------------------------
  -- | Splits an input stream into chunks of at most size @n@.
  --
  -- Example:
  --
  -- @
  -- ghci> 'fromList' [1..14::Int] >>= 'chunkList' 4 >>= 'toList'
  -- [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14]]
  -- @
  chunkList :: Int                   -- ^ chunk size
            -> InputStream a         -- ^ stream to process
            -> IO (InputStream [a])
  chunkList n input = if n <= 0
                        then error $ "chunkList: bad size: " ++ show n
                        else fromGenerator $ go n id
    where
      go !k dl | k <= 0    = yield (dl []) >> go n id
               | otherwise = do
                     liftIO (read input) >>= maybe finish chunk
        where
          finish  = let l = dl []
                    in if null l then return $! () else yield l
          chunk x = go (k - 1) (dl . (x:))
  
  
  ------------------------------------------------------------------------------
  -- | Splits an input stream into chunks whenever @p elt count@ returns true.
  --
  -- Example:
  --
  -- @
  -- ghci> 'fromList' [1..14::Int] >>= 'chunkListWith' (\x n -> n>=4) >>= 'toList'
  -- [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14]]
  -- ghci> 'fromList' ['a'..'z'] >>= 'chunkListWith' (\x n -> n>=4 && x `elem` "aeiouy") >>= 'toList'
  -- ["abcde","fghi","jklmno","pqrstu","vwxy","z"]
  -- @
  --
  -- /Since: 1.3.3.0./
  chunkListWith :: (a -> Int -> Bool)    -- ^ break predicate
                -> InputStream a         -- ^ stream to process
                -> IO (InputStream [a])
  chunkListWith p input =
    fromGenerator $ go Nothing 0 id
    where
      go v !k dl
        | Just x <- v, p x k = yield (dl []) >> go Nothing 0 id
        | otherwise = do
            liftIO (read input) >>= maybe finish chunk
        where
          finish =
            let l = dl []
            in if null l
                 then return $! ()
                 else yield l
          chunk x = go (Just x) (k + 1) (dl . (x :))
  
  
  ------------------------------------------------------------------------------
  -- | Given an input stream containing lists, produces a new input stream that
  -- will yield the concatenation of these lists. See 'Prelude.concat'.
  --
  -- Example:
  --
  -- @
  -- ghci> Streams.'fromList' [[1,2,3::Int], [4,5,6]] >>=
  --       Streams.'concatLists' >>=
  --       Streams.'toList'
  -- [1,2,3,4,5,6]
  -- @
  concatLists :: InputStream [a] -> IO (InputStream a)
  concatLists input = fromGenerator go
    where
      go      = liftIO (read input) >>= maybe (return $! ()) chunk
      chunk l = sequence_ (map yield l) >> go
  