{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE KindSignatures      #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications    #-}

module Test.Utils where

import           Data.AdditiveGroup
import           Data.Aeson
import           Data.AffineSpace
import           Data.Functor.Classes
import           Data.Proxy
import           Data.Semigroup
import           Hedgehog
import qualified Hedgehog.Gen         as Gen
import qualified Hedgehog.Range       as Range
import           Test.Tasty
import           Test.Tasty.Hedgehog
import           Test.Tasty.HUnit

eq1Laws ::
       forall f. (Eq1 f, Applicative f)
    => Proxy f
    -> TestTree
eq1Laws _ =
    let nilEq =
            assertEqual "Nil equal" True $ liftEq (==) (pure ()) (pure @f ())
    in testGroup "Eq1 Laws" [testCase "Nil Eq" nilEq]

aesonLaws :: (Show a, Eq a, ToJSON a, FromJSON a) => Gen a -> TestTree
aesonLaws gen =
    let encodeDecode = property $ do
          a <- forAll gen
          Just a === decode (encode a)
    in testGroup "Aeson Laws" [testProperty "Encode decode" encodeDecode]

semigroupLaws :: (Show a, Eq a, Semigroup a) => Gen a -> TestTree
semigroupLaws gen =
  let assoc = property $ do
         a <- forAll gen
         b <- forAll gen
         c <- forAll gen
         a <> (b <> c) === (a <> b) <> c
  in testGroup "Semigroup Laws" [testProperty "Associative" assoc]

monoidLaws :: (Show a, Eq a, Monoid a) => Gen a -> TestTree
monoidLaws gen =
  let assoc =
        property $ do
          a <- forAll gen
          b <- forAll gen
          c <- forAll gen
          mappend a (mappend b c) === mappend (mappend a b) c
      memptyId =
        property $ do
          a <- forAll gen
          a === mappend mempty a
          a === mappend a mempty
      concatIsFold =
        property $ do
          as <- forAll $ Gen.list (Range.linear 0 100) gen
          mconcat as === foldr mappend mempty as
  in testGroup
       "Monoid laws"
       [ testProperty "Associative" assoc
       , testProperty "Mempty Id" memptyId
       , testProperty "Concat is Fold" concatIsFold
       ]

additiveGroupLaws :: (Show a, Eq a, AdditiveGroup a) => Gen a -> TestTree
additiveGroupLaws gen =
  let assoc =
        property $ do
          a <- forAll gen
          b <- forAll gen
          c <- forAll gen
          a ^+^  (b ^+^ c) === (a ^+^  b) ^+^ c
      zeroId =
        property $ do
          a <- forAll gen
          a === zeroV ^+^ a
          a === a ^+^ zeroV
      inverseId = property $ do
          a <- forAll gen
          a ^-^ a === zeroV
      takeLeaves = property $ do
          a <- forAll gen
          b <- forAll gen
          a ^-^ (a ^-^ b) === b
  in testGroup
       "AdditiveGroup laws"
       [ testProperty "Associative" assoc
       , testProperty "Zero Id" zeroId
       , testProperty "Inverse id is zeroV" inverseId
       , testProperty "a - (a - b) = b" takeLeaves
       ]

affineSpaceLaws ::
       (Show a, Eq a, AffineSpace a, Eq (Diff a), Show (Diff a))
    => Gen a
    -> TestTree
affineSpaceLaws gen =
    let addZero =
            property $ do
                a <- forAll gen
                a === a .+^ zeroV
        takeSelf =
            property $ do
                a <- forAll gen
                a .-. a === zeroV
    in testGroup
           "AffineSpace Laws"
           [testProperty "Add Zero" addZero, testProperty "Take self" takeSelf]

applicativeLaws ::
       forall f a.
       (Applicative f, Traversable f, Show (f a), Eq (f a), Num a, Show a)
    => Proxy f
    -> Gen a
    -> TestTree
applicativeLaws _ gen =
    let genF :: Gen (f a) = sequence $ pure gen
        identiy =
            property $ do
                v <- forAll genF
                v === (pure id <*> v)
        homomorphism =
            property $ do
                x <- forAll gen
                f <- (+) <$> forAll gen
                (pure f <*> pure x) === pure @f (f x)
    in testGroup
           "Applicative Laws"
           [ testProperty "Identity" identiy
           , testProperty "Homomorphism" homomorphism
           ]