{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables   #-}
module Test.Validity.Functions.Inverse
    ( -- ** Standard tests involving inverse functions
      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
    -> Property
inverseFunctionsOnGen f g gen =
    forAll gen $ \a ->
      g (f a) `shouldBe` a


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


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

-- |
-- '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


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


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


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

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


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


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


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

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


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


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

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

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