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

module Hedgehog.Classes.MonadZip (monadZipLaws) where

import Control.Arrow (Arrow(..))
import Control.Monad.Zip (MonadZip(mzip))

import Hedgehog
import Hedgehog.Classes.Common

-- | Tests the following 'MonadZip' laws:
--
-- [__Naturality__]: @'fmap' (f '***' g) ('mzip' ma mb)@ ≡ @'mzip' ('fmap' f ma) ('fmap' g mb)@
monadZipLaws ::
  ( MonadZip f
  , forall x. Eq x => Eq (f x), forall x. Show x => Show (f x)
  ) => (forall x. Gen x -> Gen (f x)) -> Laws
monadZipLaws gen = Laws "Monad"
  [ ("Naturality", monadZipNaturality gen)
  ]

type MonadZipProp f =
  ( MonadZip f
  , forall x. Eq x => Eq (f x), forall x. Show x => Show (f x)
  ) => (forall x. Gen x -> Gen (f x)) -> Property

monadZipNaturality :: forall f. MonadZipProp f
monadZipNaturality fgen = property $ do
  f' <- forAll genLinearEquation
  g' <- forAll genLinearEquation
  let f = runLinearEquation f'
      g = runLinearEquation g'
  ma <- forAll $ fgen genSmallInteger
  mb <- forAll $ fgen genSmallInteger
  let lhs = fmap (f *** g) (mzip ma mb)
  let rhs = mzip (fmap f ma) (fmap g mb)
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Naturality", lawContextTcName = "MonadZip"
        , lawContextLawBody = "(fmap (f *** g) (mzip ma mb)" `congruency` "mzip (fmap f ma) (fmap g mb)"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp =
            let showF = show f'; showG = show g'; showMA = show ma; showMB = show mb;
            in lawWhere
              [ "fmap (f *** g) (mzip ma mb)" `congruency` "mzip (fmap f ma) (fmap g mb), where"
              , "f = " ++ showF
              , "g = " ++ showG
              , "ma = " ++ showMA  
              , "mb = " ++ showMB
              ]
        }
  heqCtx1 lhs rhs ctx
