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

    -- | Tests for RelativeValidity instances
    --
    -- You will need @TypeApplications@ to use these.
    module Test.Validity.RelativeValidity
        ( relativeValiditySpec
        , relativeValidityImpliesValidA
        , relativeValidityImpliesValidB
        ) where

    import Data.Data

    import Data.GenRelativeValidity
    import Data.GenValidity

    import Test.Hspec
    import Test.QuickCheck

    import Test.Validity.Property.Utils
    import Test.Validity.Utils

    -- | A @Spec@ that specifies that @isValidFor@ implies @isValid@
    --
    -- In general it is a good idea to add this spec to your test suite if
    -- the @a@ and @b@ in @RelativeValidity a b@ also have a @Validity@ instance.
    --
    -- Example usage:
    --
    -- > relativeValiditySpec @MyDataFor @MyOtherData
    relativeValiditySpec ::
           forall a b.
           ( Typeable a
           , Typeable b
           , Show a
           , Show b
           , Validity a
           , Validity b
           , GenUnchecked a
           , GenUnchecked b
           , RelativeValidity a b
           )
        => Spec
    relativeValiditySpec =
        parallel $ do
            let nameOne = nameOf @a
                nameTwo = nameOf @b
            describe ("RelativeValidity " ++ nameOne ++ " " ++ nameTwo) $
                describe
                    ("isValidFor :: " ++ nameOne ++ " -> " ++ nameTwo ++ " -> Bool") $ do
                    it ("implies isValid " ++ nameOne ++ " for any " ++ nameTwo) $
                        relativeValidityImpliesValidA @a @b
                    it ("implies isValid " ++ nameTwo ++ " for any " ++ nameOne) $
                        relativeValidityImpliesValidB @a @b

    -- | @isValidFor a b@ implies @isValid a@ for all @b@
    relativeValidityImpliesValidA ::
           forall a b.
           ( Show a
           , Show b
           , Validity a
           , GenUnchecked a
           , GenUnchecked b
           , RelativeValidity a b
           )
        => Property
    relativeValidityImpliesValidA =
        forAllUnchecked $ \(a :: a) ->
            forAllUnchecked $ \(b :: b) -> (a `isValidFor` b) ===> isValid a

    -- | @isValidFor a b@ implies @isValid b@ for all @a@
    relativeValidityImpliesValidB ::
           forall a b.
           ( Show a
           , Show b
           , Validity b
           , GenUnchecked a
           , GenUnchecked b
           , RelativeValidity a b
           )
        => Property
    relativeValidityImpliesValidB =
        forAllUnchecked $ \(a :: a) ->
            forAllUnchecked $ \(b :: b) -> (a `isValidFor` b) ===> isValid b