module Quasar.Observable.ObservableHashMapSpec (spec) where

import Quasar.Disposable
import Quasar.Observable
import Quasar.Observable.Delta
import Quasar.Observable.ObservableHashMap qualified as OM

import Control.Monad (void)
import Data.HashMap.Strict qualified as HM
import Data.IORef
import Prelude
import Test.Hspec

spec :: Spec
spec = parallel $ do
  describe "retrieveIO" $ do
    it "returns the contents of the map" $ do
      om <- OM.new :: IO (OM.ObservableHashMap String String)
      retrieveIO om `shouldReturn` HM.empty
      -- Evaluate unit for coverage
      () <- OM.insert "key" "value" om
      retrieveIO om `shouldReturn` HM.singleton "key" "value"
      OM.insert "key2" "value2" om
      retrieveIO om `shouldReturn` HM.fromList [("key", "value"), ("key2", "value2")]

  describe "subscribe" $ do
    it "calls the callback with the contents of the map" $ do
      lastCallbackValue <- newIORef undefined

      om <- OM.new :: IO (OM.ObservableHashMap String String)
      subscriptionHandle <- observe om $ writeIORef lastCallbackValue
      let lastCallbackShouldBe expected = do
            (ObservableUpdate update) <- readIORef lastCallbackValue
            update `shouldBe` expected

      lastCallbackShouldBe HM.empty
      OM.insert "key" "value" om
      lastCallbackShouldBe (HM.singleton "key" "value")
      OM.insert "key2" "value2" om
      lastCallbackShouldBe (HM.fromList [("key", "value"), ("key2", "value2")])

      awaitDispose subscriptionHandle
      lastCallbackShouldBe (HM.fromList [("key", "value"), ("key2", "value2")])

      OM.insert "key3" "value3" om
      lastCallbackShouldBe (HM.fromList [("key", "value"), ("key2", "value2")])

  describe "subscribeDelta" $ do
    it "calls the callback with changes to the map" $ do
      lastDelta <- newIORef undefined

      om <- OM.new :: IO (OM.ObservableHashMap String String)
      subscriptionHandle <- subscribeDelta om $ writeIORef lastDelta
      let lastDeltaShouldBe = (readIORef lastDelta `shouldReturn`)

      lastDeltaShouldBe $ Reset HM.empty
      OM.insert "key" "value" om
      lastDeltaShouldBe $ Insert "key" "value"
      OM.insert "key" "changed" om
      lastDeltaShouldBe $ Insert "key" "changed"
      OM.insert "key2" "value2" om
      lastDeltaShouldBe $ Insert "key2" "value2"

      awaitDispose subscriptionHandle
      lastDeltaShouldBe $ Insert "key2" "value2"

      OM.insert "key3" "value3" om
      lastDeltaShouldBe $ Insert "key2" "value2"

      void $ subscribeDelta om $ writeIORef lastDelta
      lastDeltaShouldBe $ Reset $ HM.fromList [("key", "changed"), ("key2", "value2"), ("key3", "value3")]

      OM.delete "key2" om
      lastDeltaShouldBe $ Delete "key2"

      OM.lookupDelete "key" om `shouldReturn` Just "changed"
      lastDeltaShouldBe $ Delete "key"

      retrieveIO om `shouldReturn` HM.singleton "key3" "value3"

  describe "observeKey" $ do
    it "calls key callbacks with the correct value" $ do
      value1 <- newIORef undefined
      value2 <- newIORef undefined

      om <- OM.new :: IO (OM.ObservableHashMap String String)

      void $ observe (OM.observeKey "key1" om) (writeIORef value1)
      let v1ShouldBe expected = do
            (ObservableUpdate update) <- readIORef value1
            update `shouldBe` expected

      v1ShouldBe $ Nothing

      OM.insert "key1" "value1" om
      v1ShouldBe $ Just "value1"

      OM.insert "key2" "value2" om
      v1ShouldBe $ Just "value1"

      handle2 <- observe (OM.observeKey "key2" om) (writeIORef value2)
      let v2ShouldBe expected = do
            (ObservableUpdate update) <- readIORef value2
            update `shouldBe` expected

      v1ShouldBe $ Just "value1"
      v2ShouldBe $ Just "value2"

      OM.insert "key2" "changed" om
      v1ShouldBe $ Just "value1"
      v2ShouldBe $ Just "changed"

      OM.delete "key1" om
      v1ShouldBe $ Nothing
      v2ShouldBe $ Just "changed"

      -- Delete again (should have no effect)
      OM.delete "key1" om
      v1ShouldBe $ Nothing
      v2ShouldBe $ Just "changed"

      retrieveIO om `shouldReturn` HM.singleton "key2" "changed"
      awaitDispose handle2

      OM.lookupDelete "key2" om `shouldReturn` Just "changed"
      v2ShouldBe $ Just "changed"

      OM.lookupDelete "key2" om `shouldReturn` Nothing

      OM.lookupDelete "key1" om `shouldReturn` Nothing
      v1ShouldBe $ Nothing

      retrieveIO om `shouldReturn` HM.empty