{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE NumericUnderscores #-}
module Main ( main ) where

import Control.Monad (replicateM, replicateM_, forM_)
import qualified Data.IntMap.Strict as IM
import Reactive.Banana.Combinators ( Event, Behavior, MonadMoment, filterE, accumE, switchB, accumB )
import Reactive.Banana.Frameworks (MomentIO, newAddHandler, fromAddHandler, compile, activate, Handler, reactimate)
import Reactive.Banana ( Event, Behavior, MonadMoment )
import System.Random (randomRIO)
import Test.Tasty (withResource)
import Test.Tasty.Bench (env, defaultMain, bgroup, bench, whnfIO)

main :: IO ()
main = defaultMain $ [ mkBenchmarkGroup netsize | netsize <- [ 1, 2, 4, 8, 16, 32, 64, 128 ] ] ++
                     [ boringBenchmark ]
  where
    mkBenchmarkGroup netsize =
      withResource (setupBenchmark netsize) mempty $ \getEnv ->
        bgroup ("netsize = " <> show netsize)
          [ mkBenchmark getEnv steps | steps <- [ 1, 2, 4, 8, 16, 32, 64, 128] ]
      where
        mkBenchmark getEnv duration = bench ("duration = " <> show duration) $ whnfIO $ do
          (triggers, clock) <- getEnv
          let trigMap = IM.fromList $ zip [0..netsize-1] triggers
          forM_ [1..duration] $ \step -> do
            randomRs <- replicateM 10 $ randomRIO (0,netsize-1)
            clock step
            forM_ randomRs $ \ev ->
                maybe (error "benchmark: trigger not found") ($ ()) $
                    IM.lookup ev trigMap

    boringBenchmark = withResource setup mempty $ \getEnv ->
      bench "Boring" $ whnfIO $ do
        tick <- getEnv
        {-# SCC ticks #-} replicateM_ 1_000_000 $ {-# SCC tick #-} tick ()
      where
        setup = do
          (tick, onTick) <- newAddHandler
          network <- compile $ do
            e <- fromAddHandler tick
            reactimate $ return <$> e
          activate network
          return onTick

setupBenchmark :: Int -> IO ([Handler ()], Handler Int)
setupBenchmark netsize = do
  (handlers, triggers) <- unzip <$> replicateM netsize newAddHandler
  (clock   , trigger ) <- newAddHandler

  let networkD :: MomentIO ()
      networkD = do
          es :: [Event ()] <-
            mapM fromAddHandler handlers

          e :: Event Int <-
            fromAddHandler clock

          countBs :: [Behavior Int] <-
            traverse count es

          let
            step10E :: Event Int
            step10E = filterE (\cnt -> cnt `rem` 10 == 0) e

          selectedB_E :: Event (Behavior Int) <- do
            fmap head <$> accumE countBs (keepTail <$ step10E)

          selectedB :: Behavior Int <-
            switchB (head countBs) selectedB_E

          return ()

      count :: MonadMoment m => Event () -> m (Behavior Int)
      count e = accumB 0 ((+1) <$ e)

  activate =<< compile networkD
  return (triggers, trigger)
  where
    keepTail :: [a] -> [a]
    keepTail (_:y:zs) = y:zs
    keepTail [x]      = [x]
    keepTail []       = []
