content: -- This file is part of the Wire Server implementation.
  --
  -- Copyright (C) 2022 Wire Swiss GmbH <opensource@wire.com>
  --
  -- This program is free software: you can redistribute it and/or modify it under
  -- the terms of the GNU Affero General Public License as published by the Free
  -- Software Foundation, either version 3 of the License, or (at your option) any
  -- later version.
  --
  -- This program is distributed in the hope that it will be useful, but WITHOUT
  -- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  -- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  -- details.
  --
  -- You should have received a copy of the GNU Affero General Public License along
  -- with this program. If not, see <https://www.gnu.org/licenses/>.
  
  module DelayQueue where
  
  import Gundeck.Util.DelayQueue
  import Gundeck.Util.DelayQueue qualified as DelayQueue
  import Imports
  import Test.Tasty
  import Test.Tasty.QuickCheck
  
  tests :: TestTree
  tests =
    testGroup
      "DelayQueue"
      [ testProperty "enqueue/limit" enqueueLimitProp,
        testProperty "enqueue/unique" enqueueUniqueProp,
        testProperty "enqueue/cancel" enqueueCancelProp,
        testProperty "dequeue/delay" dequeueDelayProp,
        testProperty "dequeue/order" dequeueOrderProp
      ]
  
  -- Properties
  
  enqueueLimitProp :: Positive Int -> Property
  enqueueLimitProp (Positive l) = ioProperty $ do
    q <- DelayQueue.new (Clock (pure 1)) (Delay 1) (Limit l)
    r <- forM [1 .. l + 1] $ \(i :: Int) -> DelayQueue.enqueue q i i
    l' <- DelayQueue.length q
    pure $
      r == replicate l True ++ [False]
        && l' == l
  
  enqueueUniqueProp :: Positive Int -> Property
  enqueueUniqueProp (Positive n) = ioProperty $ do
    q <- DelayQueue.new (Clock (pure 1)) (Delay 1) (Limit (n + 1))
    r <- forM [1 .. n] $ \(i :: Int) -> DelayQueue.enqueue q (1 :: Int) i
    l <- DelayQueue.length q
    pure $ and r && l == 1
  
  enqueueCancelProp :: Int -> Int -> Property
  enqueueCancelProp k v = ioProperty $ do
    q <- DelayQueue.new (Clock (pure 1)) (Delay 1) (Limit 1)
    e <- DelayQueue.enqueue q k v
    l <- DelayQueue.length q
    c <- DelayQueue.cancel q k
    l' <- DelayQueue.length q
    pure $ e && c && l == 1 && l' == 0
  
  dequeueDelayProp :: Word16 -> Property
  dequeueDelayProp d = ioProperty $ do
    c <- newIORef 0
    q <- DelayQueue.new (Clock (readIORef c)) (Delay (fromIntegral d)) (Limit 1)
    e <- DelayQueue.enqueue q (1 :: Int) (1 :: Int)
    r <- forM [1 .. d] $ \i -> do
      x <- DelayQueue.dequeue q
      tick c
      let diff = fromIntegral (d - (i - 1))
      pure $ x == Just (Left (Delay diff))
    s <- DelayQueue.dequeue q
    pure $ e && and r && s == Just (Right 1)
  
  dequeueOrderProp :: Int -> Property
  dequeueOrderProp k = ioProperty $ do
    c <- newIORef 0
    q <- DelayQueue.new (Clock (readIORef c)) (Delay 1) (Limit 2)
    e1 <- DelayQueue.enqueue q k (1 :: Int)
    tick c
    e2 <- DelayQueue.enqueue q (k - 1) (2 :: Int)
    tick c
    d1 <- DelayQueue.dequeue q
    d2 <- DelayQueue.dequeue q
    pure $ e1 && e2 && d1 == Just (Right 1) && d2 == Just (Right 2)
  
  -- Utilities
  
  tick :: IORef Word64 -> IO ()
  tick c = modifyIORef' c (+ 1)
  