content: module PropertyTests
    ( propertyTests
    ) where
  
  import Morloc.Namespace
  
  import qualified Data.Set as Set
  import Test.Tasty
  import Test.Tasty.QuickCheck as TQC
  
  propertyTests :: TestTree
  propertyTests =
    testGroup
      "internal list function properties"
      [ TQC.testProperty "unique makes unique lists" prop_unique_unique
      , TQC.testProperty "unique preserves original order" prop_unique_preserves_order
      , TQC.testProperty "duplicates makes unique lists" prop_duplicates_unique
      , TQC.testProperty "duplicates preserves original order" prop_duplicates_preserves_order
      ]
  
  -- for the uniq family of functions (unique, duplicates, isSorted), I will test
  -- on the numbers 1 to 5. If the desired property holds over this set, they
  -- will hold over any ordered set. 
  one2five :: [Int] -> [Int]
  one2five = map (\x -> mod (abs x) 5)
  
  prop_unique_unique :: [Int] -> Bool
  prop_unique_unique [] = True
  prop_unique_unique xs =
    let xs' = one2five xs
    in length (unique xs') == Set.size (Set.fromList xs') 
  
  -- This test asserts that the first element in the original and unique list is
  -- the same. This guarantee alone does not entirely guantee that the original
  -- order is preserved, but it is close.
  prop_unique_preserves_order :: [Int] -> Bool
  prop_unique_preserves_order xs = headMay xs == headMay (unique xs)
  
  -- Each element in the duplicates return list is unique
  prop_duplicates_unique :: [Int] -> Bool
  prop_duplicates_unique [] = True
  prop_duplicates_unique xs =
    let xs' = duplicates (one2five xs)
    in length xs' == Set.size (Set.fromList xs')
  
  prop_duplicates_preserves_order :: [Int] -> Bool
  prop_duplicates_preserves_order xs = f Set.empty xs (duplicates xs) where
    f _ _ [] = True
    f _ [] _ = False
    f skipped (y:rs) (y':rs')
      -- if the original and duplicated elements match:
      | y == y' =
        -- if the current element was previously skipped
        if Set.member y' skipped
        -- then the duplicates function failed to respect the initial order
        then False
        -- else continue checking on the next elements
        else f skipped rs rs'
      -- otherwise store record the skipped value and continue
      | otherwise = f (Set.insert y skipped) rs (y':rs')
  