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

#ifndef HAVE_COMONAD

module Hedgehog.Classes.Comonad () where

#else

module Hedgehog.Classes.Comonad (comonadLaws) where

import Control.Comonad

import Hedgehog
import Hedgehog.Classes.Common

-- | Tests the following 'Comonad' laws:
--
-- [__Extend/Extract Identity__]: @'extend' 'extract' ≡ 'id'@
-- [__Extract/Extend__]: @'extract' '.' 'extend' f ≡ f@
-- [__Extend/Extend__]: @'extend' f '.' 'extend' g ≡ 'extend' (f '.' 'extend' g)@
-- [__Extract Right Identity__]: @f '=>=' 'extract' ≡ f@
-- [__Extract Left Identity__]: @'extract' '=>=' f ≡ f@
-- [__Cokleisli Associativity__]: @(f '=>=' g) '=>=' h ≡ f '=>=' (g '=>=' h)@
-- [__Extract/Duplicate Identity__]: @'extract' '.' 'duplicate' ≡ 'id'@
-- [__Fmap Extract/Duplicate Identity__]: @'fmap' 'extract' '.' 'duplicate' ≡ 'id'@
-- [__Double Duplication__]: @'duplicate' '.' 'duplicate' ≡ 'fmap' 'duplicate' '.' 'duplicate'@
-- [__Extend/Fmap . Duplicate Identity__]: @'extend' f ≡ 'fmap' f '.' 'duplicate'@
-- [__Duplicate/Extend id Identity__]: @'duplicate' ≡ 'extend' 'id'@
-- [__Fmap/Extend Extract__]: @'fmap' f ≡ 'extend' (f '.' 'extract')@
-- [__Fmap/LiftW Isomorphism__]: @'fmap' ≡ 'liftW'@
comonadLaws ::
  ( Comonad f
  , forall x. Eq x => Eq (f x), forall x. Show x => Show (f x)
  ) => (forall x. Gen x -> Gen (f x)) -> Laws
comonadLaws gen = Laws "Comonad"
  [ ("Extend/Extract Identity", extendExtractIdentity gen)
  , ("Extract/Extend", extractExtend gen)
  , ("Extend/Extend", extendExtend gen)
  , ("Extract Right Identity", extractRightIdentity gen)
  , ("Extract Left Identity", extractLeftIdentity gen)
  , ("Cokleisli Associativity", cokleisliAssociativity gen)
  , ("Extract/Duplicate Identity", extractDuplicateIdentity gen)
  , ("Fmap Extract/Duplicate Identity", fmapExtractDuplicateIdentity gen)
  , ("Double Duplication", doubleDup gen)
  , ("Extend/Fmap . Duplicate Identity", extendDuplicate gen)
  , ("Duplicate/Extend id Identity", duplicateExtendId gen)
  , ("Fmap/Extend Extract", fmapExtendExtract gen)
  , ("Fmap/LiftW Isomorphism", fmapLiftW gen)
  ]

type ComonadProp f =
  ( Comonad f
  , forall x. Eq x => Eq (f x), forall x. Show x => Show (f x)
  ) => (forall x. Gen x -> Gen (f x)) -> Property

extendExtractIdentity :: forall f. ComonadProp f
extendExtractIdentity fgen = property $ do
  x <- forAll $ fgen genSmallInteger
  let lhs = extend extract x
  let rhs = x
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Extend/Extract Identity", lawContextTcName = "Comonad"
        , lawContextLawBody = "extend extract" `congruency` "id"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp = lawWhere
            [ "extend extract x" `congruency` "x, where"
            , "x = " ++ show x
            ]
        }
  heqCtx lhs rhs ctx

extractExtend :: forall f. ComonadProp f
extractExtend fgen = property $ do
  x <- forAll $ fgen genSmallInteger
  k <- forAll $ genLinearEquationW fgen
  let k' = runLinearEquationW k
  let lhs = extract . extend k' $ x
  let rhs = k' x
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Extract/Extend", lawContextTcName = "Comonad"
        , lawContextLawBody = "extract . extend f" `congruency` "f"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp = lawWhere
            [ "extract . extend f $ x" `congruency` "f x, where"
            , "f = " ++ show k
            , "x = " ++ show x
            ]
        }
  heqCtx lhs rhs ctx

extendExtend :: forall f. ComonadProp f
extendExtend fgen = property $ do
  x <- forAll $ fgen genSmallInteger
  f' <- forAll $ genLinearEquationW fgen
  g' <- forAll $ genLinearEquationW fgen
  let f = runLinearEquationW f'
  let g = runLinearEquationW g'
  let lhs = extend f . extend g $ x
  let rhs = extend (f . extend g) x
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Extend/Extend", lawContextTcName = "Comonad"
        , lawContextLawBody = "extend f . extend g" `congruency` "extend (f . extend g)"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp = lawWhere
            [ "extend f . extend g $ x" `congruency` "extend (f . extend g) $ x, where"
            , "f = " ++ show f'
            , "g = " ++ show g'
            , "x = " ++ show x
            ]
        }
  heqCtx lhs rhs ctx

extractRightIdentity :: forall f. ComonadProp f
extractRightIdentity fgen = property $ do
  x <- forAll $ fgen genSmallInteger
  f' <- forAll $ genLinearEquationW fgen
  let f = runLinearEquationW f'
  let lhs = f =>= extract $ x
  let rhs = f x
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Extract Cokleisli Right Identity", lawContextTcName = "Comonad"
        , lawContextLawBody = "f =>= extract" `congruency` "f"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp = lawWhere
            [ "f =>= extract $ x" `congruency` "f x, where"
            , "f = " ++ show f'
            , "x = " ++ show x
            ]
        }
  heqCtx lhs rhs ctx

extractLeftIdentity :: forall f. ComonadProp f
extractLeftIdentity fgen = property $ do
  x <- forAll $ fgen genSmallInteger
  f' <- forAll $ genLinearEquationW fgen
  let f = runLinearEquationW f'
  let lhs = extract =>= f $ x
  let rhs = f x
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Extract Cokleisli Left Identity", lawContextTcName = "Comonad"
        , lawContextLawBody = "extract =>= f" `congruency` "f"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp = lawWhere
            [ "extract =>= f $ x" `congruency` "f x, where"
            , "f = " ++ show f'
            , "x = " ++ show x
            ]
        }
  heqCtx lhs rhs ctx

cokleisliAssociativity :: forall f. ComonadProp f
cokleisliAssociativity fgen = property $ do
  x <- forAll $ fgen genSmallInteger
  f' <- forAll $ genLinearEquationW fgen
  g' <- forAll $ genLinearEquationW fgen
  h' <- forAll $ genLinearEquationW fgen
  let f = runLinearEquationW f'
  let g = runLinearEquationW g'
  let h = runLinearEquationW h'
  let lhs = (f =>= g) =>= h $ x
  let rhs = f =>= (g =>= h) $ x
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Cokleisli Associativity", lawContextTcName = "Comonad"
        , lawContextLawBody = "(f =>= g) =>= h" `congruency` "f =>= (g =>= h)"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp = lawWhere
            [ "(f =>= g) =>= h $ x" `congruency` "f =>= (g =>= h) $ x, where"
            , "f = " ++ show f'
            , "g = " ++ show g'
            , "h = " ++ show h'
            , "x = " ++ show x
            ]
        }
  heqCtx lhs rhs ctx


extractDuplicateIdentity :: forall f. ComonadProp f
extractDuplicateIdentity fgen = property $ do
  x <- forAll $ fgen genSmallInteger
  let lhs = extract . duplicate $ x
  let rhs = x
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Extract/Duplicate Identity", lawContextTcName = "Comonad"
        , lawContextLawBody = "extract . duplicate" `congruency` "id"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp = lawWhere
            [ "extract . duplicate $ x" `congruency` "x, where"
            , "x = " ++ show x
            ]
        }
  heqCtx lhs rhs ctx

fmapExtractDuplicateIdentity :: forall f. ComonadProp f
fmapExtractDuplicateIdentity fgen = property $ do
  x <- forAll $ fgen genSmallInteger
  let lhs = fmap extract . duplicate $ x
  let rhs = x
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Fmap Extract/Duplicate Identity", lawContextTcName = "Comonad"
        , lawContextLawBody = "fmap extract . duplicate" `congruency` "id"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp = lawWhere
            [ "fmap extract . duplicate $ x" `congruency` "x, where"
            , "x = " ++ show x
            ]
        }
  heqCtx lhs rhs ctx

doubleDup :: forall f. ComonadProp f
doubleDup fgen = property $ do
  x <- forAll $ fgen genSmallInteger
  let lhs = duplicate . duplicate $ x
  let rhs = fmap duplicate . duplicate $ x
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Double Duplicate", lawContextTcName = "Comonad"
        , lawContextLawBody = "duplicate . duplicate" `congruency` "fmap duplicate . duplicate"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp = lawWhere
            [ "duplicate . duplicate $ x" `congruency` "fmap duplicate . duplicate $ x, where"
            , "x = " ++ show x
            ]
        }
  heqCtx lhs rhs ctx

extendDuplicate :: forall f. ComonadProp f
extendDuplicate fgen = property $ do
  x <- forAll $ fgen genSmallInteger
  f' <- forAll $ genLinearEquationW fgen
  let f = runLinearEquationW f'
  let lhs = extend f $ x
  let rhs = fmap f . duplicate $ x
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Extend/Fmap Duplicate", lawContextTcName = "Comonad"
        , lawContextLawBody = "extend f" `congruency` "fmap f . duplicate"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp = lawWhere
            [ "extend f x" `congruency` "fmap f . duplicate $ x, where"
            , "f = " ++ show f'
            , "x = " ++ show x
            ]
        }
  heqCtx lhs rhs ctx

duplicateExtendId :: forall f. ComonadProp f
duplicateExtendId fgen = property $ do
  x <- forAll $ fgen genSmallInteger
  let lhs = duplicate x
  let rhs = extend id x
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Duplicate/Extend Id", lawContextTcName = "Comonad"
        , lawContextLawBody = "duplicate" `congruency` "extend id"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp = lawWhere
            [ "duplicate x" `congruency` "extend id x, where"
            , "x = " ++ show x
            ]
        }
  heqCtx lhs rhs ctx

fmapExtendExtract :: forall f. ComonadProp f
fmapExtendExtract fgen = property $ do
  x :: f Integer <- forAll $ fgen genSmallInteger
  f' <- forAll genLinearEquation
  let f = runLinearEquation f'
  let lhs = fmap f x
  let rhs = extend (f . extract) x
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Fmap/Extend Extract", lawContextTcName = "Comonad"
        , lawContextLawBody = "fmap f" `congruency` "extend (f . extract)"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp = lawWhere
            [ "fmap f x" `congruency` "extend (f . extract) x, where"
            , "f = " ++ show f'
            , "x = " ++ show x
            ]
        }
  heqCtx lhs rhs ctx

fmapLiftW :: forall f. ComonadProp f
fmapLiftW fgen = property $ do
  x <- forAll $ fgen genSmallInteger
  f' <- forAll genLinearEquation
  let f = runLinearEquation f'
  let lhs = fmap f x
  let rhs = liftW f x
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Fmap/LiftW", lawContextTcName = "Comonad"
        , lawContextLawBody = "fmap" `congruency` "liftW"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp = lawWhere
            [ "fmap f x" `congruency` "liftW f x, where"
            , "f = " ++ show f'
            , "x = " ++ show x
            ]
        }
  heqCtx lhs rhs ctx

#endif
