{-# LANGUAGE OverloadedLists #-}
-----------------------------------------------------------------------------
-- |
-- Module     : Algebra.Graph.Test.Label
-- Copyright  : (c) Andrey Mokhov 2016-2022
-- License    : MIT (see the file LICENSE)
-- Maintainer : andrey.mokhov@gmail.com
-- Stability  : experimental
--
-- Testsuite for "Algebra.Graph.Label".
-----------------------------------------------------------------------------
module Algebra.Graph.Test.Label (
  -- * Testsuite
  testLabel
  ) where

import Algebra.Graph.Test
import Algebra.Graph.Label
import Data.Monoid

type Unary a          = a -> a
type Binary a         = a -> a -> a
type Additive a       = Binary a
type Multiplicative a = Binary a
type Star a           = Unary a
type Identity a       = a
type Zero a           = a
type One a            = a

associative :: Eq a => Binary a -> a -> a -> a -> Property
associative (<>) a b c = (a <> b) <> c == a <> (b <> c) // "Associative"

commutative :: Eq a => Binary a -> a -> a -> Property
commutative (<>) a b = a <> b == b <> a // "Commutative"

idempotent :: Eq a => Binary a -> a -> Property
idempotent (<>) a = a <> a == a // "Idempotent"

annihilatingZero :: Eq a => Binary a -> Zero a -> a -> Property
annihilatingZero (<>) z a = conjoin
    [ a <> z == z // "Left"
    , z <> a == z // "Right" ] // "Annihilating zero"

closure :: Eq a => Additive a -> Multiplicative a -> One a -> Star a -> a -> Property
closure (+) (*) o s a = conjoin
    [ s a == o + (a * s a) // "Left"
    , s a == o + (s a * a) // "Right" ] // "Closure"

leftDistributive :: Eq a => Additive a -> Multiplicative a -> a -> a -> a -> Property
leftDistributive (+) (*) a b c =
    a * (b + c) == (a * b) + (a * c) // "Left distributive"

rightDistributive :: Eq a => Additive a -> Multiplicative a -> a -> a -> a -> Property
rightDistributive (+) (*) a b c =
    (a + b) * c == (a * c) + (b * c) // "Right distributive"

distributive :: Eq a => Additive a -> Multiplicative a -> a -> a -> a -> Property
distributive p m a b c = conjoin
    [ leftDistributive p m a b c
    , rightDistributive p m a b c ] // "Distributive"

identity :: Eq a => Binary a -> Identity a -> a -> Property
identity (<>) e a = conjoin
    [ a <> e == a // "Left"
    , e <> a == a // "Right" ] // "Identity"

semigroup :: Eq a => Binary a -> a -> a -> a -> Property
semigroup f a b c = associative f a b c // "Semigroup"

monoid :: Eq a => Binary a -> Identity a -> a -> a -> a -> Property
monoid f e a b c = conjoin
    [ semigroup f a b c
    , identity f e a ] // "Monoid"

commutativeMonoid :: Eq a => Binary a -> Identity a -> a -> a -> a -> Property
commutativeMonoid f e a b c = conjoin
    [ monoid f e a b c
    , commutative f a b ] // "Commutative monoid"

leftNearRing :: Eq a => Additive a -> Zero a -> Multiplicative a -> One a -> a -> a -> a -> Property
leftNearRing (+) z (*) o a b c = conjoin
    [ commutativeMonoid (+) z a b c
    , monoid (*) o a b c
    , leftDistributive (+) (*) a b c
    , annihilatingZero (*) z a ] // "Left near ring"

semiring :: Eq a => Additive a -> Zero a -> Multiplicative a -> One a -> a -> a -> a -> Property
semiring (+) z (*) o a b c = conjoin
    [ commutativeMonoid (+) z a b c
    , monoid (*) o a b c
    , distributive (+) (*) a b c
    , annihilatingZero (*) z a ] // "Semiring"

dioid :: Eq a => Additive a -> Zero a -> Multiplicative a -> One a -> a -> a -> a -> Property
dioid (+) z (*) o a b c = conjoin
    [ semiring (+) z (*) o a b c
    , idempotent (+) a ] // "Dioid"

starSemiring :: Eq a => Additive a -> Zero a -> Multiplicative a -> One a -> Star a -> a -> a -> a -> Property
starSemiring (+) z (*) o s a b c = conjoin
    [ semiring (+) z (*) o a b c
    , closure (+) (*) o s a ] // "Star semiring"

testLeftNearRing :: (Eq a, Semiring a) => a -> a -> a -> Property
testLeftNearRing = leftNearRing (<+>) zero (<.>) one

testSemiring :: (Eq a, Semiring a) => a -> a -> a -> Property
testSemiring = semiring (<+>) zero (<.>) one

testDioid :: (Eq a, Dioid a) => a -> a -> a -> Property
testDioid = dioid (<+>) zero (<.>) one

testStarSemiring :: (Eq a, StarSemiring a) => a -> a -> a -> Property
testStarSemiring = starSemiring (<+>) zero (<.>) one star

testLabel :: IO ()
testLabel = do
    putStrLn "\n============ Graph.Label ============"
    putStrLn "\n============ Any: instances ============"
    test "Semiring"     $ testSemiring     @Any
    test "StarSemiring" $ testStarSemiring @Any
    test "Dioid"        $ testDioid        @Any

    putStrLn "\n============ Distance Int: instances ============"
    test "Semiring"     $ testSemiring     @(Distance Int)
    test "StarSemiring" $ testStarSemiring @(Distance Int)
    test "Dioid"        $ testDioid        @(Distance Int)

    putStrLn "\n============ Capacity Int: instances ============"
    test "Semiring"     $ testSemiring     @(Capacity Int)
    test "StarSemiring" $ testStarSemiring @(Capacity Int)
    test "Dioid"        $ testDioid        @(Capacity Int)

    putStrLn "\n============ Minimum (Path Int): instances ============"
    test "LeftNearRing" $ testLeftNearRing @(Minimum (Path Int))

    putStrLn "\n============ PowerSet (Path Int): instances ============"
    test "Semiring" $ size10 $ testSemiring @(PowerSet (Path Int))
    test "Dioid"    $ size10 $ testDioid    @(PowerSet (Path Int))

    putStrLn "\n============ Count Int: instances ============"
    test "Semiring"     $ testSemiring     @(Count Int)
    test "StarSemiring" $ testStarSemiring @(Count Int)
