{-# LANGUAGE ScopedTypeVariables #-}

module Hedgehog.Classes.Semigroup
  ( semigroupLaws
  , commutativeSemigroupLaws
  , exponentialSemigroupLaws
  , idempotentSemigroupLaws
  , rectangularBandSemigroupLaws
  ) where

import Data.Semigroup (Semigroup(..))
import Hedgehog
import Hedgehog.Classes.Common
import Data.List.NonEmpty
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import qualified Data.Foldable as Foldable

-- | Tests the following 'Semigroup' laws:
--
-- [__Associativity__]: @a '<>' (b '<>' c)@ ≡ @(a '<>' b) '<>' c@
-- [__Concatenation__]: @'sconcat'@ ≡ @'Foldable.foldr1' ('<>')@
-- [__Times__]: @'stimes' n a@ ≡ @'foldr1' ('<>') ('replicate' n a)@
semigroupLaws :: (Eq a, Semigroup a, Show a) => Gen a -> Laws
semigroupLaws gen = Laws "Semigroup"
  [ ("Associativity", semigroupAssociative gen)
  , ("Concatenation", semigroupConcatenation gen)
  , ("Times", semigroupTimes gen)
  ]

-- | Tests the following 'Semigroup' laws:
--
-- [__Commutativity__]: @a '<>' b@ ≡ @b '<>' a@
commutativeSemigroupLaws :: (Eq a, Semigroup a, Show a) => Gen a -> Laws
commutativeSemigroupLaws gen = Laws "Commutative Semigroup"
  [ ("Commutative", semigroupCommutative gen)
  ]

-- | Tests the following 'Semigroup' laws:
--
-- [__Exponentiality__]: @'stimes' n (a '<>' b)@ ≡ @'stimes' n a '<>' 'stimes' n b@
exponentialSemigroupLaws :: (Eq a, Semigroup a, Show a) => Gen a -> Laws
exponentialSemigroupLaws gen = Laws "Exponential Semigroup"
  [ ("Exponential", semigroupExponential gen)
  ]

-- | Tests the following 'Semigroup' laws:
--
-- [__Idempotency__]: @a '<>' a@ ≡ @a@
idempotentSemigroupLaws :: (Eq a, Semigroup a, Show a) => Gen a -> Laws
idempotentSemigroupLaws gen = Laws "Idempotent Semigroup"
  [ ("Idempotent", semigroupIdempotent gen)
  ]

-- | Tests the following 'Semigroup' laws:
--
-- [__Rectangular Bandedness__]: @a '<>' b '<>' a@ ≡ @a@
rectangularBandSemigroupLaws :: (Eq a, Semigroup a, Show a) => Gen a -> Laws
rectangularBandSemigroupLaws gen = Laws "Rectangular Band Semigroup"
  [ ("Rectangular Band", semigroupRectangularBand gen)
  ]

semigroupAssociative :: forall a. (Eq a, Semigroup a, Show a) => Gen a -> Property
semigroupAssociative gen = property $ do
  a <- forAll gen
  b <- forAll gen
  c <- forAll gen
  let lhs = a <> (b <> c)
  let rhs = (a <> b) <> c
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Associativity", lawContextTcName = "Semigroup"
        , lawContextLawBody = "a <> (b <> c)" `congruency` "(a <> b) <> c"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp =
            let showA = show a; showB = show b; showC = show c;
            in lawWhere
              [ "a <> (b <> c)" `congruency` "(a <> b) <> c, where"
              , "a = " ++ showA
              , "b = " ++ showB
              , "c = " ++ showC
              ]
        }
  heqCtx lhs rhs ctx

semigroupCommutative :: forall a. (Eq a, Semigroup a, Show a) => Gen a -> Property
semigroupCommutative gen = property $ do
  a <- forAll gen
  b <- forAll gen
  let lhs = a <> b
  let rhs = b <> a
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Commutativity", lawContextTcName = "Semigroup"
        , lawContextLawBody = "a <> b" `congruency` "b <> a"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp =
            let showA = show a; showB = show b;
            in lawWhere
              [ "a <> b" `congruency` "b <> a, where"
              , "a = " ++ showA
              , "b = " ++ showB
              ]
        }  
  heqCtx lhs rhs ctx

semigroupConcatenation :: forall a. (Eq a, Semigroup a, Show a) => Gen a -> Property
semigroupConcatenation gen = property $ do
  a <- forAll gen 
  as <- forAll $ genSmallList gen
  let ne = a :| as
  let lhs = sconcat ne
  let rhs = Foldable.foldr1 (<>) ne
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Concatenation", lawContextTcName = "Semigroup"
        , lawContextLawBody = "sconcat" `congruency` "foldr1 (<>)"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp =
            let showNE = show ne;
            in lawWhere
              [ "sconcat ne" `congruency` "foldr1 (<>) ne, where"
              , "ne = " ++ showNE
              ]
        }
  heqCtx lhs rhs ctx  

semigroupTimes :: forall a. (Eq a, Semigroup a, Show a) => Gen a -> Property
semigroupTimes gen = property $ do
  a <- forAll gen
  n <- forAll (Gen.int (Range.linear 2 5))
  let lhs = stimes n a
  let rhs = Foldable.foldr1 (<>) (replicate n a)
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Times", lawContextTcName = "Semigroup"
        , lawContextLawBody = "stimes n a" `congruency` "foldr1 (<>) (replicate n a)"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp =
            let showN = show n; showA = show a;
            in lawWhere
              [ "stimes n a" `congruency` "foldr1 (<>) (replicate n a), where"
              , "a = " ++ showA
              , "n = " ++ showN
              ]
        }  
  heqCtx lhs rhs ctx

semigroupExponential :: forall a. (Eq a, Semigroup a, Show a) => Gen a -> Property
semigroupExponential gen = property $ do
  a <- forAll gen
  b <- forAll gen
  n <- forAll (Gen.int (Range.linear 2 5))
  let lhs = stimes n (a <> b)
  let rhs = stimes n a <> stimes n b
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Exponential", lawContextTcName = "Semigroup"
        , lawContextLawBody = "stimes n (a <> b)" `congruency` "stimes n a <> stimes n b"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp =
            let showN = show n; showA = show a; showB = show b;
            in lawWhere
              [ "stimes n (a <> b)" `congruency` "stimes n a <> stimes n b, where"
              , "a = " ++ showA
              , "b = " ++ showB
              , "n = " ++ showN
              ]
        }  
  heqCtx lhs rhs ctx

semigroupIdempotent :: forall a. (Eq a, Semigroup a, Show a) => Gen a -> Property
semigroupIdempotent gen = property $ do
  a <- forAll gen
  let lhs = a <> a
  let rhs = a
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Idempotency", lawContextTcName = "Semigroup"
        , lawContextLawBody = "a <> a" `congruency` "a"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp =
            let showA = show a;
            in lawWhere
              [ "a <> a" `congruency` "a, where"
              , "a = " ++ showA
              ]
        }
  heqCtx lhs rhs ctx

semigroupRectangularBand :: forall a. (Eq a, Semigroup a, Show a) => Gen a -> Property
semigroupRectangularBand gen = property $ do
  a <- forAll gen
  b <- forAll gen
  let lhs = a <> b <> a
  let rhs = a
  let ctx = contextualise $ LawContext
        { lawContextLawName = "Rectangular Band", lawContextTcName = "Semigroup"
        , lawContextLawBody = "a <> b <> a" `congruency` "a"
        , lawContextReduced = reduced lhs rhs
        , lawContextTcProp =
            let showA = show a; showB = show b;
            in lawWhere
              [ "a <> b <> a" `congruency` "a, where"
              , "a = " ++ showA
              , "b = " ++ showB
              ]
        }  
  heqCtx lhs rhs ctx
