class SemigroupLawTester:

    def __init__(self, semigroup, value1, value2, value3, result):
        self.semigroup = semigroup
        self.value1 = value1
        self.value2 = value2
        self.value3 = value3
        self.result = result

    def associativity_test(self):

        x = self.semigroup(self.value1)\
                .concat(self.semigroup(self.value2))\
                .concat(self.semigroup(self.value3))

        y = self.semigroup(self.value1).concat(
                self.semigroup(self.value2).concat(self.semigroup(self.value3))
            )

        assert x == y == self.result

    def test(self):
        self.associativity_test() class MonoidLawTester:

    def __init__(self, monoid, value):
        self.monoid = monoid
        self.value = value

    def left_identity_test(self):
        monoid = self.monoid(self.value)
        assert monoid.concat(monoid.neutral()) == monoid

    def right_identity_test(self):
        monoid = self.monoid(self.value)
        assert monoid.neutral().concat(monoid) == monoid

    def test(self):
        self.left_identity_test()
        self.right_identity_test() class Sum(Semigroup):
    """
    Sum is a Monoid that will combine 2 numbers under addition.
    """

    neutral_element = 0

    def __str__(self) -> str:  # pragma: no cover
        return 'Sum[value={}]'.format(self.value)

    def concat(self, semigroup: 'Sum') -> 'Sum':
        """
        :param semigroup: other semigroup to concat
        :type semigroup: Sum[B]
        :returns: new Sum with sum of concat semigroups values
        :rtype: Sum[A]
        """
        return Sum(self.value + semigroup.value) class All(Semigroup):
    """
    All is a Monoid that will combine 2 values of any type using logical conjunction on their coerced Boolean values.
    """

    neutral_element = True

    def __str__(self) -> str:  # pragma: no cover
        return 'All[value={}]'.format(self.value)

    def concat(self, semigroup: 'All') -> 'All':
        """
        :param semigroup: other semigroup to concat
        :type semigroup: All[B]
        :returns: new All with last truly value or first falsy
        :rtype: All[A | B]
        """
        return All(self.value and semigroup.value) class One(Semigroup):
    """
    One is a Monoid that will combine 2 values of any type using logical disjunction OR on their coerced Boolean values.
    """

    neutral_element = False

    def __str__(self) -> str:  # pragma: no cover
        return 'One[value={}]'.format(self.value)

    def concat(self, semigroup):
        """
        :param semigroup: other semigroup to concat
        :type semigroup: One[B]
        :returns: new One with first truly value or last falsy
        :rtype: One[A | B]
        """
        return One(self.value or semigroup.value) class First(Semigroup):
    """
    First is a Monoid that will always return the first, value when 2 First instances are combined.
    """

    def __str__(self) -> str:  # pragma: no cover
        return 'Fist[value={}]'.format(self.value)

    def concat(self, semigroup):
        """
        :param semigroup: other semigroup to concat
        :type semigroup: First[B]
        :returns: new First with first value
        :rtype: First[A]
        """
        return First(self.value) class Last(Semigroup):
    """
    Last is a Monoid that will always return the lastest, value when 2 Last instances are combined.
    """

    def __str__(self) -> str:  # pragma: no cover
        return 'Last[value={}]'.format(self.value)

    def concat(self, semigroup):
        """
        :param semigroup: other semigroup to concat
        :type semigroup: Last[B]
        :returns: new Last with last value
        :rtype: Last[A]
        """
        return Last(semigroup.value) class Map(Semigroup):
    """
    Map is a Semigroup that will always return contated all values inside Map value
    """

    def __str__(self) -> str:  # pragma: no cover
        return 'Map[value={}]'.format(self.value)

    def concat(self, semigroup):
        """
        :param semigroup: other semigroup to concat
        :type semigroup: Map[B]
        :returns: new Map with concated all values
        :rtype: Map[A]
        """
        return Map(
            {key: value.concat(semigroup.value[key]) for key, value in self.value.items()}
        ) class Max(Semigroup):
    """
    Max is a Monoid that will combines 2 numbers, resulting in the largest of the two.
    """

    neutral_element = -float("inf")

    def __str__(self) -> str:  # pragma: no cover
        return 'Max[value={}]'.format(self.value)

    def concat(self, semigroup):
        """
        :param semigroup: other semigroup to concat
        :type semigroup: Max[B]
        :returns: new Max with largest value
        :rtype: Max[A | B]
        """
        return Max(self.value if self.value > semigroup.value else semigroup.value) class Min(Semigroup):
    """
    Min is a Monoid that will combines 2 numbers, resulting in the smallest of the two.
    """

    neutral_element = float("inf")

    def __str__(self) -> str:  # pragma: no cover
        return 'Min[value={}]'.format(self.value)

    def concat(self, semigroup):
        """
        :param semigroup: other semigroup to concat
        :type semigroup: Min[B]
        :returns: new Min with smallest value
        :rtype: Min[A | B]
        """
        return Min(self.value if self.value <= semigroup.value else semigroup.value) def identity(value: T) -> T:
    """
    Return first argument.

    :param value:
    :type value: Any
    :returns:
    :rtype: Any
    """
    return value
from hypothesis import given
from hypothesis.strategies import text, integers, booleans, dictionaries
from testers.semigroup_law_tester import SemigroupLawTester
from testers.monoid_law_tester import MonoidLawTester
from pymonet.semigroups import Sum, All, One, First, Last, Map, Max, Min
from pymonet.utils import identity
    ).test()


@given(integers(), integers(), integers())
def test_max_semigroup(x, y, z):
    SemigroupLawTester(
        semigroup=Max,
        value1=x,
        value2=y,
        value3=z,
        result=Max(max([x, y, z]))
    ).test()


@given(integers())
def test_max_monoid(integer):
    MonoidLawTester(
        monoid=Max,
        value=integer
    ).test()


@given(integers(), integers(), integers())
def test_min_semigroup(x, y, z):
    SemigroupLawTester(
        semigroup=Min,
        value1=x,
        value2=y,
        value3=z,
        result=Min(min([x, y, z]))
    ).test()


@given(integers())
def test_min_monoid(integer):
    MonoidLawTester(
        monoid=Min,
        value=integer
    ).test()


@given(booleans(), booleans(), booleans())
def test_all(bool1, bool2, bool3):
    SemigroupLawTester(
        semigroup=All,
        value1=bool1,
        value2=bool2,
        value3=bool3,
        result=All(bool1 and bool2 and bool3)
    ).test()


@given(booleans())
def test_all_monoid(boolean):
    MonoidLawTester(
        monoid=All,
        value=boolean
    ).test()


@given(booleans(), booleans(), booleans())
def test_one(bool1, bool2, bool3):
    SemigroupLawTester(
        semigroup=One,
        value1=bool1,
        value2=bool2,
        value3=bool3,
        result=One(bool1 or bool2 or bool3)
    ).test()


@given(booleans())
def test_one_monoid(boolean):
    MonoidLawTester(
        monoid=One,
        value=boolean
    ).test()


@given(text(), text(), text())
def test_first_semigroup(text1, text2, text3):
    SemigroupLawTester(
        semigroup=First,
        value1=text1,
        value2=text2,
        value3=text3,
        result=First(text1)
    ).test()


@given(text(), text(), text())
def test_last_semigroup(text1, text2, text3):
    SemigroupLawTester(
        semigroup=Last,
        value1=text1,
        value2=text2,
        value3=text3,
        result=Last(text3)
    ).test()


@given(
    integers(), integers(), integers(),
    booleans(), booleans(), booleans(),
    text(), text(), text()
)
def test_map(integer1, integer2, integer3, boolean1, boolean2, boolean3, text1, text2, text3):
    SemigroupLawTester(
        semigroup=Map,
        value1={
            'sum': Sum(integer1),
            'all': All(boolean1),
            'first': First(text1),
            'last': Last(text1),
            'min': Min(integer1),
            'max': Max(integer1)
        },
        value2={
            'sum': Sum(integer2),
            'all': All(boolean2),

            'first': First(text2),