{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables   #-}
module Test.Validity.Operations.Identity
    ( -- *** Left identity
      leftIdentityOnElemWithEquality
    , leftIdentityOnGenWithEquality
    , leftIdentityOnGen
    , leftIdentityOnValid
    , leftIdentity

      -- *** Right identity
    , rightIdentityOnElemWithEquality
    , rightIdentityOnGenWithEquality
    , rightIdentityOnGen
    , rightIdentityOnValid
    , rightIdentity

    , identityOnGen
    , identityOnValid
    , identity
    ) where

import           Data.GenValidity

import           Test.QuickCheck

-- |
--
-- \[
--   LeftIdentity(\star, \doteq, b)
--   \quad\equiv\quad
--   \forall a: (b \star a) \doteq a
-- \]
leftIdentityOnElemWithEquality
    :: (b -> a -> a)    -- ^ A binary operation
    -> (a -> a -> Bool) -- ^ An equality
    -> b                -- ^ A candidate left-identity
    -> a                -- ^ An element
    -> Bool
leftIdentityOnElemWithEquality op eq b a = (b `op` a) `eq` a

leftIdentityOnGenWithEquality
    :: Show a
    => (b -> a -> a)    -- ^ A binary operation
    -> (a -> a -> Bool) -- ^ An equality
    -> b                -- ^ A candidate left-identity
    -> Gen a
    -> Property
leftIdentityOnGenWithEquality op eq b gen =
    forAll gen $ leftIdentityOnElemWithEquality op eq b

leftIdentityOnGen
    :: (Show a, Eq a)
    => (b -> a -> a) -- ^ A binary operation
    -> b             -- ^ A candidate left-identity
    -> Gen a
    -> Property
leftIdentityOnGen op = leftIdentityOnGenWithEquality op (==)

leftIdentityOnValid
    :: (Show a, Eq a, GenValidity a)
    => (b -> a -> a)
    -> b
    -> Property
leftIdentityOnValid op b
    = leftIdentityOnGen op b genValid

leftIdentity
    :: (Show a, Eq a, GenValidity a)
    => (b -> a -> a)
    -> b
    -> Property
leftIdentity op b
    = leftIdentityOnGen op b genUnchecked

-- |
--
-- \[
--   RightIdentity(\star, \doteq, b)
--   \quad\equiv\quad
--   \forall a: (a \star b) \doteq a
-- \]
rightIdentityOnElemWithEquality
    :: (a -> b -> a)    -- ^ A binary operation
    -> (a -> a -> Bool) -- ^ An equality
    -> b                -- ^ A candidate right-identity
    -> a                -- ^ An element
    -> Bool
rightIdentityOnElemWithEquality op eq b a = (a `op` b) `eq` a

rightIdentityOnGenWithEquality
    :: Show a
    => (a -> b -> a)    -- ^ A binary operation
    -> (a -> a -> Bool) -- ^ An equality
    -> b                -- ^ A candidate right-identity
    -> Gen a
    -> Property
rightIdentityOnGenWithEquality op eq b gen =
    forAll gen $ rightIdentityOnElemWithEquality op eq b

rightIdentityOnGen
    :: (Show a, Eq a)
    => (a -> b -> a)    -- ^ A binary operation
    -> b                -- ^ A candidate right-identity
    -> Gen a
    -> Property
rightIdentityOnGen op = rightIdentityOnGenWithEquality op (==)

rightIdentityOnValid
    :: (Show a, Eq a, GenValidity a)
    => (a -> b -> a)
    -> b
    -> Property
rightIdentityOnValid op b
    = rightIdentityOnGen op b genValid

rightIdentity
    :: (Show a, Eq a, GenValidity a)
    => (a -> b -> a)
    -> b
    -> Property
rightIdentity op b
    = rightIdentityOnGen op b genUnchecked

identityOnGen
    :: (Show a, Eq a)
    => (a -> a -> a)
    -> a
    -> Gen a
    -> Property
identityOnGen op e gen =
    leftIdentityOnGen op e gen .&&. rightIdentityOnGen op e gen

identityOnValid
    :: (Show a, Eq a, GenValidity a)
    => (a -> a -> a)
    -> a
    -> Property
identityOnValid op a
    = identityOnGen op a genValid

identity
    :: (Show a, Eq a, GenValidity a)
    => (a -> a -> a)
    -> a
    -> Property
identity op e
    = identityOnGen op e genUnchecked