{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables   #-}
module Test.Validity.Functions.Equivalence
    ( -- ** Standard tests involving equivalence of functions
      equivalentOnGen
    , equivalentOnValid
    , equivalent
    , equivalentOnGens2
    , equivalentOnValids2
    , equivalent2
    , equivalentWhenFirstSucceedsOnGen
    , equivalentWhenFirstSucceedsOnValid
    , equivalentWhenFirstSucceeds
    , equivalentWhenFirstSucceedsOnGens2
    , equivalentWhenFirstSucceedsOnValids2
    , equivalentWhenFirstSucceeds2
    , equivalentWhenSecondSucceedsOnGen
    , equivalentWhenSecondSucceedsOnValid
    , equivalentWhenSecondSucceeds
    , equivalentWhenSecondSucceedsOnGens2
    , equivalentWhenSecondSucceedsOnValids2
    , equivalentWhenSecondSucceeds2
    , equivalentWhenSucceedOnGen
    , equivalentWhenSucceedOnValid
    , equivalentWhenSucceed
    , equivalentWhenSucceedOnGens2
    , equivalentWhenSucceedOnValids2
    , equivalentWhenSucceed2
    ) where

import           Data.GenValidity

import           Test.Hspec
import           Test.QuickCheck

import           Test.Validity.Types

equivalentOnGen
    :: (Show a, Eq a, Show b, Eq b)
    => (a -> b)
    -> (a -> b)
    -> Gen a
    -> Property
equivalentOnGen f g gen =
    forAll gen $ \a ->
        f a `shouldBe` g a

equivalentOnValid
    :: (Show a, Eq a, GenValidity a, Show b, Eq b)
    => (a -> b)
    -> (a -> b)
    -> Property
equivalentOnValid f g
    = equivalentOnGen f g genValid

equivalent
    :: (Show a, Eq a, GenValidity a, Show b, Eq b)
    => (a -> b)
    -> (a -> b)
    -> Property
equivalent f g
    = equivalentOnGen f g genUnchecked

equivalentOnGens2
    :: (Show a, Eq a,
        Show b, Eq b,
        Show c, Eq c)
    => (a -> b -> c)
    -> (a -> b -> c)
    -> Gen (a, b)
    -> Property
equivalentOnGens2 f g gen =
    forAll gen $ \(a, b) ->
        f a b `shouldBe` g a b

equivalentOnValids2
    :: (Show a, Eq a, GenValidity a,
        Show b, Eq b, GenValidity b,
        Show c, Eq c)
    => (a -> b -> c)
    -> (a -> b -> c)
    -> Property
equivalentOnValids2 f g
    = equivalentOnGens2 f g genValid

equivalent2
    :: (Show a, Eq a, GenValidity a,
        Show b, Eq b, GenValidity b,
        Show c, Eq c)
    => (a -> b -> c)
    -> (a -> b -> c)
    -> Property
equivalent2 f g
    = equivalentOnGens2 f g genUnchecked

equivalentWhenFirstSucceedsOnGen
    :: (Show a, Eq a, Show b, Eq b, CanFail f)
    => (a -> f b)
    -> (a -> b)
    -> Gen a
    -> Property
equivalentWhenFirstSucceedsOnGen f g gen =
    forAll gen $ \a ->
        case resultIfSucceeded (f a) of
            Nothing -> return () -- fine
            Just r  -> r `shouldBe` g a

equivalentWhenFirstSucceedsOnValid
    :: (Show a, Eq a, GenValidity a, Show b, Eq b, CanFail f)
    => (a -> f b)
    -> (a -> b)
    -> Property
equivalentWhenFirstSucceedsOnValid f g
    = equivalentWhenFirstSucceedsOnGen f g genValid

equivalentWhenFirstSucceeds
    :: (Show a, Eq a, GenValidity a, Show b, Eq b, CanFail f)
    => (a -> f b)
    -> (a -> b)
    -> Property
equivalentWhenFirstSucceeds f g
    = equivalentWhenFirstSucceedsOnGen f g genUnchecked

equivalentWhenFirstSucceedsOnGens2
    :: (Show a, Eq a,
        Show b, Eq b,
        Show c, Eq c,
        CanFail f)
    => (a -> b -> f c)
    -> (a -> b -> c)
    -> Gen (a, b)
    -> Property
equivalentWhenFirstSucceedsOnGens2 f g gen =
    forAll gen $ \(a, b) ->
        case resultIfSucceeded (f a b) of
            Nothing -> return () -- fine
            Just rs -> rs `shouldBe` g a b

equivalentWhenFirstSucceedsOnValids2
    :: (Show a, Eq a, GenValidity a,
        Show b, Eq b, GenValidity b,
        Show c, Eq c,
        CanFail f)
    => (a -> b -> f c)
    -> (a -> b -> c)
    -> Property
equivalentWhenFirstSucceedsOnValids2 f g
    = equivalentWhenFirstSucceedsOnGens2 f g genValid

equivalentWhenFirstSucceeds2
    :: (Show a, Eq a, GenValidity a,
        Show b, Eq b, GenValidity b,
        Show c, Eq c,
        CanFail f)
    => (a -> b -> f c)
    -> (a -> b -> c)
    -> Property
equivalentWhenFirstSucceeds2 f g
    = equivalentWhenFirstSucceedsOnGens2 f g genUnchecked

equivalentWhenSecondSucceedsOnGen
    :: (Show a, Eq a, Show b, Eq b, CanFail f)
    => (a -> b)
    -> (a -> f b)
    -> Gen a
    -> Property
equivalentWhenSecondSucceedsOnGen f g gen =
    forAll gen $ \a ->
        case resultIfSucceeded (g a) of
            Nothing -> return () -- fine
            Just r  -> r `shouldBe` f a

equivalentWhenSecondSucceedsOnValid
    :: (Show a, Eq a, GenValidity a, Show b, Eq b, CanFail f)
    => (a -> b)
    -> (a -> f b)
    -> Property
equivalentWhenSecondSucceedsOnValid f g
    = equivalentWhenSecondSucceedsOnGen f g genValid

equivalentWhenSecondSucceeds
    :: (Show a, Eq a, GenValidity a, Show b, Eq b, CanFail f)
    => (a -> b)
    -> (a -> f b)
    -> Property
equivalentWhenSecondSucceeds f g
    = equivalentWhenSecondSucceedsOnGen f g genUnchecked

equivalentWhenSecondSucceedsOnGens2
    :: (Show a, Eq a,
        Show b, Eq b,
        Show c, Eq c,
        CanFail f)
    => (a -> b -> c)
    -> (a -> b -> f c)
    -> Gen (a, b)
    -> Property
equivalentWhenSecondSucceedsOnGens2 f g gen =
    forAll gen $ \(a, b) ->
        case resultIfSucceeded (g a b) of
            Nothing -> return () -- fine
            Just rs -> rs `shouldBe` f a b

equivalentWhenSecondSucceedsOnValids2
    :: (Show a, Eq a, GenValidity a,
         Show b, Eq b, GenValidity b,
         Show c, Eq c,
         CanFail f)
    => (a -> b -> c)
    -> (a -> b -> f c)
    -> Property
equivalentWhenSecondSucceedsOnValids2 f g
    = equivalentWhenSecondSucceedsOnGens2 f g genValid

equivalentWhenSecondSucceeds2
    :: (Show a, Eq a, GenValidity a,
        Show b, Eq b, GenValidity b,
        Show c, Eq c,
        CanFail f)
    => (a -> b -> c)
    -> (a -> b -> f c)
    -> Property
equivalentWhenSecondSucceeds2 f g
    = equivalentWhenSecondSucceedsOnGens2 f g genUnchecked

equivalentWhenSucceedOnGen
    :: (Show a, Eq a, Show b, Eq b, CanFail f)
    => (a -> f b)
    -> (a -> f b)
    -> Gen a
    -> Property
equivalentWhenSucceedOnGen f g gen =
    forAll gen $ \a ->
        case do fa <- resultIfSucceeded $ f a
                ga <- resultIfSucceeded $ g a
                return (fa, ga)
            of
            Nothing -> return () -- fine
            Just (fa, ga)  -> fa `shouldBe` ga

equivalentWhenSucceedOnValid
    :: (Show a, Eq a, GenValidity a, Show b, Eq b, CanFail f)
    => (a -> f b)
    -> (a -> f b)
    -> Property
equivalentWhenSucceedOnValid f g
    = equivalentWhenSucceedOnGen f g genValid

equivalentWhenSucceed
    :: (Show a, Eq a, GenValidity a, Show b, Eq b, CanFail f)
    => (a -> f b)
    -> (a -> f b)
    -> Property
equivalentWhenSucceed f g
    = equivalentWhenSucceedOnGen f g genUnchecked

equivalentWhenSucceedOnGens2
    :: (Show a, Eq a,
        Show b, Eq b,
        Show c, Eq c,
        CanFail f)
    => (a -> b -> f c)
    -> (a -> b -> f c)
    -> Gen (a, b)
    -> Property
equivalentWhenSucceedOnGens2 f g gen =
    forAll gen $ \(a, b) ->
        case do fab <- resultIfSucceeded $ f a b
                gab <- resultIfSucceeded $ g a b
                return (fab, gab)
                of
            Nothing -> return () -- fine
            Just (fab, gab) -> fab `shouldBe` gab

equivalentWhenSucceedOnValids2
    :: (Show a, Eq a, GenValidity a,
        Show b, Eq b, GenValidity b,
        Show c, Eq c,
        CanFail f)
    => (a -> b -> f c)
    -> (a -> b -> f c)
    -> Property
equivalentWhenSucceedOnValids2 f g
    = equivalentWhenSucceedOnGens2 f g genValid

equivalentWhenSucceed2
    :: (Show a, Eq a, GenValidity a,
        Show b, Eq b, GenValidity b,
        Show c, Eq c,
        CanFail f)
    => (a -> b -> f c)
    -> (a -> b -> f c)
    -> Property
equivalentWhenSucceed2 f g
    = equivalentWhenSucceedOnGens2 f g genUnchecked