Copyright 2010 Mario Blazevic

    This file is part of the Streaming Component Combinators (SCC) project.

    The SCC project is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
    License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later

    SCC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
    of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along with SCC.  If not, see

-- | This module provides a bridge between the Control.Monad.Coroutine and the Data.Enumerator monad transformers.

module Control.Monad.Coroutine.Enumerator
      coroutineEnumerator, enumeratorCoroutine,
      coroutineIteratee, iterateeCoroutine,
      iterateeStreamCoroutine, streamCoroutineIteratee

import Control.Monad (liftM, unless)
import Control.Exception.Base (SomeException)

import Data.Enumerator (Enumerator, Iteratee(..), Stream(..))
import qualified Data.Enumerator as Enumerator

import Control.Monad.Coroutine
import Control.Monad.Coroutine.SuspensionFunctors (Await(Await), Yield(Yield), await, yield)

-- | Converts an 'Iteratee' into a 'Coroutine' parameterized with the 'Await' [x] functor. The coroutine treats an empty
-- input chunk as 'EOF'.
iterateeCoroutine :: Monad m => Iteratee a m b -> Coroutine (Await [a]) m (Either SomeException (b, [a]))
iterateeCoroutine = convert . iterateeStreamCoroutine
   where convert = liftM (either Left (Right . streamChunk)) . mapSuspension convertSuspension
         convertSuspension (Await cont) = Await (endOnEmptyChunk cont)
         endOnEmptyChunk cont [] = cont EOF
         endOnEmptyChunk cont chunk = cont (Chunks chunk)
         streamChunk (b, EOF) = (b, [])
         streamChunk (b, Chunks chunk) = (b, chunk)

-- | Converts a 'Coroutine' parameterized with the 'Await' [x] functor, treating an ampty input chunk as 'EOF', into an
-- 'Iteratee'.
coroutineIteratee :: Monad m => Coroutine (Await [a]) m (Either SomeException (b, [a])) -> Iteratee a m b
coroutineIteratee = streamCoroutineIteratee . convert . liftM (either Left (Right . \(b, chunk)-> (b, Chunks chunk)))
   where convert cort = Coroutine {resume= resume cort >>= return . either (Left . convertSuspension) Right}
         convertSuspension (Await cont) = Await (ignoreEmptyChunks (convert . cont))
         ignoreEmptyChunks cont (Chunks []) = suspend $ Await (ignoreEmptyChunks cont)
         ignoreEmptyChunks cont (Chunks chunk) = cont chunk
         ignoreEmptyChunks cont EOF = cont []

-- | Converts an 'Iteratee' into a 'Coroutine' parameterized with the 'Await' ('Stream' x) functor.
iterateeStreamCoroutine :: Monad m => 
                           Iteratee a m b -> Coroutine (Await (Stream a)) m (Either SomeException (b, Stream a))
iterateeStreamCoroutine iter = Coroutine{resume= liftM convertStep $ runIteratee iter}
   where convertStep (Enumerator.Yield b s) = Right $ Right (b, s)
         convertStep (Enumerator.Error e) = Right $ Left e
         convertStep (Enumerator.Continue cont) = Left (Await (iterateeStreamCoroutine . cont))

-- | Converts a 'Coroutine' parameterized with the 'Await' functor into an 'Iteratee'.
streamCoroutineIteratee :: Monad m 
                           => Coroutine (Await (Stream a)) m (Either SomeException (b, Stream a)) -> Iteratee a m b
streamCoroutineIteratee c = Iteratee (liftM convertStep $ resume c)
   where convertStep (Left (Await cont)) = Enumerator.Continue (streamCoroutineIteratee . cont)
         convertStep (Right (Left e)) = Enumerator.Error e
         convertStep (Right (Right (b, s))) = Enumerator.Yield b s

-- | Converts an 'Enumerator' into a 'Coroutine' parameterized with the 'Yield' functor.
enumeratorCoroutine :: Monad m => Enumerator a (Coroutine (Yield [a]) m) () -> Coroutine (Yield [a]) m ()
enumeratorCoroutine enum = runIteratee (enum step) >> return ()
   where step = Enumerator.Continue yielding
         yielding (Chunks xs) = Iteratee (unless (null xs) (yield xs) >> return step)
         yielding EOF = Enumerator.returnI (Enumerator.Yield () EOF)

-- | Converts a 'Coroutine' parameterized with the 'Yield' functor into an 'Enumerator'.
coroutineEnumerator :: Monad m => Coroutine (Yield [a]) m b -> Enumerator a m c
coroutineEnumerator c step@(Enumerator.Continue cont) = Iteratee (resume c >>= either feed (const $ return step))
   where feed (Yield chunk c) = runIteratee (cont (Chunks chunk)) >>= runIteratee . coroutineEnumerator c
coroutineEnumerator _ step = Enumerator.returnI step