{-# LANGUAGE ScopedTypeVariables #-}

{-# OPTIONS_GHC -Wall #-}

module Test.QuickCheck.Classes.Enum
  ( enumLaws
  , boundedEnumLaws
  ) where

import Data.Proxy (Proxy)
import Test.QuickCheck hiding ((.&.))
import Test.QuickCheck.Property (Property)

import Test.QuickCheck.Classes.Common (Laws(..), myForAllShrink)

-- | Tests the following properties:
--
-- [/Succ Pred Identity/]
--   @'succ' ('pred' x) ≡ x@
-- [/Pred Succ Identity/]
--   @'pred' ('succ' x) ≡ x@
--
-- This only works for @Enum@ types that are not bounded, meaning
-- that 'succ' and 'pred' must be total. This means that these property
-- tests work correctly for types like 'Integer' but not for 'Int'.
--
-- Sadly, there is not a good way to test 'fromEnum' and 'toEnum',
-- since many types that have reasonable implementations for 'succ'
-- and 'pred' have more inhabitants than 'Int' does.
enumLaws :: (Enum a, Eq a, Arbitrary a, Show a) => Proxy a -> Laws
enumLaws p = Laws "Enum"
  [ ("Succ Pred Identity", succPredIdentity p)
  , ("Pred Succ Identity", predSuccIdentity p)
  ]

-- | Tests the same properties as 'enumLaws' except that it requires
-- the type to have a 'Bounded' instance. These tests avoid taking the
-- successor of the maximum element or the predecessor of the minimal
-- element.
boundedEnumLaws :: (Enum a, Bounded a, Eq a, Arbitrary a, Show a) => Proxy a -> Laws
boundedEnumLaws p = Laws "Enum"
  [ ("Succ Pred Identity", succPredBoundedIdentity p)
  , ("Pred Succ Identity", predSuccBoundedIdentity p)
  ]

succPredIdentity :: forall a. (Enum a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
succPredIdentity _ = myForAllShrink False (const True)
  (\(a :: a) -> ["a = " ++ show a])
  "succ (pred x)"
  (\a -> succ (pred a))
  "x"
  (\a -> a)

predSuccIdentity :: forall a. (Enum a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
predSuccIdentity _ = myForAllShrink False (const True)
  (\(a :: a) -> ["a = " ++ show a])
  "pred (succ x)"
  (\a -> pred (succ a))
  "x"
  (\a -> a)

succPredBoundedIdentity :: forall a. (Enum a, Bounded a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
succPredBoundedIdentity _ = myForAllShrink False (\a -> a /= minBound)
  (\(a :: a) -> ["a = " ++ show a])
  "succ (pred x)"
  (\a -> succ (pred a))
  "x"
  (\a -> a)

predSuccBoundedIdentity :: forall a. (Enum a, Bounded a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
predSuccBoundedIdentity _ = myForAllShrink False (\a -> a /= maxBound)
  (\(a :: a) -> ["a = " ++ show a])
  "pred (succ x)"
  (\a -> pred (succ a))
  "x"
  (\a -> a)