{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | This module is intended to be imported @qualified@, for example:
--
-- > import qualified Test.Tasty.Laws.Monad as Monad
--
module Test.Tasty.Laws.Monad
  ( test
  , testMono
  , testMonoExhaustive
  ) where

#if !MIN_VERSION_base(4,8,0)
import Control.Applicative (Applicative)
#endif
import Data.Functor.Identity (Identity)
import Test.SmallCheck.Series (Series, Serial(series))
import Test.SmallCheck.Laws.Monad (associativity, associativitySum)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.SmallCheck (testProperty)
import qualified Test.Tasty.Laws.Applicative as Applicative

-- | @tasty@ 'TestTree' for 'Monad' laws. The type signature forces the
--   parameter to be '()' which, unless you are dealing with non-total
--   functions, should be enough to test any 'Monad's.
test
  :: ( Applicative m, Monad m
     , Eq (m ()), Eq (m (m ()))
     , Show (m ()), Show (m (() -> ()))
     , Serial Identity (m ())
     , Serial IO (m ()), Serial IO (m (() -> ()))
     )
  => Series IO (m ()) -> TestTree
test = testMonoExhaustive

-- | @tasty@ 'TestTree' for 'Monad' laws. Monomorphic sum 'Series'.
testMono
  :: forall m a .
     ( Applicative m, Monad m
     , Eq a, Eq (m a), Eq (m (m a))
     , Show a, Show (m a), Show (m (a -> a))
     , Serial Identity a, Serial Identity (m a)
     , Serial IO a, Serial IO (a -> a)
     , Serial IO (m a) ,Serial IO (m (a -> a)), Serial IO (a -> m a)
     )
  => Series IO (m a) -> TestTree
testMono ms = testGroup "Monad laws"
  [ Applicative.testMono ms
  , testProperty "(m >>= f) >>= g ≡ m (f >=> g)"
  $ associativitySum ms (series :: Series IO (a -> m a))
                        (series :: Series IO (a -> m a))
  ]

-- | @tasty@ 'TestTree' for 'Monad' laws. Monomorphic product 'Series'.
testMonoExhaustive
  :: forall m a .
     ( Applicative m, Monad m
     , Eq a, Eq (m a), Eq (m (m a))
     , Show a, Show (m a), Show (m (a -> a))
     , Serial Identity a, Serial Identity (m a)
     , Serial IO a, Serial IO (a -> a)
     , Serial IO (m a) ,Serial IO (m (a -> a)), Serial IO (a -> m a)
     )
  => Series IO (m a) -> TestTree
testMonoExhaustive ms = testGroup "Monad laws"
  [ Applicative.testMono ms
  , testProperty "(m >>= f) >>= g ≡ m (f >=> g)"
    $ associativity ms (series :: Series IO (a -> m a))
                       (series :: Series IO (a -> m a))
  ]