{-# LANGUAGE ScopedTypeVariables #-}

{-# OPTIONS_GHC -Wall #-}

module Test.QuickCheck.Classes.Num
  ( numLaws
  ) where

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

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

-- | Tests the following properties:
--
-- [/Additive Commutativity/]
--   @a + b ≡ b + a@
-- [/Additive Left Identity/]
--   @0 + a ≡ a@
-- [/Additive Right Identity/]
--   @a + 0 ≡ a@
-- [/Multiplicative Associativity/]
--   @a * (b * c) ≡ (a * b) * c@
-- [/Multiplicative Left Identity/]
--   @1 * a ≡ a@
-- [/Multiplicative Right Identity/]
--   @a * 1 ≡ a@
-- [/Multiplication Left Distributes Over Addition/]
--   @a * (b + c) ≡ (a * b) + (a * c)@
-- [/Multiplication Right Distributes Over Addition/]
--   @(a + b) * c ≡ (a * c) + (b * c)@
-- [/Multiplicative Left Annihilation/]
--   @0 * a ≡ 0@
-- [/Multiplicative Right Annihilation/]
--   @a * 0 ≡ 0@
-- [/Additive Inverse/]
--   @'negate' a '+' a ≡ 0@
-- [/Subtraction/]
--   @a '+' 'negate' b ≡ a '-' b@
-- [/Abs Is Idempotent/]
--   @'abs' ('abs' a) ≡ 'abs' a
-- [/Signum Is Idempotent/]
--   @'signum' ('signum' a) ≡ 'signum' a
-- [/Product Of Abs And Signum Is Id/]
--   @'abs' a * 'signum' a ≡ a@
numLaws :: (Num a, Eq a, Arbitrary a, Show a) => Proxy a -> Laws
numLaws p = Laws "Num"
  [ ("Additive Commutativity", numCommutativePlus p)
  , ("Additive Left Identity", numLeftIdentityPlus p)
  , ("Additive Right Identity", numRightIdentityPlus p)
  , ("Multiplicative Associativity", numAssociativeTimes p)
  , ("Multiplicative Left Identity", numLeftIdentityTimes p)
  , ("Multiplicative Right Identity", numRightIdentityTimes p)
  , ("Multiplication Left Distributes Over Addition", numLeftMultiplicationDistributes p)
  , ("Multiplication Right Distributes Over Addition", numRightMultiplicationDistributes p)
  , ("Multiplicative Left Annihilation", numLeftAnnihilation p)
  , ("Multiplicative Right Annihilation", numRightAnnihilation p)
  , ("Additive Inverse", numAdditiveInverse p)
  , ("Subtraction", numSubtraction p)
  , ("Abs Is Idempotent", absIdempotence p)
  , ("Signum Is Idempotent", signumIdempotence p)
  , ("Product Of Abs And Signum Is Id", absSignumId p)
  ]

numLeftMultiplicationDistributes :: forall a. (Num a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
numLeftMultiplicationDistributes _ = myForAllShrink True (const True)
  (\(a :: a,b,c) -> ["a = " ++ show a, "b = " ++ show b, "c = " ++ show c])
  "a * (b + c)"
  (\(a,b,c) -> a * (b + c))
  "(a * b) + (a * c)"
  (\(a,b,c) -> (a * b) + (a * c))

numRightMultiplicationDistributes :: forall a. (Num a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
numRightMultiplicationDistributes _ = myForAllShrink True (const True)
  (\(a :: a,b,c) -> ["a = " ++ show a, "b = " ++ show b, "c = " ++ show c])
  "(a + b) * c"
  (\(a,b,c) -> (a + b) * c)
  "(a * c) + (b * c)"
  (\(a,b,c) -> (a * c) + (b * c))

numLeftIdentityPlus :: forall a. (Num a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
numLeftIdentityPlus _ = myForAllShrink False (const True)
  (\(a :: a) -> ["a = " ++ show a])
  "0 + a"
  (\a -> 0 + a)
  "a"
  (\a -> a)

numRightIdentityPlus :: forall a. (Num a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
numRightIdentityPlus _ = myForAllShrink False (const True)
  (\(a :: a) -> ["a = " ++ show a])
  "a + 0"
  (\a -> a + 0)
  "a"
  (\a -> a)

numRightIdentityTimes :: forall a. (Num a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
numRightIdentityTimes _ = myForAllShrink False (const True)
  (\(a :: a) -> ["a = " ++ show a])
  "a * 1"
  (\a -> a * 1)
  "a"
  (\a -> a)

numLeftIdentityTimes :: forall a. (Num a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
numLeftIdentityTimes _ = myForAllShrink False (const True)
  (\(a :: a) -> ["a = " ++ show a])
  "1 * a"
  (\a -> 1 * a)
  "a"
  (\a -> a)

numLeftAnnihilation :: forall a. (Num a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
numLeftAnnihilation _ = myForAllShrink False (const True)
  (\(a :: a) -> ["a = " ++ show a])
  "0 * a"
  (\a -> 0 * a)
  "0"
  (\_ -> 0)

numRightAnnihilation :: forall a. (Num a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
numRightAnnihilation _ = myForAllShrink False (const True)
  (\(a :: a) -> ["a = " ++ show a])
  "a * 0"
  (\a -> a * 0)
  "0"
  (\_ -> 0)

numCommutativePlus :: forall a. (Num a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
numCommutativePlus _ = myForAllShrink True (const True)
  (\(a :: a,b) -> ["a = " ++ show a, "b = " ++ show b])
  "a + b"
  (\(a,b) -> a + b)
  "b + a"
  (\(a,b) -> b + a)

numAssociativeTimes :: forall a. (Num a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
numAssociativeTimes _ = myForAllShrink True (const True)
  (\(a :: a,b,c) -> ["a = " ++ show a, "b = " ++ show b, "c = " ++ show c])
  "a * (b * c)"
  (\(a,b,c) -> a * (b * c))
  "(a * b) * c"
  (\(a,b,c) -> (a * b) * c)

numAdditiveInverse :: forall a. (Num a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
numAdditiveInverse _ = myForAllShrink True (const True)
  (\(a :: a) -> ["a = " ++ show a])
  "negate a + a"
  (\a -> (-a) + a)
  "0"
  (const 0)

numSubtraction :: forall a. (Num a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
numSubtraction _ = myForAllShrink True (const True)
  (\(a :: a, b :: a) -> ["a = " ++ show a, "b = " ++ show b])
  "a + negate b"
  (\(a,b) -> a + negate b)
  "a - b"
  (\(a,b) -> a - b)

absIdempotence :: forall a. (Num a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
absIdempotence _ = myForAllShrink True (const True)
  (\(a :: a) -> ["a = " ++ show a])
  "abs (abs a)"
  (abs . abs)
  "abs a"
  abs

signumIdempotence :: forall a. (Num a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
signumIdempotence _ = myForAllShrink True (const True)
  (\(a :: a) -> ["a = " ++ show a])
  "signum (signum a)"
  (signum . signum)
  "signum a"
  signum

absSignumId :: forall a. (Num a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
absSignumId _ = myForAllShrink True (const True)
  (\(a :: a) -> ["a = " ++ show a])
  "abs a * signum a"
  (\a -> abs a * signum a)
  "a"
  id
