-- | Shared players collection
playersCollection :: forall t m . MonadApp t m
  => Event t (Map PlayerId PlayerId) -- ^ Fires when a hit from bullets are occured. (killed => killer)
  -> m (Dynamic t PlayerMapping, PlayerShoots t)
playersCollection hitsE = do
  -- we need a player counter to generate ids
  playerCounterRef <- newExternalRef (0 :: Int)
  playerCounter <- externalRefDynamic playerCounterRef
  -- retreive necessary events
  connPeerE <- peerConnected
  discPeerE <- peerDisconnected
  rec
    -- Lets calculate update actions for players collection
    -- updE :: Event t (Map PlayerId (Maybe Peer))
    updE <- performEvent $ ffor (align connPeerE discPeerE) $ \e -> do
      i <- sample . current $ playerCounter
      case e of
        This connPeer -> do
          modifyExternalRef playerCounterRef $ \v -> (v+1, ())
          return $ M.singleton (PlayerId i) (Just connPeer)
        That discPeer -> do
          players <- sample . current $ fmap snd playersMappingDyn
          return $ case M.lookup discPeer players of
            Nothing -> mempty
            Just i' -> M.singleton i' Nothing
        These connPeer discPeer -> if connPeer == discPeer then return mempty
          else do
            modifyExternalRef playerCounterRef $ \v -> (v+1, ())
            players <- sample . current $ fmap snd playersMappingDyn
            let
              delMap :: Map PlayerId (Maybe AppPeer)
              delMap = case M.lookup discPeer players of
                Nothing -> mempty
                Just i' -> M.singleton i' Nothing
            return $ M.insert (PlayerId i) (Just connPeer) delMap
    -- collection primitive, note recursive dependency
    colorRoller <- makeColorRoller
    let playerWrapper i = player colorRoller (mkHitE i) (mkKills i) i
    peers <- networkPeers
    let filterPeer _ _ = () -- Don't send peer to clients
    collReses <- hostCollection playerCollectionId peers mempty updE filterPeer playerWrapper
    let playersMapDyn = joinDynThroughMap $ fmap fst <$> collReses
        shootsEvents  = switch . current $ mergeMap . fmap snd <$> collReses
    -- post processing to get peer-id map
    let playersMappingDyn :: Dynamic t PlayerMapping
        playersMappingDyn = do
          playersMap <- playersMapDyn
          let elems = M.toList $ playerPeer . playerCustom <$> playersMap
              peersMap = M.fromList . fmap (\(i, p) -> (p, i)) $ elems
          return (playersMap, peersMap)

  return (playersMappingDyn, shootsEvents)
  where
    -- Construct event that particular player is hit by a bullet
    mkHitE i = fforMaybe hitsE $ \m -> if M.member i m then Just () else Nothing
    -- Construct event that particular player killed another player
    mkKills i = fforMaybe hitsE $ \m -> let n = length . filter (== i) . M.elems $ m
      in if n > 0 then Just n else Nothing

-- | Extension of shared player with server private data
playersCollection :: forall t m . MonadApp t m
  => Event t (Map PlayerId PlayerId) -- ^ Fires when a hit from bullets are occured. (killed => killer)
  -> m (Dynamic t PlayerMapping, PlayerShoots t)
playersCollection hitsE = do
  -- we need a player counter to generate ids
  playerCounterRef <- newExternalRef (0 :: Int)
  playerCounter <- externalRefDynamic playerCounterRef
  -- retreive necessary events
  connPeerE <- peerConnected
  discPeerE <- peerDisconnected
  rec
    -- Lets calculate update actions for players collection
    -- updE :: Event t (Map PlayerId (Maybe Peer))
    updE <- performEvent $ ffor (align connPeerE discPeerE) $ \e -> do
      i <- sample . current $ playerCounter
      case e of
        This connPeer -> do
          modifyExternalRef playerCounterRef $ \v -> (v+1, ())
          return $ M.singleton (PlayerId i) (Just connPeer)
        That discPeer -> do
          players <- sample . current $ fmap snd playersMappingDyn
          return $ case M.lookup discPeer players of
            Nothing -> mempty
            Just i' -> M.singleton i' Nothing
        These connPeer discPeer -> if connPeer == discPeer then return mempty
          else do
            modifyExternalRef playerCounterRef $ \v -> (v+1, ())
            players <- sample . current $ fmap snd playersMappingDyn
            let
              delMap :: Map PlayerId (Maybe AppPeer)
              delMap = case M.lookup discPeer players of
                Nothing -> mempty
                Just i' -> M.singleton i' Nothing
            return $ M.insert (PlayerId i) (Just connPeer) delMap
    -- collection primitive, note recursive dependency
    colorRoller <- makeColorRoller
    let playerWrapper i = player colorRoller (mkHitE i) (mkKills i) i
    peers <- networkPeers
    let filterPeer _ _ = () -- Don't send peer to clients
    collReses <- hostCollection playerCollectionId peers mempty updE filterPeer playerWrapper
    let playersMapDyn = joinDynThroughMap $ fmap fst <$> collReses
        shootsEvents  = switch . current $ mergeMap . fmap snd <$> collReses
    -- post processing to get peer-id map
    let playersMappingDyn :: Dynamic t PlayerMapping
        playersMappingDyn = do
          playersMap <- playersMapDyn
          let elems = M.toList $ playerPeer . playerCustom <$> playersMap
              peersMap = M.fromList . fmap (\(i, p) -> (p, i)) $ elems
          return (playersMap, peersMap)

  return (playersMappingDyn, shootsEvents)
  where
    -- Construct event that particular player is hit by a bullet
    mkHitE i = fforMaybe hitsE $ \m -> if M.member i m then Just () else Nothing
    -- Construct event that particular player killed another player
    mkKills i = fforMaybe hitsE $ \m -> let n = length . filter (== i) . M.elems $ m
      in if n > 0 then Just n else Nothing

-- | Extension of shared player with server private data
playersCollection hitsE = do
  -- we need a player counter to generate ids
  playerCounterRef <- newExternalRef (0 :: Int)
  playerCounter <- externalRefDynamic playerCounterRef
  -- retreive necessary events
  connPeerE <- peerConnected
  discPeerE <- peerDisconnected
  rec
    -- Lets calculate update actions for players collection
    -- updE :: Event t (Map PlayerId (Maybe Peer))
    updE <- performEvent $ ffor (align connPeerE discPeerE) $ \e -> do
      i <- sample . current $ playerCounter
      case e of
        This connPeer -> do
          modifyExternalRef playerCounterRef $ \v -> (v+1, ())
          return $ M.singleton (PlayerId i) (Just connPeer)
        That discPeer -> do
          players <- sample . current $ fmap snd playersMappingDyn
          return $ case M.lookup discPeer players of
            Nothing -> mempty
            Just i' -> M.singleton i' Nothing
        These connPeer discPeer -> if connPeer == discPeer then return mempty
          else do
            modifyExternalRef playerCounterRef $ \v -> (v+1, ())
            players <- sample . current $ fmap snd playersMappingDyn
            let
              delMap :: Map PlayerId (Maybe AppPeer)
              delMap = case M.lookup discPeer players of
                Nothing -> mempty
                Just i' -> M.singleton i' Nothing
            return $ M.insert (PlayerId i) (Just connPeer) delMap
    -- collection primitive, note recursive dependency
    colorRoller <- makeColorRoller
    let playerWrapper i = player colorRoller (mkHitE i) (mkKills i) i
    peers <- networkPeers
    let filterPeer _ _ = () -- Don't send peer to clients
    collReses <- hostCollection playerCollectionId peers mempty updE filterPeer playerWrapper
    let playersMapDyn = joinDynThroughMap $ fmap fst <$> collReses
        shootsEvents  = switch . current $ mergeMap . fmap snd <$> collReses
    -- post processing to get peer-id map
    let playersMappingDyn :: Dynamic t PlayerMapping
        playersMappingDyn = do
          playersMap <- playersMapDyn
          let elems = M.toList $ playerPeer . playerCustom <$> playersMap
              peersMap = M.fromList . fmap (\(i, p) -> (p, i)) $ elems
          return (playersMap, peersMap)

  return (playersMappingDyn, shootsEvents)
  where
    -- Construct event that particular player is hit by a bullet
    mkHitE i = fforMaybe hitsE $ \m -> if M.member i m then Just () else Nothing
    -- Construct event that particular player killed another player
    mkKills i = fforMaybe hitsE $ \m -> let n = length . filter (== i) . M.elems $ m
      in if n > 0 then Just n else Nothing

-- | Extension of shared player with server private data