{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE RankNTypes #-}

module Hedgehog.Classes.MonadPlus (monadPlusLaws) where

import Control.Monad (MonadPlus(..))

import Hedgehog
import Hedgehog.Classes.Common

-- | Tests the following 'MonadPlus' laws:
--
-- [__Left Identity__]: @'mplus' 'mzero'@ ≡ @'id'@
-- [__Right Identity__]: @'flip' 'mplus' 'mzero'@ ≡ @'id'@
-- [__Associativity__]: @'mplus' a ('mplus' b c)@ ≡ @'mplus' ('mplus' a b) c@
-- [__Left Zero__]: @'mzero' '>>=' f@ ≡ @'mzero'@
-- [__Right Zero__]: @v '>>' 'mzero'@ ≡ @'mzero'@
monadPlusLaws ::
  ( MonadPlus f
  , forall x. Eq x => Eq (f x), forall x. Show x => Show (f x)
  ) => (forall x. Gen x -> Gen (f x)) -> Laws
monadPlusLaws gen = Laws "Monad"
  [ ("Left Identity", monadPlusLeftIdentity gen)
  , ("Right Identity", monadPlusRightIdentity gen)
  , ("Associativity", monadPlusAssociativity gen)
  , ("Left Zero", monadPlusLeftZero gen)
  , ("Right Zero", monadPlusRightZero gen)
  ]

type MonadPlusProp f =
  ( MonadPlus f
  , forall x. Eq x => Eq (f x), forall x. Show x => Show (f x)
  ) => (forall x. Gen x -> Gen (f x)) -> Property

monadPlusLeftIdentity :: forall f. MonadPlusProp f
monadPlusLeftIdentity fgen = property $ do
  x <- forAll $ fgen genSmallInteger
  let lhs = mplus mzero x
  let rhs = x
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Left Identity", lawContextTcName = "MonadPlus"
        , lawContextLawBody = "mplus mzero" `congruency` "id"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp  =
            let showX = show x; showMZero = show (mzero :: f Integer);
            in lawWhere
              [ "mplus mzero x" `congruency` "id x, where"
              , "x = " ++ showX
              , "mzero = " ++ showMZero
              ]
        }
  heqCtx1 lhs rhs ctx

monadPlusRightIdentity :: forall f. MonadPlusProp f
monadPlusRightIdentity fgen = property $ do
  x <- forAll $ fgen genSmallInteger
  let lhs = mplus x mzero
  let rhs = x
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Right Identity", lawContextTcName = "MonadPlus"
        , lawContextLawBody = "flip mplus mzero" `congruency` "id"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp  =
            let showX = show x; showMZero = show (mzero :: f Integer);
            in lawWhere
              [ "mplus x mzero" `congruency` "id x, where"
              , "x = " ++ showX
              , "mzero = " ++ showMZero
              ]
        }
  heqCtx1 lhs rhs ctx

monadPlusAssociativity :: forall f. MonadPlusProp f
monadPlusAssociativity fgen = property $ do
  a <- forAll $ fgen genSmallInteger
  b <- forAll $ fgen genSmallInteger
  c <- forAll $ fgen genSmallInteger
  let lhs = mplus a (mplus b c)
  let rhs = mplus (mplus a b) c
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Associativity", lawContextTcName = "MonadPlus"
        , lawContextLawBody = "mplus a (mplus b c)" `congruency` "mplus (mplus a b) c"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp =
            let showA = show a; showB = show b; showC = show c;
            in lawWhere
              [ "mplus a (mplus b c)" `congruency` "mplus (mplus a b) c, where"
              , "a = " ++ showA
              , "b = " ++ showB
              , "c = " ++ showC
              ]
        }
  heqCtx1 lhs rhs ctx

monadPlusLeftZero :: forall f. MonadPlusProp f
monadPlusLeftZero _ = property $ do
  k' :: LinearEquationM f <- forAll genLinearEquationM
  let lhs = mzero >>= runLinearEquationM k'
  let rhs = mzero
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Left Zero", lawContextTcName = "MonadPlus"
        , lawContextLawBody = "mzero >>= f" `congruency` "mzero"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp  =
            let showF = show k'; showMZero = show (mzero :: f Integer);
            in lawWhere
              [ "mzero >>= f" `congruency` "mzero, where"
              , "f = " ++ showF
              , "mzero = " ++ showMZero
              ]
        }
  heqCtx1 lhs rhs ctx

monadPlusRightZero :: forall f. MonadPlusProp f
monadPlusRightZero fgen = property $ do
  v <- forAll $ fgen genSmallInteger
  let lhs = v >> (mzero :: f Integer)
  let rhs = mzero
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Right Zero", lawContextTcName = "MonadPlus"
        , lawContextLawBody = "v >> mzero" `congruency` "mzero"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp  =
            let showV = show v; showMZero = show (mzero :: f Integer);
            in lawWhere
              [ "v >> mzero" `congruency` "mzero, where"
              , "v = " ++ showV
              , "mzero = " ++ showMZero
              ]
        }
  heqCtx1 lhs rhs ctx