content: module OutputSpec where
  
  import Control.Concurrent.STM
  import Control.Exception (evaluate)
  
  import Data.IORef
  import Data.Foldable
  
  import Polysemy
  import Polysemy.Async
  import Polysemy.Output
  import Polysemy.Final
  
  import Test.Hspec
  
  spec :: Spec
  spec = parallel $ do
    describe "runOutputBatched" $ do
      it "should return nothing at batch size 0" $ do
        let (ms, _) = runOutput 0 $ traverse (output @Int) [0..99]
        length ms `shouldBe` 0
  
      for_ (1 : [5..13] ++ [99..101]) $ \size ->
        context ("Works at size " ++ show size) $ do
          let (ms, _) = runOutput size $ traverse (output @Int) [0..99]
          it "returns the correct amount of batches" $
            length ms `shouldBe` div (100 + size - 1) size -- 100 `div` size but rounding up
          it "all batches except the last one are of the specified size" $
            map length (init ms) `shouldBe` replicate (length ms - 1) size
          it "returns all original elements in the correct order" $
            concat ms `shouldBe` [0..99]
  
    describe "runOutputList" $
      it "should return elements in the order they were output" $
        let (xs, ()) = runOutputList' $ traverse_ (output @Int) [0..100]
         in xs `shouldBe` [0..100]
  
    describe "runOutputMonoid" $
      it "should be strict in the output" $
        let t = runOutputMonoid (id @String) $ do
              output @String (error "strict")
              return ()
        in do
          runM t           `shouldThrow` errorCall "strict"
          evaluate (run t) `shouldThrow` errorCall "strict"
  
    describe "runOutputMonoidIORef" $ do
      it "should commit writes of asynced computations" $
        let io = do
              ref <- newIORef ""
              runFinal
                . embedToFinal @IO
                . asyncToIOFinal
                . runOutputMonoidIORef ref (show @Int)
                $ test1
              readIORef ref
        in do
          res <- io
          res `shouldBe` "12"
  
    describe "runOutputMonoidTVar" $ do
      it "should commit writes of asynced computations" $
        let io = do
              ref <- newTVarIO ""
              runFinal
                . embedToFinal @IO
                . asyncToIOFinal
                . runOutputMonoidTVar ref (show @Int)
                $ test1
              readTVarIO ref
        in do
          res <- io
          res `shouldBe` "12"
  
  runOutput :: Int -> Sem '[Output Int, Output [Int]] a -> ([[Int]], a)
  runOutput size = run . runOutputMonoid (:[]) . runOutputBatched size
  
  runOutputList' :: Sem '[Output Int] a -> ([Int], a)
  runOutputList' = run . runOutputList
  
  test1 :: Members '[Async, Output Int] r
       => Sem r ()
  test1 = do
    output @Int 1
    a <- async $ do
      output @Int 2
    _ <- await a
    return ()
  