{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- | Standard tests involving inverse functions
module Test.Validity.Functions.Inverse
    ( inverseFunctionsOnGen
    , inverseFunctionsOnValid
    , inverseFunctions
    , inverseFunctionsOnArbitrary
    , inverseFunctionsIfFirstSucceedsOnGen
    , inverseFunctionsIfFirstSucceedsOnValid
    , inverseFunctionsIfFirstSucceeds
    , inverseFunctionsIfFirstSucceedsOnArbitrary
    , inverseFunctionsIfSecondSucceedsOnGen
    , inverseFunctionsIfSecondSucceedsOnValid
    , inverseFunctionsIfSecondSucceeds
    , inverseFunctionsIfSecondSucceedsOnArbitrary
    , inverseFunctionsIfSucceedOnGen
    , inverseFunctionsIfSucceedOnValid
    , inverseFunctionsIfSucceed
    , inverseFunctionsIfSucceedOnArbitrary
    ) where

import Data.GenValidity

import Test.Hspec
import Test.QuickCheck

import Test.Validity.Types

inverseFunctionsOnGen ::
       (Show a, Eq a) => (a -> b) -> (b -> a) -> Gen a -> (a -> [a]) -> Property
inverseFunctionsOnGen f g gen s =
    forAllShrink gen s $ \a -> g (f a) `shouldBe` a

inverseFunctionsOnValid ::
       (Show a, Eq a, GenValid a) => (a -> b) -> (b -> a) -> Property
inverseFunctionsOnValid f g = inverseFunctionsOnGen f g genValid shrinkValid

inverseFunctions ::
       (Show a, Eq a, GenUnchecked a) => (a -> b) -> (b -> a) -> Property
inverseFunctions f g = inverseFunctionsOnGen f g genUnchecked shrinkUnchecked

-- |
-- 'id' is its own inverse function for every type:
-- prop> inverseFunctionsOnArbitrary id (id :: Int -> Int)
inverseFunctionsOnArbitrary ::
       (Show a, Eq a, Arbitrary a) => (a -> b) -> (b -> a) -> Property
inverseFunctionsOnArbitrary f g = inverseFunctionsOnGen f g arbitrary shrink

inverseFunctionsIfFirstSucceedsOnGen ::
       (Show a, Eq a, CanFail f)
    => (a -> f b)
    -> (b -> a)
    -> Gen a
    -> (a -> [a])
    -> Property
inverseFunctionsIfFirstSucceedsOnGen f g gen s =
    forAllShrink gen s $ \a ->
        case resultIfSucceeded (f a) of
            Nothing -> return () -- fine
            Just b -> g b `shouldBe` a

inverseFunctionsIfFirstSucceedsOnValid ::
       (Show a, Eq a, GenValid a, CanFail f)
    => (a -> f b)
    -> (b -> a)
    -> Property
inverseFunctionsIfFirstSucceedsOnValid f g =
    inverseFunctionsIfFirstSucceedsOnGen f g genValid shrinkValid

inverseFunctionsIfFirstSucceeds ::
       (Show a, Eq a, GenUnchecked a, CanFail f)
    => (a -> f b)
    -> (b -> a)
    -> Property
inverseFunctionsIfFirstSucceeds f g =
    inverseFunctionsIfFirstSucceedsOnGen f g genUnchecked shrinkUnchecked

inverseFunctionsIfFirstSucceedsOnArbitrary ::
       (Show a, Eq a, Arbitrary a, CanFail f)
    => (a -> f b)
    -> (b -> a)
    -> Property
inverseFunctionsIfFirstSucceedsOnArbitrary f g =
    inverseFunctionsIfFirstSucceedsOnGen f g arbitrary shrink

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

inverseFunctionsIfSecondSucceedsOnValid ::
       (Show a, Eq a, GenValid a, CanFail f)
    => (a -> b)
    -> (b -> f a)
    -> Property
inverseFunctionsIfSecondSucceedsOnValid f g =
    inverseFunctionsIfSecondSucceedsOnGen f g genValid shrinkValid

inverseFunctionsIfSecondSucceeds ::
       (Show a, Eq a, GenUnchecked a, CanFail f)
    => (a -> b)
    -> (b -> f a)
    -> Property
inverseFunctionsIfSecondSucceeds f g =
    inverseFunctionsIfSecondSucceedsOnGen f g genUnchecked shrinkUnchecked

inverseFunctionsIfSecondSucceedsOnArbitrary ::
       (Show a, Eq a, Arbitrary a, CanFail f)
    => (a -> b)
    -> (b -> f a)
    -> Property
inverseFunctionsIfSecondSucceedsOnArbitrary f g =
    inverseFunctionsIfSecondSucceedsOnGen f g arbitrary shrink

inverseFunctionsIfSucceedOnGen ::
       (Show a, Eq a, CanFail f, CanFail g)
    => (a -> f b)
    -> (b -> g a)
    -> Gen a
    -> (a -> [a])
    -> Property
inverseFunctionsIfSucceedOnGen f g gen s =
    forAllShrink gen s $ \a ->
        case do fa <- resultIfSucceeded $ f a
                resultIfSucceeded $ g fa of
            Nothing -> return () -- fine
            Just r -> r `shouldBe` a

inverseFunctionsIfSucceedOnValid ::
       (Show a, Eq a, GenValid a, CanFail f, CanFail g)
    => (a -> f b)
    -> (b -> g a)
    -> Property
inverseFunctionsIfSucceedOnValid f g =
    inverseFunctionsIfSucceedOnGen f g genValid shrinkValid

inverseFunctionsIfSucceed ::
       (Show a, Eq a, GenUnchecked a, CanFail f, CanFail g)
    => (a -> f b)
    -> (b -> g a)
    -> Property
inverseFunctionsIfSucceed f g =
    inverseFunctionsIfSucceedOnGen f g genUnchecked shrinkUnchecked

inverseFunctionsIfSucceedOnArbitrary ::
       (Show a, Eq a, Arbitrary a, CanFail f, CanFail g)
    => (a -> f b)
    -> (b -> g a)
    -> Property
inverseFunctionsIfSucceedOnArbitrary f g =
    inverseFunctionsIfSucceedOnGen f g arbitrary shrink
