module Reflex.Spider.Internal where
import qualified Reflex.Class as R
import qualified Reflex.Host.Class as R
import Data.IORef
import System.Mem.Weak
import Data.Foldable
import Data.Traversable
import Control.Monad hiding (mapM, mapM_, forM_, forM, sequence)
import Control.Monad.Identity hiding  (mapM, mapM_, forM_, forM, sequence)
import Control.Monad.Reader hiding (mapM, mapM_, forM_, forM, sequence)
import GHC.Exts
import Control.Applicative 
import Data.Dependent.Map (DMap, DSum (..))
import qualified Data.Dependent.Map as DMap
import Data.GADT.Compare
import Data.Maybe
import Data.IntMap (IntMap)
import qualified Data.IntMap as IntMap
import Control.Monad.Ref
import Control.Monad.Exception
import Data.Monoid ((<>))
import Data.Coerce
import System.IO.Unsafe
import Unsafe.Coerce
import Control.Monad.Primitive
import Prelude hiding (mapM, mapM_, any, sequence, concat)
debugPropagate :: Bool
debugInvalidateHeight :: Bool
#ifdef DEBUG
#define DEBUG_NODEIDS
debugPropagate = True
debugInvalidateHeight = True
class HasNodeId a where
  getNodeId :: a -> Int
instance HasNodeId (Hold a) where
  getNodeId = holdNodeId
instance HasNodeId (PushSubscribed a b) where
  getNodeId = pushSubscribedNodeId
instance HasNodeId (SwitchSubscribed a) where
  getNodeId = switchSubscribedNodeId
instance HasNodeId (MergeSubscribed a) where
  getNodeId = mergeSubscribedNodeId
instance HasNodeId (FanSubscribed a) where
  getNodeId = fanSubscribedNodeId
instance HasNodeId (CoincidenceSubscribed a) where
  getNodeId = coincidenceSubscribedNodeId
instance HasNodeId (RootSubscribed a) where
  getNodeId = rootSubscribedNodeId
showNodeId :: HasNodeId a => a -> String
showNodeId = ("#"<>) . show . getNodeId
#else
debugPropagate = False
debugInvalidateHeight = False
showNodeId :: a -> String
showNodeId = const ""
#endif
#ifdef DEBUG_NODEIDS
nextNodeIdRef :: IORef Int
nextNodeIdRef = unsafePerformIO $ newIORef 1
unsafeNodeId :: a -> Int
unsafeNodeId a = unsafePerformIO $ do
  touch a
  atomicModifyIORef' nextNodeIdRef $ \n -> (succ n, n)
#endif
data Hold a
   = Hold { holdValue :: !(IORef a)
          , holdInvalidators :: !(IORef [Weak Invalidator])
            
          , holdSubscriber :: !(IORef Any) 
          , holdParent :: !(IORef Any) 
#ifdef DEBUG_NODEIDS
          , holdNodeId :: Int
#endif
          }
data EventEnv
   = EventEnv { eventEnvAssignments :: !(IORef [SomeAssignment])
              , eventEnvHoldInits :: !(IORef [SomeHoldInit])
              , eventEnvClears :: !(IORef [SomeMaybeIORef])
              , eventEnvRootClears :: !(IORef [SomeDMapIORef])
              , eventEnvCurrentHeight :: !(IORef Int)
              , eventEnvCoincidenceInfos :: !(IORef [SomeCoincidenceInfo])
              , eventEnvDelayedMerges :: !(IORef (IntMap [DelayedMerge]))
              }
runEventM :: EventM a -> EventEnv -> IO a
runEventM = runReaderT . unEventM
askToAssignRef :: EventM (IORef [SomeAssignment])
askToAssignRef = EventM $ asks eventEnvAssignments
askHoldInitRef :: EventM (IORef [SomeHoldInit])
askHoldInitRef = EventM $ asks eventEnvHoldInits
getCurrentHeight :: EventM Int
getCurrentHeight = EventM $ do
  heightRef <- asks eventEnvCurrentHeight
  liftIO $ readIORef heightRef
putCurrentHeight :: Int -> EventM ()
putCurrentHeight h = EventM $ do
  heightRef <- asks eventEnvCurrentHeight
  liftIO $ writeIORef heightRef h
scheduleClear :: IORef (Maybe a) -> EventM ()
scheduleClear r = EventM $ do
  clears <- asks eventEnvClears
  liftIO $ modifyIORef' clears (SomeMaybeIORef r :)
scheduleRootClear :: IORef (DMap k Identity) -> EventM ()
scheduleRootClear r = EventM $ do
  clears <- asks eventEnvRootClears
  liftIO $ modifyIORef' clears (SomeDMapIORef r :)
scheduleMerge :: Int -> MergeSubscribed a -> EventM ()
scheduleMerge height subscribed = EventM $ do
  delayedRef <- asks eventEnvDelayedMerges
  liftIO $ modifyIORef' delayedRef $ IntMap.insertWith (++) height [DelayedMerge subscribed]
emitCoincidenceInfo :: SomeCoincidenceInfo -> EventM ()
emitCoincidenceInfo sci = EventM $ do
  ciRef <- asks eventEnvCoincidenceInfos
  liftIO $ modifyIORef' ciRef (sci:)
hold :: a -> Event a -> EventM (Behavior a)
hold v0 e = do
  holdInitRef <- askHoldInitRef
  liftIO $ do
    valRef <- newIORef v0
    invsRef <- newIORef []
    parentRef <- newIORef $ error "hold not yet initialized (parent)"
    subscriberRef <- newIORef $ error "hold not yet initialized (subscriber)"
    let h = Hold
          { holdValue = valRef
          , holdInvalidators = invsRef
          , holdSubscriber = subscriberRef
          , holdParent = parentRef
#ifdef DEBUG_NODEIDS
          , holdNodeId = unsafeNodeId (v0, e)
#endif
          }
    s <- newSubscriberHold h
    writeIORef subscriberRef $ unsafeCoerce s
    modifyIORef' holdInitRef (SomeHoldInit e h :)
    return $ BehaviorHold h
subscribeHold :: Event a -> Hold a -> EventM ()
subscribeHold e h = do
  toAssignRef <- askToAssignRef
  !s <- liftIO $ liftM unsafeCoerce $ readIORef $ holdSubscriber h 
  ws <- liftIO $ mkWeakPtrWithDebug s "holdSubscriber"
  subd <- subscribe e $ WeakSubscriberSimple ws
  liftIO $ writeIORef (holdParent h) $ unsafeCoerce subd
  occ <- liftIO $ getEventSubscribedOcc subd
  case occ of
    Nothing -> return ()
    Just o -> liftIO $ modifyIORef' toAssignRef (SomeAssignment h o :)
newtype BehaviorM a = BehaviorM { unBehaviorM :: ReaderT (Maybe (Weak Invalidator, IORef [SomeBehaviorSubscribed])) IO a } deriving (Functor, Applicative, Monad, MonadIO, MonadFix)
data BehaviorSubscribed a
   = BehaviorSubscribedHold (Hold a)
   | BehaviorSubscribedPull (PullSubscribed a)
data SomeBehaviorSubscribed = forall a. SomeBehaviorSubscribed (BehaviorSubscribed a)
data PullSubscribed a
   = PullSubscribed { pullSubscribedValue :: !a
                    , pullSubscribedInvalidators :: !(IORef [Weak Invalidator])
                    , pullSubscribedOwnInvalidator :: !Invalidator
                    , pullSubscribedParents :: ![SomeBehaviorSubscribed] 
                    }
data Pull a
   = Pull { pullValue :: !(IORef (Maybe (PullSubscribed a)))
          , pullCompute :: !(BehaviorM a)
          }
data Invalidator
   = forall a. InvalidatorPull (Pull a)
   | forall a. InvalidatorSwitch (SwitchSubscribed a)
data RootSubscribed a
   = RootSubscribed { rootSubscribedSubscribers :: !(IORef [WeakSubscriber a])
                    , rootSubscribedOccurrence :: !(IO (Maybe a)) 
                    }
data Root (k :: * -> *)
   = Root { rootOccurrence :: !(IORef (DMap k Identity)) 
          , rootSubscribed :: !(IORef (DMap k RootSubscribed))
          , rootInit :: !(forall a. k a -> RootTrigger a -> IO (IO ()))
          }
data SomeHoldInit = forall a. SomeHoldInit (Event a) (Hold a)
newtype EventM a = EventM { unEventM :: ReaderT EventEnv IO a } deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadException, MonadAsyncException) 
data PushSubscribed a b
   = PushSubscribed { pushSubscribedOccurrence :: !(IORef (Maybe b)) 
                    , pushSubscribedHeight :: !(IORef Int)
                    , pushSubscribedSubscribers :: !(IORef [WeakSubscriber b])
                    , pushSubscribedSelf :: !(Subscriber a) 
                    , pushSubscribedParent :: !(EventSubscribed a)
#ifdef DEBUG_NODEIDS
                    , pushSubscribedNodeId :: Int
#endif
                    }
data Push a b
   = Push { pushCompute :: !(a -> EventM (Maybe b)) 
          , pushParent :: !(Event a)
          , pushSubscribed :: !(IORef (Maybe (PushSubscribed a b))) 
          }
data MergeSubscribed k
   = MergeSubscribed { mergeSubscribedOccurrence :: !(IORef (Maybe (DMap k Identity)))
                     , mergeSubscribedAccum :: !(IORef (DMap k Identity)) 
                     , mergeSubscribedHeight :: !(IORef Int)
                     , mergeSubscribedSubscribers :: !(IORef [WeakSubscriber (DMap k Identity)])
                     , mergeSubscribedSelf :: !Any 
                     , mergeSubscribedParents :: !(DMap k EventSubscribed)
#ifdef DEBUG_NODEIDS
                     , mergeSubscribedNodeId :: Int
#endif
                     }
data Merge k
   = Merge { mergeParents :: !(DMap k Event)
           , mergeSubscribed :: !(IORef (Maybe (MergeSubscribed k))) 
           }
data FanSubscriberKey k a where
  FanSubscriberKey :: k a -> FanSubscriberKey k [WeakSubscriber a]
instance GEq k => GEq (FanSubscriberKey k) where
  geq (FanSubscriberKey a) (FanSubscriberKey b) = case geq a b of
    Nothing -> Nothing
    Just Refl -> Just Refl
instance GCompare k => GCompare (FanSubscriberKey k) where
  gcompare (FanSubscriberKey a) (FanSubscriberKey b) = case gcompare a b of
    GLT -> GLT
    GEQ -> GEQ
    GGT -> GGT
data FanSubscribed k
   = FanSubscribed { fanSubscribedSubscribers :: !(IORef (DMap (FanSubscriberKey k) Identity))
                   , fanSubscribedParent :: !(EventSubscribed (DMap k Identity))
                   , fanSubscribedSelf ::  (Subscriber (DMap k Identity))
#ifdef DEBUG_NODEIDS
                   , fanSubscribedNodeId :: Int
#endif
                   }
data Fan k
   = Fan { fanParent :: !(Event (DMap k Identity))
         , fanSubscribed :: !(IORef (Maybe (FanSubscribed k)))
         }
data SwitchSubscribed a
   = SwitchSubscribed { switchSubscribedOccurrence :: !(IORef (Maybe a))
                      , switchSubscribedHeight :: !(IORef Int)
                      , switchSubscribedSubscribers :: !(IORef [WeakSubscriber a])
                      , switchSubscribedSelf ::  (Subscriber a)
                      , switchSubscribedSelfWeak :: !(IORef (Weak (Subscriber a)))
                      , switchSubscribedOwnInvalidator ::  Invalidator
                      , switchSubscribedOwnWeakInvalidator :: !(IORef (Weak Invalidator))
                      , switchSubscribedBehaviorParents :: !(IORef [SomeBehaviorSubscribed])
                      , switchSubscribedParent :: !(Behavior (Event a))
                      , switchSubscribedCurrentParent :: !(IORef (EventSubscribed a))
#ifdef DEBUG_NODEIDS
                      , switchSubscribedNodeId :: Int
#endif
                      }
data Switch a
   = Switch { switchParent :: !(Behavior (Event a))
            , switchSubscribed :: !(IORef (Maybe (SwitchSubscribed a)))
            }
data CoincidenceSubscribed a
   = CoincidenceSubscribed { coincidenceSubscribedOccurrence :: !(IORef (Maybe a))
                           , coincidenceSubscribedSubscribers :: !(IORef [WeakSubscriber a])
                           , coincidenceSubscribedHeight :: !(IORef Int)
                           , coincidenceSubscribedOuter ::  (Subscriber (Event a))
                           , coincidenceSubscribedOuterParent :: !(EventSubscribed (Event a))
                           , coincidenceSubscribedInnerParent :: !(IORef (Maybe (EventSubscribed a)))
#ifdef DEBUG_NODEIDS
                           , coincidenceSubscribedNodeId :: Int
#endif
                           }
data Coincidence a
   = Coincidence { coincidenceParent :: !(Event (Event a))
                 , coincidenceSubscribed :: !(IORef (Maybe (CoincidenceSubscribed a)))
                 }
data Box a = Box { unBox :: a }
data WeakSubscriber a
   = forall k. GCompare k => WeakSubscriberMerge !(k a) !(Weak (Box (MergeSubscribed k))) 
   | WeakSubscriberSimple !(Weak (Subscriber a))
showWeakSubscriberType :: WeakSubscriber a -> String
showWeakSubscriberType = \case
  WeakSubscriberMerge _ _ -> "WeakSubscriberMerge"
  WeakSubscriberSimple _ -> "WeakSubscriberSimple"
deRefWeakSubscriber :: WeakSubscriber a -> IO (Maybe (Subscriber a))
deRefWeakSubscriber ws = case ws of
  WeakSubscriberSimple w -> deRefWeak w
  WeakSubscriberMerge k w -> liftM (fmap $ SubscriberMerge k . unBox) $ deRefWeak w
data Subscriber a
   = forall b. SubscriberPush !(a -> EventM (Maybe b)) (PushSubscribed a b)
   | forall k. GCompare k => SubscriberMerge !(k a) (MergeSubscribed k) 
   | forall k. (GCompare k, a ~ DMap k Identity) => SubscriberFan (FanSubscribed k)
   | SubscriberHold !(Hold a)
   | SubscriberSwitch (SwitchSubscribed a)
   | forall b. a ~ Event b => SubscriberCoincidenceOuter (CoincidenceSubscribed b)
   | SubscriberCoincidenceInner (CoincidenceSubscribed a)
showSubscriberType :: Subscriber a -> String
showSubscriberType = \case
  SubscriberPush _ _ -> "SubscriberPush"
  SubscriberMerge _ _ -> "SubscriberMerge"
  SubscriberFan _ -> "SubscriberFan"
  SubscriberHold _ -> "SubscriberHold"
  SubscriberSwitch _ -> "SubscriberSwitch"
  SubscriberCoincidenceOuter _ -> "SubscriberCoincidenceOuter"
  SubscriberCoincidenceInner _ -> "SubscriberCoincidenceInner"
data Event a
   = forall k. GCompare k => EventRoot !(k a) !(Root k)
   | EventNever
   | forall b. EventPush !(Push b a)
   | forall k. (GCompare k, a ~ DMap k Identity) => EventMerge !(Merge k)
   | forall k. GCompare k => EventFan !(k a) !(Fan k)
   | EventSwitch !(Switch a)
   | EventCoincidence !(Coincidence a)
showEventType :: Event a -> String
showEventType = \case
  EventRoot _ _ -> "EventRoot"
  EventNever -> "EventNever"
  EventPush _ -> "EventPush"
  EventMerge _ -> "EventMerge"
  EventFan _ _ -> "EventFan"
  EventSwitch _ -> "EventSwitch"
  EventCoincidence _ -> "EventCoincidence"
data EventSubscribed a
   = EventSubscribedRoot  (RootSubscribed a)
   | EventSubscribedNever
   | forall b. EventSubscribedPush !(PushSubscribed b a)
   | forall k. (GCompare k, a ~ DMap k Identity) => EventSubscribedMerge !(MergeSubscribed k)
   | forall k. GCompare k => EventSubscribedFan !(k a) !(FanSubscribed k)
   | EventSubscribedSwitch !(SwitchSubscribed a)
   | EventSubscribedCoincidence !(CoincidenceSubscribed a)
newRootSubscribed :: IO (Maybe a) -> IORef [WeakSubscriber a] -> IO (RootSubscribed a)
newRootSubscribed occ subs =
  return $! RootSubscribed
    { rootSubscribedOccurrence = occ
    , rootSubscribedSubscribers = subs
    }
newSubscriberPush :: (a -> EventM (Maybe b)) -> PushSubscribed a b -> IO (Subscriber a)
newSubscriberPush compute subd = return $! SubscriberPush compute subd
newSubscriberHold :: Hold a -> IO (Subscriber a)
newSubscriberHold h = return $! SubscriberHold h
newSubscriberFan :: GCompare k => FanSubscribed k -> IO (Subscriber (DMap k Identity))
newSubscriberFan subd = return $! SubscriberFan subd
newSubscriberSwitch :: SwitchSubscribed a -> IO (Subscriber a)
newSubscriberSwitch subd = return $! SubscriberSwitch subd
newSubscriberCoincidenceOuter :: CoincidenceSubscribed b -> IO (Subscriber (Event b))
newSubscriberCoincidenceOuter subd = return $! SubscriberCoincidenceOuter subd
newSubscriberCoincidenceInner :: CoincidenceSubscribed a -> IO (Subscriber a)
newSubscriberCoincidenceInner subd = return $! SubscriberCoincidenceInner subd
newInvalidatorSwitch :: SwitchSubscribed a -> IO Invalidator
newInvalidatorSwitch subd = return $! InvalidatorSwitch subd
newInvalidatorPull :: Pull a -> IO Invalidator
newInvalidatorPull p = return $! InvalidatorPull p
newBox :: a -> IO (Box a)
newBox a = return $! Box a
data Behavior a
   = BehaviorHold !(Hold a)
   | BehaviorConst !a
   | BehaviorPull !(Pull a)
type ResultM = EventM
unsafeNewIORef :: a -> b -> IORef b
unsafeNewIORef _ b = unsafePerformIO $ newIORef b
instance Functor Event where
  fmap f = push $ return . Just . f
instance Functor Behavior where
  fmap f = pull . liftM f . readBehaviorTracked
 
push :: (a -> EventM (Maybe b)) -> Event a -> Event b
push f e = EventPush $ Push
  { pushCompute = f
  , pushParent = e
  , pushSubscribed = unsafeNewIORef (f, e) Nothing 
  }
pull :: BehaviorM a -> Behavior a
pull a = BehaviorPull $ Pull
  { pullCompute = a
  , pullValue = unsafeNewIORef a Nothing
  }
switch :: Behavior (Event a) -> Event a
switch a = EventSwitch $ Switch
  { switchParent = a
  , switchSubscribed = unsafeNewIORef a Nothing
  }
coincidence :: Event (Event a) -> Event a
coincidence a = EventCoincidence $ Coincidence
  { coincidenceParent = a
  , coincidenceSubscribed = unsafeNewIORef a Nothing
  }
newRoot :: IO (Root k)
newRoot = do
  occRef <- newIORef DMap.empty
  subscribedRef <- newIORef DMap.empty
  return $ Root
    { rootOccurrence = occRef
    , rootSubscribed = subscribedRef
    , rootInit = \_ _ -> return $ return ()
    }
propagateAndUpdateSubscribersRef :: IORef [WeakSubscriber a] -> a -> EventM ()
propagateAndUpdateSubscribersRef subscribersRef a = do
  subscribers <- liftIO $ readIORef subscribersRef
  liftIO $ writeIORef subscribersRef []
  stillAlive <- propagate a subscribers
  liftIO $ modifyIORef' subscribersRef (++stillAlive)
run :: [DSum RootTrigger Identity] -> ResultM b -> IO b
run roots after = do
  when debugPropagate $ putStrLn "Running an event frame"
  result <- runFrame $ do
    rootsToPropagate <- forM roots $ \r@(RootTrigger (_, occRef, k) :=> a) -> do
      occBefore <- liftIO $ do
        occBefore <- readIORef occRef
        writeIORef occRef $ DMap.insert k a occBefore
        return occBefore
      if DMap.null occBefore
        then do scheduleRootClear occRef
                return $ Just r
        else return Nothing
    forM_ (catMaybes rootsToPropagate) $ \(RootTrigger (subscribersRef, _, _) :=> Identity a) -> do
      propagateAndUpdateSubscribersRef subscribersRef a
    delayedRef <- EventM $ asks eventEnvDelayedMerges
    let go = do
          delayed <- liftIO $ readIORef delayedRef
          case IntMap.minViewWithKey delayed of
            Nothing -> return ()
            Just ((currentHeight, current), future) -> do
              when debugPropagate $ liftIO $ putStrLn $ "Running height " ++ show currentHeight
              putCurrentHeight currentHeight
              liftIO $ writeIORef delayedRef future
              forM_ current $ \d -> case d of
                DelayedMerge subscribed -> do
                  height <- liftIO $ readIORef $ mergeSubscribedHeight subscribed
                  case height `compare` currentHeight of
                    LT -> error "Somehow a merge's height has been decreased after it was scheduled"
                    GT -> scheduleMerge height subscribed 
                    EQ -> do
                      m <- liftIO $ readIORef $ mergeSubscribedAccum subscribed
                      liftIO $ writeIORef (mergeSubscribedAccum subscribed) DMap.empty
                      
                      liftIO $ writeIORef (mergeSubscribedOccurrence subscribed) $ Just m
                      scheduleClear $ mergeSubscribedOccurrence subscribed
                      propagateAndUpdateSubscribersRef (mergeSubscribedSubscribers subscribed) m
              go
    go
    putCurrentHeight maxBound
    after
  when debugPropagate $ putStrLn "Done running an event frame"
  return result
data SomeMaybeIORef = forall a. SomeMaybeIORef (IORef (Maybe a))
data SomeDMapIORef = forall k. SomeDMapIORef (IORef (DMap k Identity))
data SomeAssignment = forall a. SomeAssignment (Hold a) a
data DelayedMerge = forall k. DelayedMerge (MergeSubscribed k)
debugFinalize :: Bool
debugFinalize = False
mkWeakPtrWithDebug :: a -> String -> IO (Weak a)
mkWeakPtrWithDebug x debugNote = mkWeakPtr x $
  if debugFinalize
  then Just $ putStrLn $ "finalizing: " ++ debugNote
  else Nothing
type WeakList a = [Weak a]
traverseAndCleanWeakList_ :: Monad m => (wa -> m (Maybe a)) -> [wa] -> (a -> m ()) -> m [wa]
traverseAndCleanWeakList_ deRef ws f = go ws
  where go [] = return []
        go (h:t) = do
          ma <- deRef h
          case ma of
            Just a -> do
              f a
              t' <- go t
              return $ h : t'
            Nothing -> go t
propagate :: a -> [WeakSubscriber a] -> EventM [WeakSubscriber a]
propagate a subscribers = do
  traverseAndCleanWeakList_ (liftIO . deRefWeakSubscriber) subscribers $ \s -> case s of
    SubscriberPush compute subscribed -> do
      when debugPropagate $ liftIO $ putStrLn $ "SubscriberPush" <> showNodeId subscribed
      occ <- compute a
      case occ of
        Nothing -> return () 
        Just o -> do
          liftIO $ writeIORef (pushSubscribedOccurrence subscribed) occ
          scheduleClear $ pushSubscribedOccurrence subscribed
          liftIO . writeIORef (pushSubscribedSubscribers subscribed) =<< propagate o =<< liftIO (readIORef (pushSubscribedSubscribers subscribed))
    SubscriberMerge k subscribed -> do
      when debugPropagate $ liftIO $ putStrLn $ "SubscriberMerge" <> showNodeId subscribed
      oldM <- liftIO $ readIORef $ mergeSubscribedAccum subscribed
      liftIO $ writeIORef (mergeSubscribedAccum subscribed) $ DMap.insertWith (error "Same key fired multiple times for") k (Identity a) oldM
      when (DMap.null oldM) $ do 
        height <- liftIO $ readIORef $ mergeSubscribedHeight subscribed
        
        currentHeight <- getCurrentHeight
        when (height <= currentHeight) $ error $ "Height (" ++ show height ++ ") is not greater than current height (" ++ show currentHeight ++ ")"
        scheduleMerge height subscribed
    SubscriberFan subscribed -> do
      subs <- liftIO $ readIORef $ fanSubscribedSubscribers subscribed
      when debugPropagate $ liftIO $ putStrLn $ "SubscriberFan" <> showNodeId subscribed <> ": " ++ show (DMap.size subs) ++ " keys subscribed, " ++ show (DMap.size a) ++ " keys firing"
      
      forM_ (DMap.toList a) $ \(k :=> Identity v) -> case DMap.lookup (FanSubscriberKey k) subs of
        Nothing -> do
          when debugPropagate $ liftIO $ putStrLn "No subscriber for key"
          return ()
        Just (Identity subsubs) -> do
          _ <- propagate v subsubs 
          return ()
      
      subs' <- liftIO $ forM (DMap.toList subs) $ ((\(FanSubscriberKey k :=> Identity subsubs) -> do
        subsubs' <- traverseAndCleanWeakList_ (liftIO . deRefWeakSubscriber) subsubs (const $ return ())
        return $ if null subsubs'
                    then Nothing
                    else Just $ FanSubscriberKey k :=> Identity subsubs') :: DSum (FanSubscriberKey k) Identity -> IO (Maybe (DSum (FanSubscriberKey k) Identity)))
      liftIO $ writeIORef (fanSubscribedSubscribers subscribed) $ DMap.fromDistinctAscList $ catMaybes subs'
    SubscriberHold h -> do
      invalidators <- liftIO $ readIORef $ holdInvalidators h
      when debugPropagate $ liftIO $ putStrLn $ "SubscriberHold" <> showNodeId h <> ": " ++ show (length invalidators)
      toAssignRef <- askToAssignRef
      liftIO $ modifyIORef' toAssignRef (SomeAssignment h a :)
    SubscriberSwitch subscribed -> do
      when debugPropagate $ liftIO $ putStrLn $ "SubscriberSwitch" <> showNodeId subscribed
      liftIO $ writeIORef (switchSubscribedOccurrence subscribed) $ Just a
      scheduleClear $ switchSubscribedOccurrence subscribed
      subs <- liftIO $ readIORef $ switchSubscribedSubscribers subscribed
      liftIO . writeIORef (switchSubscribedSubscribers subscribed) =<< propagate a subs
    SubscriberCoincidenceOuter subscribed -> do
      when debugPropagate $ liftIO $ putStrLn $ "SubscriberCoincidenceOuter" <> showNodeId subscribed
      outerHeight <- liftIO $ readIORef $ coincidenceSubscribedHeight subscribed
      when debugPropagate $ liftIO $ putStrLn $ "  outerHeight = " <> show outerHeight
      (occ, innerHeight, innerSubd) <- subscribeCoincidenceInner a outerHeight subscribed
      when debugPropagate $ liftIO $ putStrLn $ "  isJust occ = " <> show (isJust occ)
      when debugPropagate $ liftIO $ putStrLn $ "  innerHeight = " <> show innerHeight
      liftIO $ writeIORef (coincidenceSubscribedInnerParent subscribed) $ Just innerSubd
      scheduleClear $ coincidenceSubscribedInnerParent subscribed
      case occ of
        Nothing -> do
          when (innerHeight > outerHeight) $ liftIO $ do 
            writeIORef (coincidenceSubscribedHeight subscribed) innerHeight
            mapM_ invalidateSubscriberHeight =<< readIORef (coincidenceSubscribedSubscribers subscribed)
            mapM_ recalculateSubscriberHeight =<< readIORef (coincidenceSubscribedSubscribers subscribed)
        Just o -> do 
          liftIO $ writeIORef (coincidenceSubscribedOccurrence subscribed) occ
          scheduleClear $ coincidenceSubscribedOccurrence subscribed
          liftIO . writeIORef (coincidenceSubscribedSubscribers subscribed) =<< propagate o =<< liftIO (readIORef (coincidenceSubscribedSubscribers subscribed))
    SubscriberCoincidenceInner subscribed -> do
      when debugPropagate $ liftIO $ putStrLn $ "SubscriberCoincidenceInner" <> showNodeId subscribed
      liftIO $ writeIORef (coincidenceSubscribedOccurrence subscribed) $ Just a
      scheduleClear $ coincidenceSubscribedOccurrence subscribed
      liftIO . writeIORef (coincidenceSubscribedSubscribers subscribed) =<< propagate a =<< liftIO (readIORef (coincidenceSubscribedSubscribers subscribed))
data SomeCoincidenceInfo = forall a. SomeCoincidenceInfo (Weak (Subscriber a)) (Subscriber a) (Maybe (CoincidenceSubscribed a)) 
subscribeCoincidenceInner :: Event a -> Int -> CoincidenceSubscribed a -> EventM (Maybe a, Int, EventSubscribed a)
subscribeCoincidenceInner o outerHeight subscribedUnsafe = do
  subInner <- liftIO $ newSubscriberCoincidenceInner subscribedUnsafe
  wsubInner <- liftIO $ mkWeakPtrWithDebug subInner "SubscriberCoincidenceInner"
  innerSubd <-  (subscribe o $ WeakSubscriberSimple wsubInner)
  innerOcc <- liftIO $ getEventSubscribedOcc innerSubd
  innerHeight <- liftIO $ readIORef $ eventSubscribedHeightRef innerSubd
  let height = max innerHeight outerHeight
  emitCoincidenceInfo $ SomeCoincidenceInfo wsubInner subInner $ if height > outerHeight then Just subscribedUnsafe else Nothing
  return (innerOcc, height, innerSubd)
readBehavior :: Behavior a -> IO a
readBehavior b = runBehaviorM (readBehaviorTracked b) Nothing 
runBehaviorM :: BehaviorM a -> Maybe (Weak Invalidator, IORef [SomeBehaviorSubscribed]) -> IO a
runBehaviorM a mwi = runReaderT (unBehaviorM a) mwi
askInvalidator :: BehaviorM (Maybe (Weak Invalidator))
askInvalidator = liftM (fmap fst) $ BehaviorM ask
askParentsRef :: BehaviorM (Maybe (IORef [SomeBehaviorSubscribed]))
askParentsRef = liftM (fmap snd) $ BehaviorM ask
readBehaviorTracked :: Behavior a -> BehaviorM a
readBehaviorTracked b = case b of
  BehaviorHold h -> do
    result <- liftIO $ readIORef $ holdValue h
    askInvalidator >>= mapM_ (\wi -> liftIO $ modifyIORef' (holdInvalidators h) (wi:))
    askParentsRef >>= mapM_ (\r -> liftIO $ modifyIORef' r (SomeBehaviorSubscribed (BehaviorSubscribedHold h) :))
    liftIO $ touch $ holdSubscriber h
    return result
  BehaviorConst a -> return a
  BehaviorPull p -> do
    val <- liftIO $ readIORef $ pullValue p
    case val of
      Just subscribed -> do
        askParentsRef >>= mapM_ (\r -> liftIO $ modifyIORef' r (SomeBehaviorSubscribed (BehaviorSubscribedPull subscribed) :))
        askInvalidator >>= mapM_ (\wi -> liftIO $ modifyIORef' (pullSubscribedInvalidators subscribed) (wi:))
        liftIO $ touch $ pullSubscribedOwnInvalidator subscribed
        return $ pullSubscribedValue subscribed
      Nothing -> do
        i <- liftIO $ newInvalidatorPull p
        wi <- liftIO $ mkWeakPtrWithDebug i "InvalidatorPull"
        parentsRef <- liftIO $ newIORef []
        a <- liftIO $ runReaderT (unBehaviorM $ pullCompute p) $ Just (wi, parentsRef)
        invsRef <- liftIO . newIORef . maybeToList =<< askInvalidator
        parents <- liftIO $ readIORef parentsRef
        let subscribed = PullSubscribed
              { pullSubscribedValue = a
              , pullSubscribedInvalidators = invsRef
              , pullSubscribedOwnInvalidator = i
              , pullSubscribedParents = parents
              }
        liftIO $ writeIORef (pullValue p) $ Just subscribed
        askParentsRef >>= mapM_ (\r -> liftIO $ modifyIORef' r (SomeBehaviorSubscribed (BehaviorSubscribedPull subscribed) :))
        return a
readEvent :: Event a -> ResultM (Maybe a)
readEvent e = case e of
  EventRoot k r -> liftIO . liftM (coerce . DMap.lookup k) . readIORef $ rootOccurrence r
  EventNever -> return Nothing
  EventPush p -> do
    subscribed <- getPushSubscribed p
    liftIO $ do
      result <- readIORef $ pushSubscribedOccurrence subscribed 
      touch $ pushSubscribedSelf subscribed
      return result
  EventMerge m -> do
    subscribed <- getMergeSubscribed m
    liftIO $ do
      result <- readIORef $ mergeSubscribedOccurrence subscribed
      touch $ mergeSubscribedSelf subscribed
      return result
  EventFan k f -> do
    parentOcc <- readEvent $ fanParent f
    return . coerce $ DMap.lookup k =<< parentOcc
  EventSwitch s -> do
    subscribed <- getSwitchSubscribed s
    liftIO $ do
      result <- readIORef $ switchSubscribedOccurrence subscribed
      touch $ switchSubscribedSelf subscribed
      touch $ switchSubscribedOwnInvalidator subscribed
      return result
  EventCoincidence c -> do
    subscribed <- getCoincidenceSubscribed c
    liftIO $ do
      result <- readIORef $ coincidenceSubscribedOccurrence subscribed
      touch $ coincidenceSubscribedOuter subscribed
      
      return result
zeroRef :: IORef Int
zeroRef = unsafePerformIO $ newIORef 0
getEventSubscribed :: Event a -> EventM (EventSubscribed a)
getEventSubscribed e = case e of
  EventRoot k r -> liftM EventSubscribedRoot $ getRootSubscribed k r
  EventNever -> return EventSubscribedNever
  EventPush p -> liftM EventSubscribedPush $ getPushSubscribed p
  EventFan k f -> liftM (EventSubscribedFan k) $ getFanSubscribed f
  EventMerge m -> liftM EventSubscribedMerge $ getMergeSubscribed m
  EventSwitch s -> liftM EventSubscribedSwitch $ getSwitchSubscribed s
  EventCoincidence c -> liftM EventSubscribedCoincidence $ getCoincidenceSubscribed c
debugSubscribe :: Bool
debugSubscribe = False
subscribeEventSubscribed :: EventSubscribed a -> WeakSubscriber a -> IO ()
subscribeEventSubscribed es ws = case es of
  EventSubscribedRoot r -> do
    when debugSubscribe $ liftIO $ putStrLn $ "subscribeEventSubscribed Root"
    modifyIORef' (rootSubscribedSubscribers r) (ws:)
  EventSubscribedNever -> do
    when debugSubscribe $ liftIO $ putStrLn $ "subscribeEventSubscribed Never"
    return ()
  EventSubscribedPush subscribed -> do
    when debugSubscribe $ liftIO $ putStrLn $ "subscribeEventSubscribed Push"
    modifyIORef' (pushSubscribedSubscribers subscribed) (ws:)
  EventSubscribedFan k subscribed -> do
    when debugSubscribe $ liftIO $ putStrLn $ "subscribeEventSubscribed Fan"
    modifyIORef' (fanSubscribedSubscribers subscribed) $ DMap.insertWith (liftA2 (++)) (FanSubscriberKey k) (Identity [ws])
  EventSubscribedMerge subscribed -> do
    when debugSubscribe $ liftIO $ putStrLn $ "subscribeEventSubscribed Merge"
    modifyIORef' (mergeSubscribedSubscribers subscribed) (ws:)
  EventSubscribedSwitch subscribed -> do
    when debugSubscribe $ liftIO $ putStrLn $ "subscribeEventSubscribed Switch"
    modifyIORef' (switchSubscribedSubscribers subscribed) (ws:)
  EventSubscribedCoincidence subscribed -> do
    when debugSubscribe $ liftIO $ putStrLn $ "subscribeEventSubscribed Coincidence"
    modifyIORef' (coincidenceSubscribedSubscribers subscribed) (ws:)
getEventSubscribedOcc :: EventSubscribed a -> IO (Maybe a)
getEventSubscribedOcc es = case es of
  EventSubscribedRoot r -> rootSubscribedOccurrence r
  EventSubscribedNever -> return Nothing
  EventSubscribedPush subscribed -> readIORef $ pushSubscribedOccurrence subscribed
  EventSubscribedFan k subscribed -> do
    parentOcc <- getEventSubscribedOcc $ fanSubscribedParent subscribed
    let occ = coerce $ DMap.lookup k =<< parentOcc
    return occ
  EventSubscribedMerge subscribed -> readIORef $ mergeSubscribedOccurrence subscribed
  EventSubscribedSwitch subscribed -> readIORef $ switchSubscribedOccurrence subscribed
  EventSubscribedCoincidence subscribed -> readIORef $ coincidenceSubscribedOccurrence subscribed
eventSubscribedHeightRef :: EventSubscribed a -> IORef Int
eventSubscribedHeightRef es = case es of
  EventSubscribedRoot _ -> zeroRef
  EventSubscribedNever -> zeroRef
  EventSubscribedPush subscribed -> pushSubscribedHeight subscribed
  EventSubscribedFan _ subscribed -> eventSubscribedHeightRef $ fanSubscribedParent subscribed
  EventSubscribedMerge subscribed -> mergeSubscribedHeight subscribed
  EventSubscribedSwitch subscribed -> switchSubscribedHeight subscribed
  EventSubscribedCoincidence subscribed -> coincidenceSubscribedHeight subscribed
subscribe :: Event a -> WeakSubscriber a -> EventM (EventSubscribed a)
subscribe e ws = do
  subd <- getEventSubscribed e
  liftIO $ subscribeEventSubscribed subd ws
  return subd
noinlineFalse :: Bool
noinlineFalse = False
getRootSubscribed :: GCompare k => k a -> Root k -> EventM (RootSubscribed a)
getRootSubscribed k r = do
  mSubscribed <- liftIO $ readIORef $ rootSubscribed r
  case DMap.lookup k mSubscribed of
    Just subscribed -> return subscribed
    Nothing -> liftIO $ do
      subscribersRef <- newIORef []
      subscribed <- newRootSubscribed (liftM (coerce . DMap.lookup k) $ readIORef $ rootOccurrence r) subscribersRef
      
      uninit <- rootInit r k $ RootTrigger (subscribersRef, rootOccurrence r, k)
      addFinalizer subscribed $ do
        when noinlineFalse $ putStr "" 
        uninit
      liftIO $ modifyIORef' (rootSubscribed r) $ DMap.insert k subscribed
      return subscribed
getPushSubscribed :: Push a b -> EventM (PushSubscribed a b)
getPushSubscribed p = do
  mSubscribed <- liftIO $ readIORef $ pushSubscribed p
  case mSubscribed of
    Just subscribed -> return subscribed
    Nothing -> do 
      subscribedUnsafe <- liftIO $ unsafeInterleaveIO $ liftM fromJust $ readIORef $ pushSubscribed p
      s <- liftIO $ newSubscriberPush (pushCompute p) subscribedUnsafe
      ws <- liftIO $ mkWeakPtrWithDebug s "SubscriberPush"
      subd <- subscribe (pushParent p) $ WeakSubscriberSimple ws
      parentOcc <- liftIO $ getEventSubscribedOcc subd
      occ <- liftM join $ mapM (pushCompute p) parentOcc
      occRef <- liftIO $ newIORef occ
      when (isJust occ) $ scheduleClear occRef
      subscribersRef <- liftIO $ newIORef []
      let subscribed = PushSubscribed
            { pushSubscribedOccurrence = occRef
            , pushSubscribedHeight = eventSubscribedHeightRef subd 
            , pushSubscribedSubscribers = subscribersRef
            , pushSubscribedSelf = unsafeCoerce s
            , pushSubscribedParent = subd
#ifdef DEBUG_NODEIDS
            , pushSubscribedNodeId = unsafeNodeId p
#endif
            }
      liftIO $ writeIORef (pushSubscribed p) $ Just subscribed
      return subscribed
getMergeSubscribed :: forall k. GCompare k => Merge k -> EventM (MergeSubscribed k)
getMergeSubscribed m =  do
  mSubscribed <- liftIO $ readIORef $ mergeSubscribed m
  case mSubscribed of
    Just subscribed -> return subscribed
    Nothing -> if DMap.null $ mergeParents m then emptyMergeSubscribed else do
      subscribedRef <- liftIO $ newIORef $ error "getMergeSubscribed: subscribedRef not yet initialized"
      subscribedUnsafe <- liftIO $ unsafeInterleaveIO $ readIORef subscribedRef
      s <- liftIO $ newBox subscribedUnsafe
      ws <- liftIO $ mkWeakPtrWithDebug s "SubscriberMerge"
      subscribers :: [(Any, Maybe (DSum k Identity), Int, DSum k EventSubscribed)] <- forM (DMap.toList $ mergeParents m) $  \(k :=> e) ->  do
        parentSubd <-  subscribe e $ WeakSubscriberMerge k ws
        parentOcc <-  liftIO $ getEventSubscribedOcc parentSubd
        height <-  liftIO $ readIORef $ eventSubscribedHeightRef parentSubd
        return $  (unsafeCoerce s :: Any, fmap (\x -> k :=> Identity x) parentOcc, height, k :=> parentSubd)
      let dm = DMap.fromDistinctAscList $ catMaybes $ map (\(_, x, _, _) -> x) subscribers
          subscriberHeights = map (\(_, _, x, _) -> x) subscribers
          myHeight =
            if any (==invalidHeight) subscriberHeights 
            then invalidHeight
            else succ $ Prelude.maximum subscriberHeights 
      currentHeight <- getCurrentHeight
      let (occ, accum) = if currentHeight >= myHeight 
                         then (if DMap.null dm then Nothing else Just dm, DMap.empty)
                         else (Nothing, dm)
      when (not $ DMap.null accum) $ do
        scheduleMerge myHeight subscribedUnsafe
      occRef <- liftIO $ newIORef occ
      when (isJust occ) $ scheduleClear occRef
      accumRef <- liftIO $ newIORef accum
      heightRef <- liftIO $ newIORef myHeight
      subsRef <- liftIO $ newIORef []
      let subscribed = MergeSubscribed
            { mergeSubscribedOccurrence = occRef
            , mergeSubscribedAccum = accumRef
            , mergeSubscribedHeight = heightRef
            , mergeSubscribedSubscribers = subsRef
            , mergeSubscribedSelf = unsafeCoerce $ map (\(x, _, _, _) -> x) subscribers 
            , mergeSubscribedParents = DMap.fromDistinctAscList $ map (\(_, _, _, x) -> x) subscribers
#ifdef DEBUG_NODEIDS
            , mergeSubscribedNodeId = unsafeNodeId m
#endif
            }
      liftIO $ writeIORef subscribedRef subscribed
      return subscribed
  where emptyMergeSubscribed = do 
          occRef <- liftIO $ newIORef Nothing
          accumRef <- liftIO $ newIORef DMap.empty
          subsRef <- liftIO $ newIORef []
          return $ MergeSubscribed
            { mergeSubscribedOccurrence = occRef
            , mergeSubscribedAccum = accumRef
            , mergeSubscribedHeight = zeroRef
            , mergeSubscribedSubscribers = subsRef 
            , mergeSubscribedSelf = unsafeCoerce ()
            , mergeSubscribedParents = DMap.empty
#ifdef DEBUG_NODEIDS
            , mergeSubscribedNodeId = 1
#endif
            }
getFanSubscribed :: GCompare k => Fan k -> EventM (FanSubscribed k)
getFanSubscribed f = do
  mSubscribed <- liftIO $ readIORef $ fanSubscribed f
  case mSubscribed of
    Just subscribed -> return subscribed
    Nothing -> do
      subscribedUnsafe <- liftIO $ unsafeInterleaveIO $ liftM fromJust $ readIORef $ fanSubscribed f
      sub <- liftIO $ newSubscriberFan subscribedUnsafe
      wsub <- liftIO $ mkWeakPtrWithDebug sub "SubscriberFan"
      subd <- subscribe (fanParent f) $ WeakSubscriberSimple wsub
      subscribersRef <- liftIO $ newIORef DMap.empty
      let subscribed = FanSubscribed
            { fanSubscribedParent = subd
            , fanSubscribedSubscribers = subscribersRef
            , fanSubscribedSelf = sub
#ifdef DEBUG_NODEIDS
            , fanSubscribedNodeId = unsafeNodeId f
#endif
            }
      liftIO $ writeIORef (fanSubscribed f) $ Just subscribed
      return subscribed
getSwitchSubscribed :: Switch a -> EventM (SwitchSubscribed a)
getSwitchSubscribed s = do
  mSubscribed <- liftIO $ readIORef $ switchSubscribed s
  case mSubscribed of
    Just subscribed -> return subscribed
    Nothing -> do
      subscribedRef <- liftIO $ newIORef $ error "getSwitchSubscribed: subscribed has not yet been created"
      subscribedUnsafe <- liftIO $ unsafeInterleaveIO $ readIORef subscribedRef
      i <- liftIO $ newInvalidatorSwitch subscribedUnsafe
      sub <- liftIO $ newSubscriberSwitch subscribedUnsafe
      wi <- liftIO $ mkWeakPtrWithDebug i "InvalidatorSwitch"
      wiRef <- liftIO $ newIORef wi
      wsub <- liftIO $ mkWeakPtrWithDebug sub "SubscriberSwitch"
      selfWeakRef <- liftIO $ newIORef wsub
      parentsRef <- liftIO $ newIORef [] 
      e <- liftIO $ runBehaviorM (readBehaviorTracked (switchParent s)) $ Just (wi, parentsRef)
      subd <- subscribe e $ WeakSubscriberSimple wsub
      subdRef <- liftIO $ newIORef subd
      parentOcc <- liftIO $ getEventSubscribedOcc subd
      occRef <- liftIO $ newIORef parentOcc
      when (isJust parentOcc) $ scheduleClear occRef
      heightRef <- liftIO $ newIORef =<< readIORef (eventSubscribedHeightRef subd)
      subscribersRef <- liftIO $ newIORef []
      let subscribed = SwitchSubscribed
            { switchSubscribedOccurrence = occRef
            , switchSubscribedHeight = heightRef
            , switchSubscribedSubscribers = subscribersRef
            , switchSubscribedSelf = sub
            , switchSubscribedSelfWeak = selfWeakRef
            , switchSubscribedOwnInvalidator = i
            , switchSubscribedOwnWeakInvalidator = wiRef
            , switchSubscribedBehaviorParents = parentsRef
            , switchSubscribedParent = switchParent s
            , switchSubscribedCurrentParent = subdRef
#ifdef DEBUG_NODEIDS
            , switchSubscribedNodeId = unsafeNodeId s
#endif
            }
      liftIO $ writeIORef subscribedRef subscribed
      liftIO $ writeIORef (switchSubscribed s) $ Just subscribed
      return subscribed
getCoincidenceSubscribed :: forall a. Coincidence a -> EventM (CoincidenceSubscribed a)
getCoincidenceSubscribed c = do
  mSubscribed <- liftIO $ readIORef $ coincidenceSubscribed c
  case mSubscribed of
    Just subscribed -> return subscribed
    Nothing -> do
      subscribedRef <- liftIO $ newIORef $ error "getCoincidenceSubscribed: subscribed has not yet been created"
      subscribedUnsafe <- liftIO $ unsafeInterleaveIO $ readIORef subscribedRef
      subOuter <- liftIO $ newSubscriberCoincidenceOuter subscribedUnsafe
      wsubOuter <- liftIO $ mkWeakPtrWithDebug subOuter "subOuter"
      outerSubd <- subscribe (coincidenceParent c) $ WeakSubscriberSimple wsubOuter
      outerOcc <- liftIO $ getEventSubscribedOcc outerSubd
      outerHeight <- liftIO $ readIORef $ eventSubscribedHeightRef outerSubd
      (occ, height, mInnerSubd) <- case outerOcc of
        Nothing -> return (Nothing, outerHeight, Nothing)
        Just o -> do
          (occ, height, innerSubd) <- subscribeCoincidenceInner o outerHeight subscribedUnsafe
          return (occ, height, Just innerSubd)
      occRef <- liftIO $ newIORef occ
      when (isJust occ) $ scheduleClear occRef
      heightRef <- liftIO $ newIORef height
      innerSubdRef <- liftIO $ newIORef mInnerSubd
      scheduleClear innerSubdRef
      subscribersRef <- liftIO $ newIORef []
      let subscribed = CoincidenceSubscribed
            { coincidenceSubscribedOccurrence = occRef
            , coincidenceSubscribedHeight = heightRef
            , coincidenceSubscribedSubscribers = subscribersRef
            , coincidenceSubscribedOuter = subOuter
            , coincidenceSubscribedOuterParent = outerSubd
            , coincidenceSubscribedInnerParent = innerSubdRef
#ifdef DEBUG_NODEIDS
            , coincidenceSubscribedNodeId = unsafeNodeId c
#endif
            }
      liftIO $ writeIORef subscribedRef subscribed
      liftIO $ writeIORef (coincidenceSubscribed c) $ Just subscribed
      return subscribed
merge :: GCompare k => DMap k Event -> Event (DMap k Identity)
merge m = EventMerge $ Merge
  { mergeParents = m
  , mergeSubscribed = unsafeNewIORef m Nothing
  }
newtype EventSelector k = EventSelector { select :: forall a. k a -> Event a }
fan :: GCompare k => Event (DMap k Identity) -> EventSelector k
fan e =
  let f = Fan
        { fanParent = e
        , fanSubscribed = unsafeNewIORef e Nothing
        }
  in EventSelector $ \k -> EventFan k f
runFrame :: EventM a -> IO a
runFrame a = do
  toAssignRef <- newIORef [] 
  holdInitRef <- newIORef []
  heightRef <- newIORef 0
  toClearRef <- newIORef []
  toClearRootRef <- newIORef []
  coincidenceInfosRef <- newIORef []
  delayedRef <- liftIO $ newIORef IntMap.empty
  result <- flip runEventM (EventEnv toAssignRef holdInitRef toClearRef toClearRootRef heightRef coincidenceInfosRef delayedRef) $ do
    result <- a
    let runHoldInits = do
          holdInits <- liftIO $ readIORef holdInitRef
          if null holdInits then return () else do
            liftIO $ writeIORef holdInitRef []
            forM_ holdInits $ \(SomeHoldInit e h) -> subscribeHold e h
            runHoldInits
    runHoldInits 
    return result
  toClear <- readIORef toClearRef
  forM_ toClear $ \(SomeMaybeIORef ref) -> writeIORef ref Nothing
  toClearRoot <- readIORef toClearRootRef
  forM_ toClearRoot $ \(SomeDMapIORef ref) -> writeIORef ref DMap.empty
  toAssign <- readIORef toAssignRef
  toReconnectRef <- newIORef []
  forM_ toAssign $ \(SomeAssignment h v) -> do
    writeIORef (holdValue h) v
    writeIORef (holdInvalidators h) =<< invalidate toReconnectRef =<< readIORef (holdInvalidators h)
  coincidenceInfos <- readIORef coincidenceInfosRef
  forM_ coincidenceInfos $ \(SomeCoincidenceInfo wsubInner subInner mcs) -> do
    touch subInner
    finalize wsubInner
    mapM_ invalidateCoincidenceHeight mcs
  toReconnect <- readIORef toReconnectRef
  forM_ toReconnect $ \(SomeSwitchSubscribed subscribed) -> do
    wsub <- readIORef $ switchSubscribedSelfWeak subscribed
    finalize wsub
    wi <- readIORef $ switchSubscribedOwnWeakInvalidator subscribed
    finalize wi
    let !i = switchSubscribedOwnInvalidator subscribed
    wi' <- mkWeakPtrWithDebug i "wi'"
    writeIORef (switchSubscribedBehaviorParents subscribed) []
    e <- runBehaviorM (readBehaviorTracked (switchSubscribedParent subscribed)) (Just (wi', switchSubscribedBehaviorParents subscribed))
    
    let !sub = switchSubscribedSelf subscribed 
    wsub' <- mkWeakPtrWithDebug sub "wsub'"
    writeIORef (switchSubscribedSelfWeak subscribed) wsub'
    subd' <- runFrame $ subscribe e $ WeakSubscriberSimple wsub' 
    
    writeIORef (switchSubscribedCurrentParent subscribed) subd'
    parentHeight <- readIORef $ eventSubscribedHeightRef subd'
    myHeight <- readIORef $ switchSubscribedHeight subscribed
    if parentHeight == myHeight then return () else do
      writeIORef (switchSubscribedHeight subscribed) parentHeight
      mapM_ invalidateSubscriberHeight =<< readIORef (switchSubscribedSubscribers subscribed)
  forM_ coincidenceInfos $ \(SomeCoincidenceInfo _ _ mcs) -> mapM_ recalculateCoincidenceHeight mcs
  forM_ toReconnect $ \(SomeSwitchSubscribed subscribed) -> do
    mapM_ recalculateSubscriberHeight =<< readIORef (switchSubscribedSubscribers subscribed)
  return result
invalidHeight :: Int
invalidHeight = 1000
invalidateSubscriberHeight :: WeakSubscriber a -> IO ()
invalidateSubscriberHeight ws = do
  ms <- deRefWeakSubscriber ws
  case ms of
    Nothing -> return () 
    Just s -> case s of
      SubscriberPush _ subscribed -> do
        when debugInvalidateHeight $ putStrLn $ "invalidateSubscriberHeight: SubscriberPush" <> showNodeId subscribed
        mapM_ invalidateSubscriberHeight =<< readIORef (pushSubscribedSubscribers subscribed)
        when debugInvalidateHeight $ putStrLn $ "invalidateSubscriberHeight: SubscriberPush" <> showNodeId subscribed <> " done"
      SubscriberMerge _ subscribed -> do
        when debugInvalidateHeight $ putStrLn $ "invalidateSubscriberHeight: SubscriberMerge" <> showNodeId subscribed
        oldHeight <- readIORef $ mergeSubscribedHeight subscribed
        when (oldHeight /= invalidHeight) $ do
          writeIORef (mergeSubscribedHeight subscribed) $ invalidHeight
          mapM_ invalidateSubscriberHeight =<< readIORef (mergeSubscribedSubscribers subscribed)
        when debugInvalidateHeight $ putStrLn $ "invalidateSubscriberHeight: SubscriberMerge" <> showNodeId subscribed <> " done"
      SubscriberFan subscribed -> do
        when debugInvalidateHeight $ putStrLn $ "invalidateSubscriberHeight: SubscriberFan" <> showNodeId subscribed
        subscribers <- readIORef $ fanSubscribedSubscribers subscribed
        forM_ (DMap.toList subscribers) $ ((\(FanSubscriberKey _ :=> Identity v) -> mapM_ invalidateSubscriberHeight v) :: DSum (FanSubscriberKey k) Identity -> IO ())
        when debugInvalidateHeight $ putStrLn $ "invalidateSubscriberHeight: SubscriberFan" <> showNodeId subscribed <> " done"
      SubscriberHold _ -> return ()
      SubscriberSwitch subscribed -> do
        when debugInvalidateHeight $ putStrLn $ "invalidateSubscriberHeight: SubscriberSwitch" <> showNodeId subscribed
        oldHeight <- readIORef $ switchSubscribedHeight subscribed
        when (oldHeight /= invalidHeight) $ do
          writeIORef (switchSubscribedHeight subscribed) $ invalidHeight
          mapM_ invalidateSubscriberHeight =<< readIORef (switchSubscribedSubscribers subscribed)
        when debugInvalidateHeight $ putStrLn $ "invalidateSubscriberHeight: SubscriberSwitch" <> showNodeId subscribed <> " done"
      SubscriberCoincidenceOuter subscribed -> do
        when debugInvalidateHeight $ putStrLn $ "invalidateSubscriberHeight: SubscriberCoincidenceOuter" <> showNodeId subscribed
        invalidateCoincidenceHeight subscribed
        when debugInvalidateHeight $ putStrLn $ "invalidateSubscriberHeight: SubscriberCoincidenceOuter" <> showNodeId subscribed <> " done"
      SubscriberCoincidenceInner subscribed -> do
        when debugInvalidateHeight $ putStrLn $ "invalidateSubscriberHeight: SubscriberCoincidenceInner" <> showNodeId subscribed
        invalidateCoincidenceHeight subscribed
        when debugInvalidateHeight $ putStrLn $ "invalidateSubscriberHeight: SubscriberCoincidenceInner" <> showNodeId subscribed <> " done"
invalidateCoincidenceHeight :: CoincidenceSubscribed a -> IO ()
invalidateCoincidenceHeight subscribed = do
  oldHeight <- readIORef $ coincidenceSubscribedHeight subscribed
  when (oldHeight /= invalidHeight) $ do
    writeIORef (coincidenceSubscribedHeight subscribed) $ invalidHeight
    mapM_ invalidateSubscriberHeight =<< readIORef (coincidenceSubscribedSubscribers subscribed)
recalculateSubscriberHeight :: WeakSubscriber a -> IO ()
recalculateSubscriberHeight ws = do
  ms <- deRefWeakSubscriber ws
  case ms of
    Nothing -> return () 
    Just s -> case s of
      SubscriberPush _ subscribed -> do
        when debugInvalidateHeight $ putStrLn $ "recalculateSubscriberHeight: SubscriberPush" <> showNodeId subscribed
        mapM_ recalculateSubscriberHeight =<< readIORef (pushSubscribedSubscribers subscribed)
        when debugInvalidateHeight $ putStrLn $ "recalculateSubscriberHeight: SubscriberPush" <> showNodeId subscribed <> " done"
      SubscriberMerge _ subscribed -> do
        when debugInvalidateHeight $ putStrLn $ "recalculateSubscriberHeight: SubscriberMerge" <> showNodeId subscribed
        oldHeight <- readIORef $ mergeSubscribedHeight subscribed
        when (oldHeight == invalidHeight) $ do
          height <- calculateMergeHeight subscribed
          when debugInvalidateHeight $ putStrLn $ "recalculateSubscriberHeight: height: " <> show height
          when (height /= invalidHeight) $ do 
            writeIORef (mergeSubscribedHeight subscribed) height
            mapM_ recalculateSubscriberHeight =<< readIORef (mergeSubscribedSubscribers subscribed)
        when debugInvalidateHeight $ putStrLn $ "recalculateSubscriberHeight: SubscriberMerge" <> showNodeId subscribed <> " done"
      SubscriberFan subscribed -> do
        when debugInvalidateHeight $ putStrLn $ "recalculateSubscriberHeight: SubscriberFan" <> showNodeId subscribed
        subscribers <- readIORef $ fanSubscribedSubscribers subscribed
        forM_ (DMap.toList subscribers) $ ((\(FanSubscriberKey _ :=> Identity v) -> mapM_ recalculateSubscriberHeight v) :: DSum (FanSubscriberKey k) Identity -> IO ())
        when debugInvalidateHeight $ putStrLn $ "recalculateSubscriberHeight: SubscriberFan" <> showNodeId subscribed <> " done"
      SubscriberHold _ -> return ()
      SubscriberSwitch subscribed -> do
        when debugInvalidateHeight $ putStrLn $ "recalculateSubscriberHeight: SubscriberSwitch" <> showNodeId subscribed
        oldHeight <- readIORef $ switchSubscribedHeight subscribed
        when (oldHeight == invalidHeight) $ do
          height <- calculateSwitchHeight subscribed
          when (height /= invalidHeight) $ do
            writeIORef (switchSubscribedHeight subscribed) height
            mapM_ recalculateSubscriberHeight =<< readIORef (switchSubscribedSubscribers subscribed)
        when debugInvalidateHeight $ putStrLn $ "recalculateSubscriberHeight: SubscriberSwitch" <> showNodeId subscribed <> " done"
      SubscriberCoincidenceOuter subscribed -> do
        when debugInvalidateHeight $ putStrLn $ "recalculateSubscriberHeight: SubscriberCoincidenceOuter" <> showNodeId subscribed
        void $ recalculateCoincidenceHeight subscribed
        when debugInvalidateHeight $ putStrLn $ "recalculateSubscriberHeight: SubscriberCoincidenceOuter" <> showNodeId subscribed <> " done"
      SubscriberCoincidenceInner subscribed -> do
        when debugInvalidateHeight $ putStrLn $ "recalculateSubscriberHeight: SubscriberCoincidenceInner" <> showNodeId subscribed
        void $ recalculateCoincidenceHeight subscribed
        when debugInvalidateHeight $ putStrLn $ "recalculateSubscriberHeight: SubscriberCoincidenceInner" <> showNodeId subscribed <> " done"
recalculateCoincidenceHeight :: CoincidenceSubscribed a -> IO ()
recalculateCoincidenceHeight subscribed = do
  oldHeight <- readIORef $ coincidenceSubscribedHeight subscribed
  when (oldHeight == invalidHeight) $ do
    height <- calculateCoincidenceHeight subscribed
    when (height /= invalidHeight) $ do
      writeIORef (coincidenceSubscribedHeight subscribed) height
      mapM_ recalculateSubscriberHeight =<< readIORef (coincidenceSubscribedSubscribers subscribed) 
calculateMergeHeight :: MergeSubscribed k -> IO Int
calculateMergeHeight subscribed = if DMap.null (mergeSubscribedParents subscribed) then return 0 else do
  heights <- forM (DMap.toList $ mergeSubscribedParents subscribed) $ \(_ :=> es) -> do
    readIORef $ eventSubscribedHeightRef es
  return $ if any (== invalidHeight) heights then invalidHeight else succ $ Prelude.maximum heights 
calculateSwitchHeight :: SwitchSubscribed a -> IO Int
calculateSwitchHeight subscribed = readIORef . eventSubscribedHeightRef =<< readIORef (switchSubscribedCurrentParent subscribed)
calculateCoincidenceHeight :: CoincidenceSubscribed a -> IO Int
calculateCoincidenceHeight subscribed = do
  outerHeight <- readIORef $ eventSubscribedHeightRef $ coincidenceSubscribedOuterParent subscribed
  innerHeight <- maybe (return 0) (readIORef . eventSubscribedHeightRef) =<< readIORef (coincidenceSubscribedInnerParent subscribed)
  return $ if outerHeight == invalidHeight || innerHeight == invalidHeight then invalidHeight else max outerHeight innerHeight
data SomeSwitchSubscribed = forall a. SomeSwitchSubscribed (SwitchSubscribed a)
debugInvalidate :: Bool
debugInvalidate = False
invalidate :: IORef [SomeSwitchSubscribed] -> WeakList Invalidator -> IO (WeakList Invalidator)
invalidate toReconnectRef wis = do
  forM_ wis $ \wi -> do
    mi <- deRefWeak wi
    case mi of
      Nothing -> do
        when debugInvalidate $ liftIO $ putStrLn "invalidate Dead"
        return () 
      Just i -> do
        finalize wi 
        case i of
          InvalidatorPull p -> do
            when debugInvalidate $ liftIO $ putStrLn "invalidate Pull"
            mVal <- readIORef $ pullValue p
            forM_ mVal $ \val -> do
              writeIORef (pullValue p) Nothing
              writeIORef (pullSubscribedInvalidators val) =<< invalidate toReconnectRef =<< readIORef (pullSubscribedInvalidators val)
          InvalidatorSwitch subscribed -> do
            when debugInvalidate $ liftIO $ putStrLn "invalidate Switch"
            modifyIORef' toReconnectRef (SomeSwitchSubscribed subscribed :)
  return [] 
data Spider
instance R.Reflex Spider where
  newtype Behavior Spider a = SpiderBehavior { unSpiderBehavior :: Behavior a }
  newtype Event Spider a = SpiderEvent { unSpiderEvent :: Event a }
  type PullM Spider = BehaviorM
  type PushM Spider = EventM
  
  
  never = SpiderEvent EventNever
  constant = SpiderBehavior . BehaviorConst
  push f = SpiderEvent. push f . unSpiderEvent
  pull = SpiderBehavior . pull
  merge = SpiderEvent . merge . (unsafeCoerce :: DMap k (R.Event Spider) -> DMap k Event)
  fan e = R.EventSelector $ SpiderEvent . select (fan (unSpiderEvent e))
  switch = SpiderEvent . switch . (unsafeCoerce :: Behavior (R.Event Spider a) -> Behavior (Event a)) . unSpiderBehavior
  coincidence = SpiderEvent . coincidence . (unsafeCoerce :: Event (R.Event Spider a) -> Event (Event a)) . unSpiderEvent
instance R.MonadSample Spider SpiderHost where
  
  sample = SpiderHost . readBehavior . unSpiderBehavior
instance R.MonadHold Spider SpiderHost where
  hold v0 = SpiderHost . liftM SpiderBehavior . runFrame . hold v0 . unSpiderEvent
instance R.MonadSample Spider BehaviorM where
  
  sample = readBehaviorTracked . unSpiderBehavior
instance R.MonadSample Spider EventM where
  
  sample = liftIO . readBehavior . unSpiderBehavior
instance R.MonadHold Spider EventM where
  
  hold v0 e = SpiderBehavior <$> hold v0 (unSpiderEvent e)
data RootTrigger a = forall k. GCompare k => RootTrigger (IORef [WeakSubscriber a], IORef (DMap k Identity), k a)
newtype SpiderEventHandle a = SpiderEventHandle { unEventHandle :: Event a }
instance R.MonadSubscribeEvent Spider SpiderHostFrame where
  subscribeEvent e = SpiderHostFrame $ do
    _ <- getEventSubscribed $ unSpiderEvent e 
    return $ SpiderEventHandle (unSpiderEvent e)
instance R.ReflexHost Spider where
  type EventTrigger Spider = RootTrigger
  type EventHandle Spider = SpiderEventHandle
  type HostFrame Spider = SpiderHostFrame
instance R.MonadReadEvent Spider ReadPhase where
  
  readEvent = ReadPhase . liftM (fmap return) . readEvent . unEventHandle
instance MonadRef EventM where
  type Ref EventM = Ref IO
  
  
  
  newRef = liftIO . newRef
  readRef = liftIO . readRef
  writeRef r a = liftIO $ writeRef r a
instance MonadAtomicRef EventM where
  
  atomicModifyRef r f = liftIO $ atomicModifyRef r f
newtype SpiderHost a = SpiderHost { runSpiderHost :: IO a } deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadException, MonadAsyncException)
newtype SpiderHostFrame a = SpiderHostFrame { runSpiderHostFrame :: EventM a } deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadException, MonadAsyncException)
instance R.MonadSample Spider SpiderHostFrame where
  sample = SpiderHostFrame . R.sample 
  
instance R.MonadHold Spider SpiderHostFrame where
  
  hold v0 e = SpiderHostFrame $ R.hold v0 e
newEventWithTriggerIO :: forall a. (RootTrigger a -> IO (IO ())) -> IO (Event a)
newEventWithTriggerIO f = do
  es <- newFanEventWithTriggerIO $ \Refl -> f
  return $ select es Refl
newFanEventWithTriggerIO :: GCompare k => (forall a. k a -> RootTrigger a -> IO (IO ())) -> IO (EventSelector k)
newFanEventWithTriggerIO f = do
  occRef <- newIORef DMap.empty
  subscribedRef <- newIORef DMap.empty
  let !r = Root
        { rootOccurrence = occRef
        , rootSubscribed = subscribedRef
        , rootInit = f
        }
  return $ EventSelector $ \k -> EventRoot k r
instance R.MonadReflexCreateTrigger Spider SpiderHost where
  newEventWithTrigger = SpiderHost . liftM SpiderEvent . newEventWithTriggerIO
  newFanEventWithTrigger f = SpiderHost $ do
    es <- newFanEventWithTriggerIO f
    return $ R.EventSelector $ SpiderEvent . select es
instance R.MonadReflexCreateTrigger Spider SpiderHostFrame where
  newEventWithTrigger = SpiderHostFrame . EventM . liftIO . liftM SpiderEvent . newEventWithTriggerIO
  newFanEventWithTrigger f = SpiderHostFrame $ EventM $ liftIO $ do
    es <- newFanEventWithTriggerIO f
    return $ R.EventSelector $ SpiderEvent . select es
instance R.MonadSubscribeEvent Spider SpiderHost where
  subscribeEvent e = SpiderHost $ do
    _ <- runFrame $ getEventSubscribed $ unSpiderEvent e 
    return $ SpiderEventHandle (unSpiderEvent e)
newtype ReadPhase a = ReadPhase { runReadPhase :: ResultM a } deriving (Functor, Applicative, Monad, MonadFix, R.MonadSample Spider, R.MonadHold Spider)
instance R.MonadReflexHost Spider SpiderHost where
  type ReadPhase SpiderHost = ReadPhase
  fireEventsAndRead es (ReadPhase a) = SpiderHost $ run es a
  runHostFrame = SpiderHost . runFrame . runSpiderHostFrame
instance MonadRef SpiderHost where
  type Ref SpiderHost = Ref IO
  newRef = SpiderHost . newRef
  readRef = SpiderHost . readRef
  writeRef r = SpiderHost . writeRef r
instance MonadAtomicRef SpiderHost where
  atomicModifyRef r = SpiderHost . atomicModifyRef r
instance MonadRef SpiderHostFrame where
  type Ref SpiderHostFrame = Ref IO
  newRef = SpiderHostFrame . newRef
  readRef = SpiderHostFrame . readRef
  writeRef r = SpiderHostFrame . writeRef r
instance MonadAtomicRef SpiderHostFrame where
  atomicModifyRef r = SpiderHostFrame . atomicModifyRef r