{-# LANGUAGE ScopedTypeVariables #-}

-- | Tests for "Test.ChasingBottoms.Nat".

-- The automatically derived 'Typeable' instance is not tested.

module Test.ChasingBottoms.Nat.Tests (tests) where

import Test.ChasingBottoms.Nat
import Test.ChasingBottoms.SemanticOrd
import Test.ChasingBottoms.TestUtilities
import Test.QuickCheck
import Data.Maybe
import Data.List
import Data.Ratio

default ()

-- The default versions of succ and pred go via Ints, and hence
-- perform incorrectly in the presence of large natural numbers. Hence
-- this generator is needed. Other tests can possibly also fail in the
-- presence of large natural numbers, but QuickCheck does not handle
-- large numbers very well, especially not when coarbitrary is used,
-- so we do not use this generator for all tests. Furthermore
-- defaulting is turned off here and in Test.ChasingBottoms.Nat, and
-- that should minimise surprises.

largeNat :: Gen Nat
largeNat = do
  n <- choose (0, 2 * toInteger (maxBound :: Int))
  return (fromInteger n)

-- Testing isSucc.

prop_isSucc n = isSucc n == (n > 0)

-- Testing fromSucc.

prop_fromSucc n | n == 0    = fromSucc n == Nothing
                | otherwise = fromSucc n == Just (n-1)

-- Testing natrec.

-- How do you test something as versatile as natrec? Well, at least we
-- can verify that we can use it to implement addition.

prop_natrec_add m n = natrec m (\_ o -> succ o) n == m + n

-- There is no need to test foldN, since it is specified by its
-- definition.

-- Testing Enum.

prop_Nat_Enum_succ =
  forAll largeNat $ \n ->
    succ n == n + 1
prop_Nat_Enum_pred =
  forAll largeNat $ \n ->
    n > 0 ==> pred n == n - 1

prop_Nat_Enum_toEnum (n :: Int) =
   n >= 0 ==> (toEnum n :: Nat) == fromInteger (toInteger n)
prop_Nat_Enum_fromEnum (n :: Nat) =
  n <= fromInteger (toInteger (maxBound :: Int)) ==>
  toInteger (fromEnum n) == toInteger n

-- enumFrom and friends have default definitions.

-- Testing Eq.

prop_Nat_Eq_congruence =
  eqIsCongruence arbitrary equalTo notEqualTo
                 (arbitrary :: Gen (Nat -> Integer))

equalTo (n :: Nat) = return n

notEqualTo (n :: Nat) = do
  m <- fmap succ arbitrary  -- m >= 1.
  if m <= n then
    elements [n - m, n + m]
   else
    return (n + m)

-- Testing Show.

prop_Nat_Show (m :: Nat) = show m == show (toInteger m)

-- Testing Ord.

prop_Nat_Ord_total_order = ordIsTotalOrder arbitrary
                             equalTo notEqualTo greaterThanOrEqual

greaterThanOrEqual (n :: Nat) = do
  m <- arbitrary
  return (n + m)

-- Testing Num.

prop_Nat_mul_iterated_sum (m :: Nat) n =
  m * n == foldr (+) 0 (genericReplicate m n)

prop_Nat_plus_assoc (m :: Nat) n o = m + (n + o) == (m + n) + o
prop_Nat_plus_comm (m :: Nat) n    = m + n == n + m

prop_Nat_mul_assoc (m :: Nat) n o = m * (n * o) == (m * n) * o
prop_Nat_mul_comm (m :: Nat) n    = m * n == n * m

prop_Nat_mul_plus_left_dist (m :: Nat) n o = m * (n + o) == m * n + m * o

prop_Nat_mul_plus_zero (m :: Nat) = m + 0 == m
prop_Nat_mul_mul_unit (m :: Nat)  = m * 1 == m

prop_Nat_minus n =
  forAll (greaterThanOrEqual n) $ \m ->
    (m - n) + n == m

prop_Nat_signum_abs (m :: Nat) = signum m * abs m == m
prop_Nat_signum_zero           = (signum 0 :: Nat) == 0

prop_Nat_fromInteger_plus m n =
  m >= 0 && n >= 0 ==>
    fromInteger m + fromInteger n == (fromInteger (m + n) :: Nat)
prop_Nat_fromInteger_mul m n =
  m >= 0 && n >= 0 ==>
    fromInteger m * fromInteger n == (fromInteger (m * n) :: Nat)

-- negate is undefined.

-- Testing Integral.

prop_Nat_to_from (m :: Nat) = fromInteger (toInteger m) == m
prop_Nat_from_to i = i >= 0 ==> toInteger (fromInteger i :: Nat) == i

prop_Nat_quotRem (m :: Nat) n =
  n /= 0 ==> m `quotRem` n == (m `quot` n, m `rem` n)
prop_Nat_divMod (m :: Nat) n =
  n /= 0 ==> m `divMod` n == (m `div` n, m `mod` n)

prop_Nat_quot_rem (m :: Nat) n =
  n /= 0 ==> (m `quot` n) * n + m `rem` n == m
prop_Nat_div_mod (m :: Nat) n =
  n /= 0 ==> (m `div` n) * n + m `mod` n == m

-- Testing Real.

prop_Nat_toRational (m :: Nat) = toRational m == toInteger m % 1

-- Since the implementation is based on Integers I'd like to test that
-- we can't construct values of the form "Nat i" where i is a negative
-- Integer. (This can be seen as a test of the observation function
-- toInteger.)

prop_Nat_closed = [ unary (fromJust . fromSucc)
                    -- Ord
                  , binary max
                  , binary min
                    -- Enum
                    -- enumFrom and friends have default definitions.
                  , unary succ
                  , unary pred
                  , unary' toEnum
                    -- Num
                  , binary (+)
                  , binary (-)
                  , binary (*)
                  , unary negate
                  , unary abs
                  , unary signum
                  , unary' fromInteger
                    -- Integral
                  , binary quot
                  , binary rem
                  , binary div
                  , binary mod
                  , binary (fst .^^ quotRem)
                  , binary (snd .^^ quotRem)
                  , binary (fst .^^ divMod)
                  , binary (snd .^^ divMod)
                  ]
  where
  ok (n :: Nat) = (toInteger n >= 0) <=! True

  unary (f :: Nat -> Nat) = unary' f

  unary' f =
    forAll arbitrary $ \x ->
      ok (f x)

  binary f =
    forAll arbitrary $ \(m :: Nat) ->
    forAll arbitrary $ \(n :: Nat) ->
      ok (f m n)

  f .^^ g = \x y -> f (g x y)

-- | All tests collected together.

tests :: IO Bool
tests = runQuickCheckTests theTests
  where
  theTests = map run (concat testLists) ++ singleTests

  singleTests =
    [ run prop_isSucc
    , run prop_fromSucc
    , run prop_natrec_add
    , run prop_Nat_Enum_succ
    , run prop_Nat_Enum_pred
    , run prop_Nat_Enum_toEnum
    , run prop_Nat_Enum_fromEnum
    , run prop_Nat_Show
    , run prop_Nat_mul_iterated_sum
    , run prop_Nat_plus_assoc
    , run prop_Nat_plus_comm
    , run prop_Nat_mul_assoc
    , run prop_Nat_mul_comm
    , run prop_Nat_mul_plus_left_dist
    , run prop_Nat_mul_plus_zero
    , run prop_Nat_mul_mul_unit
    , run prop_Nat_minus
    , run prop_Nat_signum_abs
    , run prop_Nat_signum_zero
    , run prop_Nat_fromInteger_plus
    , run prop_Nat_fromInteger_mul
    , run prop_Nat_to_from
    , run prop_Nat_from_to
    , run prop_Nat_quotRem
    , run prop_Nat_divMod
    , run prop_Nat_quot_rem
    , run prop_Nat_div_mod
    , run prop_Nat_toRational
    ]

  testLists =
    [ prop_Nat_Eq_congruence
    , prop_Nat_Ord_total_order
    , prop_Nat_closed
    ]
