module Quasar.DisposableSpec (spec) where

import Control.Exception
import Control.Concurrent
import Control.Monad (void)
import Quasar.Prelude
import Test.Hspec
import Quasar.Awaitable
import Quasar.Disposable

data TestException = TestException
  deriving stock (Eq, Show)

instance Exception TestException

spec :: Spec
spec = parallel $ do
  describe "Disposable" $ do
    describe "noDisposable" $ do
      it "can be disposed" $ do
        await =<< dispose noDisposable

      it "can be awaited" $ do
        await (isDisposed noDisposable)
        pure () :: IO ()

    describe "newDisposable" $ do
      it "signals it's disposed state" $ do
        disposable <- newDisposable $ pure $ pure ()
        void $ forkIO $ threadDelay 100000 >> disposeIO disposable
        await (isDisposed disposable)
        pure () :: IO ()

      it "can be disposed multiple times" $ do
        disposable <- newDisposable $ pure $ pure ()
        disposeIO disposable
        disposeIO disposable
        await (isDisposed disposable)

      it "can be disposed in parallel" $ do
        disposable <- newDisposable $ pure () <$ threadDelay 100000
        void $ forkIO $ disposeIO disposable
        disposeIO disposable
        await (isDisposed disposable)


  describe "ResourceManager" $ do
    it "can be created" $ io do
      void unsafeNewResourceManager

    it "can be created and disposed" $ do
      resourceManager <- unsafeNewResourceManager
      await =<< dispose resourceManager

    it "can be created and disposed" $ io do
      withResourceManager \_ -> pure ()

    it "can be created and disposed with a delay" $ do
      withResourceManager \_ -> threadDelay 100000

    it "can \"dispose\" a noDisposable" $ io do
      withResourceManager \resourceManager -> do
        attachDisposable resourceManager noDisposable

    it "can attach an disposable" $ do
      withResourceManager \resourceManager -> do
        avar <- newAsyncVar :: IO (AsyncVar ())
        attachDisposable resourceManager $ alreadyDisposing avar
        putAsyncVar_ avar ()
      pure () :: IO ()

    it "can dispose an awaitable that is completed asynchronously" $ do
      avar <- newAsyncVar :: IO (AsyncVar ())
      void $ forkIO $ do
        threadDelay 100000
        putAsyncVar_ avar ()

      withResourceManager \resourceManager -> do
        attachDisposable resourceManager (alreadyDisposing avar)

    it "can call a trivial dispose action" $ do
      withResourceManager \resourceManager ->
        attachDisposeAction_ resourceManager $ pure $ pure ()
      pure () :: IO ()

    it "can call a dispose action" $ do
      withResourceManager \resourceManager -> do
        avar <- newAsyncVar :: IO (AsyncVar ())
        attachDisposeAction_ resourceManager $ toAwaitable avar <$ putAsyncVar_ avar ()
      pure () :: IO ()

    it "re-throws an exception from a dispose action" $ do
      shouldThrow
        do
          withResourceManager \resourceManager ->
            attachDisposeAction resourceManager $ throwIO $ TestException
        \TestException -> True

    it "can attach an disposable that is disposed asynchronously" $ do
      withResourceManager \resourceManager -> do
        disposable <- attachDisposeAction resourceManager $ pure () <$ threadDelay 100000
        void $ forkIO $ disposeIO disposable