\begin{code}
module TermE where
import ListenerOps
import IOExts
import MutSet
import Utils
import Remover
\end{code}

On some occasions we can know when an event has terminated. In particular,
this is important when connected with caching. 

 do e <- onceE e
    addListener (filterE f e) l

This forms an event chain, with two cached events. However, after the 
first occurrence of e, the entire chain can be broken down as the onceE 
definition will terminate the event. We can use this to prevent
the second of the space leaks, inherent in the use of caching.

Knowing about event termination can also be useful when programming,
for instance, it is useful when coding switcherL.

We can specify the rules for event termination in terms of the event algebra.
There are three important combinators that will affect event termination.

1. When applying onceE to an event, the new event will terminate when either 
   e terminates or after the first occurrence of e.
	termE (onceE e) == e -=> () .|. termE e

2. When merging two events, e1 and e2, the combined event will terminate when 
   either e1 terminates, and then e2 terminates; or when e2 terminates and 
   then e1 terminates. (Here the operator >> is the monadic event combinator). 
 	termE (e1 .|. e2) == (termE e1 >> termE e2) .|. (termE e2 >> termE e1)

3. When applying switcher to an event, the new event will terminate when ee 
   has terminated, and the current (last) event has terminated. We define this
   by maintaining the current event in the event-valued behavior currE. 
   When ee terminates, we snapshot it and then wait for the last event to
   terminate.
	termE (switcherE e ee) ==
	 do {e <- termE ee `snapshot` currE;termE e}
	 where currE :: Behavior (Event a)
               currE = e `stepper` ee

We cannot simply implement a termination event as a standard event as we get 
caught up in an ultimately recursive definition. We can, however, use the same
approach. If we are careful, we can include extra optimisations, as we know 
that the termination event will only ever fire once, when the event dies.


We can define a termination event as follows. It is useful to also have an 
IO action saying whether the event has now terminated.

\begin{code}
data TermE = TermE {onTerminate_ :: Listener () -> IO Remover,
                    isTerminated :: IO Bool}

hasTermE :: TermE
hasTermE = TermE (\_ -> return (return ())) (return True)

neverTermE :: TermE
neverTermE = TermE (\_ -> return (return ())) (return False)
           
onTerminate :: TermE -> Listener () -> IO Remover
onTerminate (TermE add _) l = add l
\end{code}

We create a termination event in a manner very reminiscent of the newWire 
implementation. It has three important differences. 

Firstly, the termination event has a boolean variable that records whether its
event has already terminated. We therefore need to set this variable to True 
when we fire the termination action.

Secondly, we can safely assume that an event will only terminate once. 
After we have run each termination listener, we can clear out the listener set

Thirdly, the termination listener may have been added to more than one event. 
However, we know that after it has been fired once, we do not need to wait 
for any of the other termination sources to fire. We can therefore safely 
remove the termination listener from all its sources. 

Every time we register the listener, we receive as an argument the remove 
action to unsubscribe the listener; we then generate the actual listener 
callback using this. In this case we want to store the unsubscribe action and 
then return the actual termination action. We also store the listener in a 
variable.

Note that this termination action therefore performs 5 different actions: 
(1) it tells all its termination listeners about the event termination; 
(2) it sets the termination boolean variable to True; 
(3) it clears out the termination event's listener set; 
(4) it clears its own listener from all the events the termination sources 
    that it is listening to; 
(5) finally, it sets its listener variable to contain a null listener. 
    This means that if its listener is ever added to another source again, it
    will do nothing.

\begin{code}
mkTermE :: IO (Listener (),TermE) 
mkTermE = do
 var <- newIORef False
 mset <- newMutSet
 dieRef <- newIORef (return ())
 let add l = fixIO $ \rm -> do
       op <- getListener' l rm
       addToMutSet mset (op )

 tellref <- newIORef neverL 
 let fire = mkL' $ \rm -> do
      b <- readIORef var 
      updIORef dieRef (\oldrm -> oldrm >> rm) 
      return $ \v -> do
       {foreachInMutSet mset ($ v);
        writeIORef var True;
        clearMutSet mset;
        callRemover dieRef (return ());
        writeIORef tellref neverL}

 writeIORef tellref fire

 let fireL = mkL' $ \rm -> do
        l <- readIORef tellref
        getListener' l rm

 return (fire,TermE add (readIORef var))

\end{code}


To merge two event terminators we must do two things. The merged termE will 
have terminated when both of its arguments have terminated. To add a 
termination listener to the merged termE we add a modified listener to both. 
The termination listener for one event will only be fired if the other has
already terminated.

\begin{code}
mergeTerm :: TermE -> TermE -> TermE
mergeTerm t1 t2 = TermE add terminated
  where
   terminated = do
    b1 <- isTerminated t1
    b2 <- isTerminated t2
    return $ b1 && b2 

    
   add l = do let mk :: IO Bool -> Listener () -> Listener ()
                  mk isterm = liftL1 $ \op v ->do 
                                b <- isterm
                                if b then op v
                                     else return ()
              rm1 <- onTerminate t1 (mk (isTerminated t2) l)
              rm2 <- onTerminate t2 (mk (isTerminated t1) l)
              return $ (rm1 >> rm2)
\end{code}
