{-# LANGUAGE PatternGuards  #-}
module Main where

import qualified Control.Distributed.Process.Extras.Internal.Queue.SeqQ as FIFO
import Control.Distributed.Process.Extras.Internal.Queue.SeqQ ( SeqQ )
import qualified Control.Distributed.Process.Extras.Internal.Queue.PriorityQ as PQ

import Control.Rematch hiding (on)
import Control.Rematch.Run
import Data.Function (on)
import Data.List
import Test.Framework as TF (defaultMain, testGroup, Test)
import Test.Framework.Providers.HUnit
import Test.Framework.Providers.QuickCheck2 (testProperty)
import Test.HUnit (Assertion, assertFailure)

import Prelude

expectThat :: a -> Matcher a -> Assertion
expectThat a matcher = case res of
  MatchSuccess -> return ()
  (MatchFailure msg) -> assertFailure msg
  where res = runMatch matcher a

-- NB: these tests/properties are not meant to be complete, but rather
-- they exercise the small number of behaviours that we actually use!

-- TODO: some laziness vs. strictness tests, with error/exception checking

prop_pq_ordering :: [Int] -> Bool
prop_pq_ordering xs =
    let xs' = map (\x -> (x, show x)) xs
        q   = foldl (\q' x -> PQ.enqueue (fst x) (snd x) q') PQ.empty xs'
        ys  = drain q []
        zs  = [snd x | x <- reverse $ sortBy (compare `on` fst) xs']
        -- the sorted list should match the stuff we drained back out
    in zs == ys
  where
    drain q xs'
      | True <- PQ.isEmpty q = xs'
      | otherwise =
          let Just (x, q') = PQ.dequeue q in drain q' (x:xs')

prop_fifo_enqueue :: Int -> Int -> Int -> Bool
prop_fifo_enqueue a b c =
  let q1           = foldl FIFO.enqueue FIFO.empty [a,b,c]
      Just (a', q2) = FIFO.dequeue q1
      Just (b', q3) = FIFO.dequeue q2
      Just (c', q4) = FIFO.dequeue q3
      d             = FIFO.dequeue q4
  in (d == Nothing) && (q4 `seq` [a',b',c'] == [a,b,c])  -- why seq here? to shut the compiler up.

prop_enqueue_empty :: String -> Bool
prop_enqueue_empty s =
  let q            = FIFO.enqueue FIFO.empty s
      Just (_, q') = FIFO.dequeue q
  in (FIFO.isEmpty q') == ((FIFO.isEmpty q) == False)

tests :: [TF.Test]
tests = [
     testGroup "Priority Queue Tests" [
        -- testCase "New Queue Should Be Empty"
        --   (expect (PQ.isEmpty $ PQ.empty) $ equalTo True),
        -- testCase "Singleton Queue Should Contain One Element"
        --   (expect (PQ.dequeue $ (PQ.singleton 1 "hello") :: PriorityQ Int String) $
        --      equalTo $ (Just ("hello", PQ.empty)) :: Maybe (PriorityQ Int String)),
        -- testCase "Dequeue Empty Queue Should Be Nothing"
        --   (expect (Q.isEmpty $ PQ.dequeue $
        --              (PQ.empty :: PriorityQ Int ())) $ equalTo True),
        testProperty "Enqueue/Dequeue should respect Priority order"
            prop_pq_ordering
     ],
     testGroup "FIFO Queue Tests" [
        testCase "New Queue Should Be Empty"
          (expectThat (FIFO.isEmpty $ FIFO.empty) $ equalTo True),
        testCase "Singleton Queue Should Contain One Element"
          (expectThat (FIFO.dequeue $ FIFO.singleton "hello") $
             equalTo $ Just ("hello", FIFO.empty)),
        testCase "Dequeue Empty Queue Should Be Nothing"
          (expectThat (FIFO.dequeue $ (FIFO.empty :: SeqQ ())) $
            is (Nothing :: Maybe ((), SeqQ ()))),
        testProperty "Enqueue/Dequeue should respect FIFO order"
            prop_fifo_enqueue,
        testProperty "Enqueue/Dequeue should respect isEmpty"
            prop_enqueue_empty
     ]
   ]

main :: IO ()
main = defaultMain tests

