module TestInternal
    ( runTests
    , leftistHeapProperty
    ) where

import Data.Char
import HeapInternal as Heap
import qualified Data.List as List
import TestHeapCommon
import TestQuickCheck

runTests :: IO ()
runTests = do
    qc "Eq" (eqProperty :: HeapT Int Char -> HeapT Int Char -> HeapT Int Char -> Bool)
    qc "Ord" (ordProperty :: HeapT Int Char -> HeapT Int Char -> HeapT Int Char -> Bool)
    qc "leftist heap" (leftistHeapProperty :: HeapT Int Char -> Bool)
    qc "read/show" (readShowProperty :: [HeapT Int Char] -> Bool)
    qc "Monoid" (monoidProperty :: HeapT Int Char -> HeapT Int Char -> HeapT Int Char -> Bool)
    qc "union" (unionProperty :: HeapT Int Char -> HeapT Int Char -> Bool)
    qc "Functor" (functorProperty (subtract 1000) (*42) :: HeapT Char Int -> Bool)
    qc "fmap" (fmapProperty (subtract 1000) :: HeapT Char Int -> Bool)
    qc "Foldable" (foldableProperty :: HeapT Char Int -> Bool)
    qc "size" sizeProperty
    qc "view" viewProperty
    qc "singleton" (singletonProperty :: Char -> Int -> Bool)
    qc "partition" (partitionProperty testProp :: HeapT Char Int -> Bool)
    qc "splitAt" splitAtProperty
    qc "span" spanProperty
    qc "fromList/toList" (listProperty :: [Char] -> Bool)
    qc "fromDescList/toAscList" (sortedListProperty :: [Char] -> Bool)
    where
    testProp :: Char -> Int -> Bool
    testProp c i = even i && isLetter c

instance (Arbitrary prio, Arbitrary val, Ord prio) => Arbitrary (HeapT prio val) where
    arbitrary = fmap (fromList . take 100) arbitrary
    shrink    = fmap fromList . shrink . toList

leftistHeapProperty :: (Ord prio) => HeapT prio val -> Bool
leftistHeapProperty Empty = True
leftistHeapProperty heap  =
    (maybe True (\(p, _, _) -> p >= _priority heap) (view (_left heap)))
        && (maybe True (\(p, _, _) -> p >= _priority heap) (view (_right heap)))
        && _rank heap == 1 + rank (_right heap)    -- rank == length of right spine
        && rank (_left heap) >= rank (_right heap) -- leftist property
        && _size heap == 1 + size (_left heap) + size (_right heap)
        && leftistHeapProperty (_left heap)
        && leftistHeapProperty (_right heap)

unionProperty :: (Ord prio, Ord val) => HeapT prio val -> HeapT prio val -> Bool
unionProperty a b = let ab = a `union` b
    in leftistHeapProperty ab && size ab == size a + size b
        && ab == ab `union` empty
        && ab == empty `union` ab
        && a == unions (fmap (uncurry singleton) (toList a))

fmapProperty :: (Ord prio) => (val -> val) -> HeapT prio val -> Bool
fmapProperty f = leftistHeapProperty . fmap f

sizeProperty :: Int -> Bool
sizeProperty n = let
    n' = abs n `mod` 100
    h  = fromList (zip [1..n'] (repeat ())) :: HeapT Int ()
    in
    size h == n' && if n' == 0 then isEmpty h else not (isEmpty h)

viewProperty :: [Int] -> Bool
viewProperty []   = True
viewProperty list = let
    heap = fromList (zip list (repeat ()))
    m    = minimum list
    in case view heap of
        Nothing          -> False -- list is not empty
        Just (p, (), hs) -> p == m
            && heap == union (singleton p ()) hs
            && viewProperty (tail list)

singletonProperty :: (Ord prio, Ord val) => prio -> val -> Bool
singletonProperty p v = let
    heap = singleton p v
    in
    leftistHeapProperty heap && size heap == 1 && view heap == Just (p, v, empty)

partitionProperty :: (Ord prio, Ord val) => (prio -> val -> Bool) -> HeapT prio val -> Bool
partitionProperty p heap = let
    (yes,  no)  = partition (uncurry p) heap
    (yes', no') = List.partition (uncurry p) (toList heap)
    in
    (heap, empty) == partition (const True) heap
        && (empty, heap) == partition (const False) heap
        && yes == fromList yes'
        && no == fromList no'
        && yes `union` no == heap -- nothing gets lost

splitAtProperty :: Int -> Int -> Bool
splitAtProperty i n = let
    i'     = i `mod` 100
    n'     = n `mod` 100
    ab     = [1..n']
    (a, b) = List.splitAt i' ab
    heap   = fromList $ zip ab (repeat ())
    in
    Heap.splitAt i' heap == (zip a (repeat ()), fromList (zip b (repeat ())))

spanProperty :: Int -> Int -> Bool
spanProperty i n = let
    i'      = i `mod` 100
    n'      = n `mod` 100
    ab      = [1..n']
    (a, b)  = List.span (<= i') ab
    (a', h) = Heap.span ((<=i') . fst) $ fromList (zip ab (repeat ()))
    in
    a == (fmap fst a') && h == fromList (zip b (repeat ()))

listProperty :: (Ord prio) => [prio] -> Bool
listProperty xs = let
    list = List.sort xs
    heap = fromList (zip xs [(1 :: Int) ..])
    in
    list == fmap fst (List.sort (toList heap))

sortedListProperty :: (Ord prio) => [prio] -> Bool
sortedListProperty xs = let
    list = List.sort xs
    heap = fromDescList (zip (reverse list) [(1 :: Int) ..])
    in
    list == fmap fst (toAscList heap)
