module Reactive.Bacon.EventStream.Combinators(mergeE, takeUntilE, eitherE, combineLatestE) where

import Reactive.Bacon.Core
import Reactive.Bacon.EventStream
import Reactive.Bacon.PushStream
import Reactive.Bacon.Property
import Data.IORef
import Control.Monad
import Control.Applicative

instance Show a => Show (EventStream a) where
  show = const "EventStream"

instance Eq a => Eq (EventStream a) where
  (==) = \x y -> False

mergeE :: EventSource s1 => EventSource s2 => s1 a -> s2 a -> IO (EventStream a)
mergeE xs ys = eitherE xs ys >>= mapE simplify 
   where simplify (Right x) = x
         simplify (Left x)  = x

takeUntilE :: EventSource s1 => EventSource s2 => s1 a -> s2 b -> IO (EventStream b)
takeUntilE stopper src = wrap $ sinkMap takeUntil' $ mergeRawE src stopper
  where takeUntil' sink (Next (Left (Next x)))  = sink (Next x)
        takeUntil' sink (Next (Left End))       = sink End >> return NoMore
        takeUntil' sink (Next (Right (Next x))) = sink End >> return NoMore
        takeUntil' sink (Next (Right End))      = return More

eitherE :: EventSource s1 => EventSource s2 => s1 a -> s2 b -> IO (EventStream (Either a b))
eitherE left right = do endFlag <- newIORef False
                        return $ sinkMap (skipFirstEnd endFlag) (mergeRawE left right)
  where skipFirstEnd flag sink event | isEnd event = do done <- readIORef flag
                                                        writeIORef flag True
                                                        handleEnd done sink
                                     | otherwise   = send sink skipFirstEnd event
        handleEnd True sink = sink End >> return NoMore
        handleEnd False sink = return More
        send sink mapper (Next (Right (Next x))) = sink (Next (Right x))
        send sink mapper (Next (Left (Next x))) = sink (Next (Left x))

combineLatestE :: EventSource s1 => EventSource s2 => s1 a -> s2 b -> IO (EventStream (a, b))
combineLatestE left right = do leftP <- fromEventSource left
                               rightP <- fromEventSource right
                               changesP $ combineP leftP rightP

mergeRawE :: EventSource s1 => EventSource s2 => s1 a -> s2 b -> EventStream (Either (Event a) (Event b))
mergeRawE left right = EventStream $\sink -> do
  disposeRightHolder <- newIORef Nothing
  disposeLeft <- subscribe (toEventStream left) (barrier Left sink (disposeIfPossible disposeRightHolder))
  disposeRight <- subscribe (toEventStream right) (barrier Right sink disposeLeft)
  writeIORef disposeRightHolder (Just disposeRight)
  return $ disposeLeft >> disposeRight
    where barrier mapping sink disposeOther event = do
              result <- sink $ Next (mapping event)
              case result of
                 More -> do
                    return More
                 NoMore   -> do
                    disposeOther
                    return NoMore
          disposeIfPossible ref = do
              dispose <- readIORef ref
              case dispose of Nothing -> return () -- TODO: is it necessary to dispose later in this case?
                              Just f  -> f

isEnd :: Event (Either (Event a) (Event b)) -> Bool
isEnd (Next (Right End)) = True
isEnd (Next (Left End))  = True
isEnd _                  = False