{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE AllowAmbiguousTypes #-}

-- | Eq properties
--
-- You will need @TypeApplications@ to use these.
module Test.Validity.Eq
    ( eqSpecOnValid
    , eqSpecOnInvalid
    , eqSpec
    , eqSpecOnArbitrary
    , eqSpecOnGen
    ) where

import Data.Data

import Data.GenValidity

import Test.Hspec
import Test.QuickCheck

import Test.Validity.Functions
import Test.Validity.Relations
import Test.Validity.Utils

eqTypeStr ::
       forall a. Typeable a
    => String
eqTypeStr = binRelStr @a "=="

neqTypeStr ::
       forall a. Typeable a
    => String
neqTypeStr = binRelStr @a "/="

-- | Standard test spec for properties of Eq instances for valid values
--
-- Example usage:
--
-- > eqSpecOnValid @Double
eqSpecOnValid ::
       forall a. (Show a, Eq a, Typeable a, GenValid a)
    => Spec
eqSpecOnValid = eqSpecOnGen @a genValid "valid" shrinkValid

-- | Standard test spec for properties of Eq instances for invalid values
--
-- Example usage:
--
-- > eqSpecOnInvalid @Double
eqSpecOnInvalid ::
       forall a. (Show a, Eq a, Typeable a, GenInvalid a)
    => Spec
eqSpecOnInvalid = eqSpecOnGen @a genInvalid "invalid" shrinkInvalid

-- | Standard test spec for properties of Eq instances for unchecked values
--
-- Example usage:
--
-- > eqSpec @Int
eqSpec ::
       forall a. (Show a, Eq a, Typeable a, GenUnchecked a)
    => Spec
eqSpec = eqSpecOnGen @a genUnchecked "unchecked" shrinkUnchecked

-- | Standard test spec for properties of Eq instances for arbitrary values
--
-- Example usage:
--
-- > eqSpecOnArbitrary @Int
eqSpecOnArbitrary ::
       forall a. (Show a, Eq a, Typeable a, Arbitrary a)
    => Spec
eqSpecOnArbitrary = eqSpecOnGen @a arbitrary "arbitrary" shrink

-- | Standard test spec for properties of Eq instances for values generated by a given generator (and name for that generator).
--
-- Example usage:
--
-- > eqSpecOnGen ((* 2) <$> genValid @Int) "even"
eqSpecOnGen ::
       forall a. (Show a, Eq a, Typeable a)
    => Gen a
    -> String
    -> (a -> [a])
    -> Spec
eqSpecOnGen gen genname s =
    parallel $ do
        let name = nameOf @a
            funeqstr = eqTypeStr @a
            funneqstr = neqTypeStr @a
            gen2 = (,) <$> gen <*> gen
            gen3 = (,,) <$> gen <*> gen <*> gen
            s2 = shrinkT2 s
        describe ("Eq " ++ name) $ do
            let eq = (==) @a
                neq = (/=) @a
            describe funeqstr $ do
                it
                    (unwords
                         [ "is reflexive for"
                         , "\"" ++ genname
                         , name ++ "\"" ++ "s"
                         ]) $
                    reflexivityOnGen eq gen s
                it
                    (unwords
                         [ "is symmetric for"
                         , "\"" ++ genname
                         , name ++ "\"" ++ "s"
                         ]) $
                    symmetryOnGens eq gen2 s
                it
                    (unwords
                         [ "is transitive for"
                         , "\"" ++ genname
                         , name ++ "\"" ++ "s"
                         ]) $
                    transitivityOnGens eq gen3 s
                it
                    (unwords
                         [ "is equivalent to (\\a b -> not $ a /= b) for"
                         , "\"" ++ genname
                         , name ++ "\"" ++ "s"
                         ]) $
                    equivalentOnGens2 eq (\a b -> not $ a `neq` b) gen2 s2
            describe funneqstr $ do
                it
                    (unwords
                         [ "is antireflexive for"
                         , "\"" ++ genname
                         , name ++ "\"" ++ "s"
                         ]) $
                    antireflexivityOnGen neq gen s
                it
                    (unwords
                         [ "is equivalent to (\\a b -> not $ a == b) for"
                         , "\"" ++ genname
                         , name ++ "\"" ++ "s"
                         ]) $
                    equivalentOnGens2 neq (\a b -> not $ a `eq` b) gen2 s2
