class MonadLawTester:  # pragma: no cover

    def __init__(self, monad, value, mapper1, mapper2, get_fn=identity):
        self.monad = monad
        self.value = value
        self.mapper1 = mapper1
        self.mapper2 = mapper2
        self.get_fn = get_fn

    def _assert(self, x, y):
        assert self.get_fn(x) == self.get_fn(y)

    def associativity_test(self):
        x = self.monad(self.value).bind(self.mapper1).bind(self.mapper2)
        y = self.monad(self.value).bind(lambda value: self.mapper2(value).bind(self.mapper1))

        self._assert(x, y)

    def left_unit_test(self):
        self._assert(self.monad(self.value).bind(self.mapper1), self.mapper1(self.value))

    def right_unit_test(self):
        self._assert(self.monad(self.value).bind(lambda value: self.monad(value)), self.monad(self.value))

    def test(self, run_associativity_law_test=True, run_left_law_test=True, run_right_law_test=True):
        if run_associativity_law_test:
            self.associativity_test()
        if run_left_law_test:
            self.left_unit_test()
        if run_right_law_test:
            self.right_unit_test() class FunctorLawTester:  # pragma: no cover

    def __init__(self, functor, mapper1, mapper2, get_fn=identity):
        self.functor = functor
        self.mapper1 = mapper1
        self.mapper2 = mapper2
        self.get_fn = get_fn

    def _assert(self, x, y):
        assert self.get_fn(x) == self.get_fn(y)

    def identity_law_test(self):
        x = self.functor.map(identity)
        y = self.functor

        self._assert(x, y)

    def composition_law_test(self):
        mapped_functor1 = self.functor.map(self.mapper1).map(self.mapper2)
        mapped_fuctor2 = self.functor.map(lambda value: self.mapper2(self.mapper1(value)))
        self._assert(mapped_functor1, mapped_fuctor2)

    def test(self, run_identity_law_test=True, run_composition_law_test=True):
        if run_identity_law_test:
            self.identity_law_test()
        if run_composition_law_test:
            self.composition_law_test() class MonadTransformTester:  # pragma: no cover

    def __init__(self, monad, value, is_fail=False):
        self.monad = monad
        self.value = value
        self.is_fail = is_fail

    def to_box_test(self):
        assert self.monad(self.value).to_box() == Box(self.value)

    def to_maybe_test(self):
        if self.is_fail:
            assert self.monad(self.value).to_maybe() == Maybe.nothing()
        else:
            assert self.monad(self.value).to_maybe() == Maybe.just(self.value)

    def to_either_test(self):
        if self.is_fail:
            assert self.monad(self.value).to_either() == Left(self.value)
        else:
            assert self.monad(self.value).to_either() == Right(self.value)

    def to_lazy_test(self):
        assert self.monad(self.value).to_lazy().get() == self.value

    def to_try_test(self):
        assert self.monad(self.value).to_try() == Try(self.value, is_success=not self.is_fail)

    def to_validation_test(self):
        if self.is_fail:
            assert self.monad(self.value).to_validation() == Validation.fail([self.value])
        else:
            assert self.monad(self.value).to_validation() == Validation.success(self.value)

    def test(
        self,
        run_to_box_test=True,
        run_to_maybe_test=True,
        run_to_either_test=True,
        run_to_lazy_test=True,
        run_to_try_test=True,
        run_to_validation_test=True
    ):
        if run_to_box_test:
            self.to_box_test()
        if run_to_maybe_test:
            self.to_maybe_test()
        if run_to_either_test:
            self.to_either_test()
        if run_to_lazy_test:
            self.to_lazy_test()
        if run_to_try_test:
            self.to_try_test()
        if run_to_validation_test:
            self.to_validation_test() class ApplicativeLawTester:

    def __init__(self, applicative, value, mapper1, mapper2, get_fn=identity):
        self.applicative = applicative
        self.value = value
        self.mapper1 = mapper1
        self.mapper2 = mapper2
        self.get_fn = get_fn

    def _assert(self, x, y):
        assert self.get_fn(x) == self.get_fn(y)

    def identity_test(self):
        x = self.applicative(identity).ap(self.applicative(self.value))
        y = self.applicative(self.value)

        self._assert(x, y)

    def composition_test(self):
        def lambda_fn(fn1):
            return lambda fn2: lambda value: fn1(fn2(value))

        x = self.applicative(lambda_fn)\
                .ap(self.applicative(self.mapper1))\
                .ap(self.applicative(self.mapper2))\
                .ap(self.applicative(self.value))
        y = self.applicative(self.mapper1).ap(
            self.applicative(self.mapper2).ap(self.applicative(self.value))
            )

        self._assert(x, y)

    def homomorphism_test(self):
        x = self.applicative(self.mapper1).ap(self.applicative(self.value))
        y = self.applicative(
            self.mapper1(self.value)
        )
        self._assert(x, y)

    def interchange_test(self):
        x = self.applicative(self.mapper1).ap(self.applicative(self.value))
        y = self.applicative(lambda fn: fn(self.value)).ap(
            self.applicative(self.mapper1)
        )
        self._assert(x, y)

    def test(self):
        self.identity_test()
        self.composition_test()
        self.homomorphism_test()
        self.interchange_test() class Maybe(Generic[T]):
    """
    Maybe type is the most common way of representing nothingness (or the null type).
    Maybe is effectively abstract and has two concrete subtypes: Box (also Some) and Nothing.
    """

    def __init__(self, value: T, is_nothing: bool) -> None:
        self.is_nothing = is_nothing
        if not is_nothing:
            self.value = value

    def __eq__(self, other: object) -> bool:
        return isinstance(other, Maybe) and \
            self.is_nothing == other.is_nothing and \
            (self.is_nothing or self.value == other.value)

    @classmethod
    def just(cls, value: T) -> 'Maybe[T]':
        """
        Create not empty maybe.

        :param mapper: value to store in Maybe
        :type mapper: Any
        :returns: Maybe[Any]
        """
        return Maybe(value, False)

    @classmethod
    def nothing(cls) -> 'Maybe[None]':
        """
        Create empty maybe.

        :returns: Maybe[None]
        """
        return Maybe(None, True)

    def map(self, mapper: Callable[[T], U]) -> Union['Maybe[U]', 'Maybe[None]']:
        """
        If Maybe is empty return new empty Maybe, in other case
        takes mapper function and returns new instance of Maybe
        with result of mapper.

        :param mapper: function to call with Maybe value
        :type mapper: Function(A) -> B
        :returns: Maybe[B | None]
        """
        if self.is_nothing:
            return Maybe.nothing()
        return Maybe.just(
            mapper(self.value)
        )

    def bind(self, mapper: Callable[[T], 'Maybe[U]']) -> Union['Maybe[U]', 'Maybe[None]']:
        """
        If Maybe is empty return new empty Maybe, in other case
        takes mapper function and returns result of mapper.

        :param mapper: function to call with Maybe.value
        :type mapper: Function(A) -> Maybe[B]
        :returns: Maybe[B | None]
        """
        if self.is_nothing:
            return Maybe.nothing()
        return mapper(self.value)

    def ap(self, applicative):
        """
        Applies the function inside the Maybe[A] structure to another applicative type for notempty Maybe.
        For empty returns copy of itself

        :param applicative: applicative contains function
        :type applicative: Maybe[B]
        :returns: new Maybe with result of contains function
        :rtype: Maybe[A(B) | None]
        """
        if self.is_nothing:
            return Maybe.nothing()
        return applicative.map(self.value)

    def filter(self, filterer: Callable[[T], bool]) -> Union['Maybe[T]', 'Maybe[None]']:
        """
        If Maybe is empty or filterer returns False return default_value, in other case
        return new instance of Maybe with the same value.

        :param filterer:
        :type filterer: Function(A) -> Boolean
        :returns: copy of self when filterer returns True, in other case empty Maybe
        :rtype: Maybe[A] | Maybe[None]
        """
        if self.is_nothing or not filterer(self.value):
            return Maybe.nothing()
        return Maybe.just(self.value)

    def get_or_else(self, default_value: U) -> Union[T, U]:
        """
        If Maybe is empty return default_value, in other case.

        :param default_value: value to return if Maybe is empty
        :type default_value: Any
        :returns: value of Maybe or default_value
        :rtype: A
        """
        if self.is_nothing:
            return default_value
        return self.value

    def to_either(self):
        """
        Transform Maybe to Either.

        :returns: Right monad with previous value when Maybe is not empty, in other case Left with None
        :rtype: Either[A | None]
        """
        from pymonet.either import Left, Right

        if self.is_nothing:
            return Left(None)
        return Right(self.value)

    def to_box(self):
        """
        Transform Maybe to Box.

        :returns: Box monad with previous value when Maybe is not empty, in other case Box with None
        :rtype: Box[A | None]
        """
        from pymonet.box import Box

        if self.is_nothing:
            return Box(None)
        return Box(self.value)

    def to_lazy(self):
        """
        Transform Maybe to Try.

        :returns: Lazy monad with function returning previous value in other case Left with None
        :rtype: Lazy[Function() -> (A | None)]
        """
        from pymonet.lazy import Lazy

        if self.is_nothing:
            return Lazy(lambda: None)
        return Lazy(lambda: self.value)

    def to_try(self):
        """
        Transform Maybe to Try.

        :returns: successfully Try with previous value when Maybe is not empty, othercase not successfully Try with None
        :rtype: Try[A]
        """
        from pymonet.monad_try import Try

        if self.is_nothing:
            return Try(None, is_success=False)
        return Try(self.value, is_success=True)

    def to_validation(self):
        """
        Transform Maybe into Validation.

        :returns: successfull Validation monad with previous value or None when Maybe is empty
        :rtype: Validation[A, []]
        """
        from pymonet.validation import Validation

        if self.is_nothing:
            return Validation.success(None)
        return Validation.success(self.value) class Box(Generic[T]):
    """
    Data type for storage any type of data
    """

    def __init__(self, value: T) -> None:
        """
        :param value: value to store in Box
        :type value: Any
        """
        self.value = value

    def __eq__(self, other: object) -> bool:
        return isinstance(other, Box) and self.value == other.value

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

    def map(self, mapper: Callable[[T], U]) -> 'Box[U]':
        """
        Take function (A) -> b and applied this function on current box value and returns new box with mapped value.

        :param mapper: mapper function
        :type mapper: Function(A) -> B
        :returns: new box with mapped value
        :rtype: Box[B]
        """
        return Box(mapper(self.value))

    def bind(self, mapper: Callable[[T], U]) -> U:
        """
        Take function and applied this function on current box value and returns mapped value.

        :param mapper: mapper function
        :type mapper: Function(A) -> B
        :returns: new box with mapped value
        :rtype: B
        """
        return mapper(self.value)

    def ap(self, applicative):
        """
        Applies the function inside the Box[A] structure to another applicative type.

        :param applicative: applicative contains function
        :type applicative: Box[B]
        :returns: new Box with result of contains function
        :rtype: Box[A(B)]
        """
        return applicative.map(self.value)

    def to_maybe(self):
        """
        Transform Box into not empty Maybe.

        :returns: non empty Maybe monad with previous value
        :rtype: Maybe[A]
        """
        from pymonet.maybe import Maybe

        return Maybe.just(self.value)

    def to_either(self):
        """
        Transform Box into Right either.

        :returns: right Either monad with previous value
        :rtype: Right[A]
        """
        from pymonet.either import Right

        return Right(self.value)

    def to_lazy(self):
        """
        Transform Box into Lazy with returning value function.

        :returns: not folded Lazy monad with function returning previous value
        :rtype: Lazy[Function(() -> A)]
        """
        from pymonet.lazy import Lazy

        return Lazy(lambda: self.value)

    def to_try(self):
        """
        Transform Box into successfull Try.

        :returns: successfull Try monad with previous value
        :rtype: Try[A]
        """
        from pymonet.monad_try import Try

        return Try(self.value, is_success=True)

    def to_validation(self):
        """
        Transform Box into Validation.

        :returns: successfull Validation monad with previous value
        :rtype: Validation[A, []]
        """
        from pymonet.validation import Validation

        return Validation.success(self.value) class Left(Either, Generic[T]):
    """Not successfully Either"""

    def map(self, _: Callable[[Any], Any]) -> 'Left[T]':
        """
        Take mapper function and return new instance of Left with the same value.

        :returns: Copy of self
        :rtype: Left[A]
        """
        return Left(self.value)

    def bind(self, _) -> 'Left[T]':
        """
        Take mapper function and return value of Left.

        :returns: Stored value
        :rtype: A
        """
        return self

    def ap(self, monad):
        """
        :returns: Copy of self
        :rtype: Left[A]
        """
        return Left(self.value)

    def is_left(self) -> bool:
        """
        :returns: True
        :rtype: Boolean
        """
        return True

    def is_right(self) -> bool:
        """
        :returns: False
        :rtype: Boolean
        """
        return False

    def to_maybe(self):
        """
        Transform Either to Maybe.

        :returns: Empty Maybe
        :rtype: Maybe[None]
        """
        from pymonet.maybe import Maybe

        return Maybe.nothing()

    def to_validation(self):
        """
        Transform Box into Validation.

        :returns: failed Validation monad with previous value as error
        :rtype: Validation[None, [A]]
        """
        from pymonet.validation import Validation

        return Validation.fail([self.value]) class Try:
    """
    The Try control gives us the ability write safe code
    without focusing on try-catch blocks in the presence of exceptions.
    """

    def __init__(self, value, is_success: bool) -> None:
        self.value = value
        self.is_success = is_success

    def __eq__(self, other) -> bool:
        return isinstance(other, type(self))\
            and self.value == other.value\
            and self.is_success == other.is_success

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

    @classmethod
    def of(cls, fn: Callable, *args):
        """
        Call argument function with args in try-catch.
        when function don't raise exception, not successfully when raise.

        :params fn: function to call and store in monad
        :type fn: Function(*args) -> A
        :params *args:
        :type fn: List
        :retruns: Successfully monad Try when function don't raise exception, not successfully when raise
        :rtype: Try[A]
        """
        try:
            return cls(fn(*args), True)
        except Exception as e:
            return cls(e, False)

    def map(self, mapper):
        """
        Take function and applied this function with monad value and returns new monad with mapped value.

        :params mapper: function to apply on monad value
        :type mapper: Function(A) -> B
        :returns: for successfully new Try with mapped value, othercase copy of self
        :rtype: Try[B]
        """
        if self.is_success:
            return Try(mapper(self.value), True)
        return Try(self.value, False)

    def bind(self, binder):
        """
        Take function and applied this function with monad value and returns function result.

        :params binder: function to apply on monad value
        :type binder: Function(A) -> Try[B]
        :returns: for successfully result of binder, othercase copy of self
        :rtype: Try[B]
        """
        if self.is_success:
            return binder(self.value)
        return self

    def on_success(self, success_callback):
        """
        Call success_callback function with monad value when monad is successfully.

        :params success_callback: function to apply with monad value.
        :type success_callback: Function(A)
        :returns: self
        :rtype: Try[A]
        """
        if self.is_success:
            success_callback(self.value)
        return self

    def on_fail(self, fail_callback):
        """
        Call success_callback function with monad value when monad is not successfully.

        :params fail_callback: function to apply with monad value.
        :type fail_callback: Function(A)
        :returns: self
        :rtype: Try[A]
        """
        if not self.is_success:
            fail_callback(self.value)
        return self

    def filter(self, filterer):
        """
        Take filterer function, when monad is successfully call filterer with monad value.
        When filterer returns True method returns copy of monad, othercase
        not successfully Try with previous value.

        :params filterer: function to apply on monad value
        :type filterer: Function(A) -> Boolean
        :returns: Try with previous value
        :rtype: Try[A]
        """
        if self.is_success and filterer(self.value):
            return Try(self.value, True)
        return Try(self.value, False)

    def get(self):
        """
        Return monad value.

        :returns: monad value
        :rtype: A
        """
        return self.value

    def get_or_else(self, default_value):
        """
        Return monad value when is successfully.
        Othercase return default_value argument.

        :params default_value: value to return when monad is not successfully.
        :type default_value: B
        :returns: monad value
        :rtype: A | B
        """
        if self.is_success:
            return self.value
        return default_value class Validation:
    """It that can hold either a success value or a failure value and has methods for accumulating errors"""

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

    def __eq__(self, other):
        """
        Two Validations are equals when values and errors lists are equal.
        """
        return (isinstance(other, Validation) and
                self.errors == other.errors and
                self.value == other.value)

    def __str__(self):  # pragma: no cover
        if self.is_success():
            return 'Validation.success[{}]'.format(self.value)
        return 'Validation.fail[{}, {}]'.format(self.value, self.errors)

    @classmethod
    def success(cls, value=None):
        """
        Returns successful Validation with value and empty errors list.

        :params value: value to store in Validation
        :type value: A
        :returns: Successful Validation
        :rtype: Validation[A, []]
        """
        return Validation(value, [])

    @classmethod
    def fail(cls, errors=[]):
        """
        Returns failed Validation with None as value and errors list.

        :params errors: list of errors to store
        :type value: List[E]
        :returns: Failed Validation
        :rtype: Validation[None, List[E]]
        """
        return Validation(None, errors)

    def is_success(self):
        """
        Returns True when errors list are empty.

        :returns: True for empty errors list
        :rtype: Boolean
        """
        return len(self.errors) == 0

    def is_fail(self):
        """
        Returns True when errors list are not empty.

        :returns: True for empty errors not list
        :rtype: Boolean
        """
        return len(self.errors) != 0

    def map(self, mapper):
        """
        Take function (A) -> B and applied this function on current Validation value.

        :param mapper: mapper function
        :type mapper: Function(A) -> B
        :returns: new Validation with mapped value and previous errors
        :rtype: Validation[B, List[E]]
        """
        return Validation(mapper(self.value), self.errors)

    def bind(self, folder):
        """
        Take function and applied this function on current Validation value and returns folder result.

        :param mapper: mapper function
        :type mapper: Function(A) -> Validation[B, E]
        :returns: new Validation with mapped value
        :rtype: Validation[B, E]
        """
        return folder(self.value)

    def ap(self, fn):
        """
        It takes as a parameter function returning another Validation.
        Function is called with Validation value and returns new Validation with previous value
        and concated new and old errors.

        :param monad: monad contains function
        :type monad: Function(A) -> Validation[Any, List[E]]
        :returns: new validation with stored errors
        :rtype: Validation[A, List[E]]
        """
        return Validation(self.value, self.errors + fn(self.value).errors)

    def to_either(self):
        """
        Transform Validation to Either.

        :returns: Right monad with previous value when Validation has no errors, in other case Left with errors list
        :rtype: Right[A] | Left[E]
        """
        from pymonet.either import Left, Right

        if self.is_success():
            return Right(self.value)
        return Left(self.errors)

    def to_maybe(self):
        """
        Transform Validation to Maybe.

        :returns: Maybe with Validation Value when Validation has no errors, in other case empty Maybe
        :rtype: Maybe[A | None]
        """
        from pymonet.maybe import Maybe

        if self.is_success():
            return Maybe.just(self.value)
        return Maybe.nothing()

    def to_box(self):
        """
        Transform Validation to Box.

        :returns: Box with Validation value
        :rtype: Box[A]
        """
        from pymonet.box import Box

        return Box(self.value)

    def to_lazy(self):
        """
        Transform Validation to Try.

        :returns: Lazy monad with function returning Validation value
        :rtype: Lazy[Function() -> (A | None)]
        """
        from pymonet.lazy import Lazy

        return Lazy(lambda: self.value)

    def to_try(self):
        """
        Transform Validation to Try.

        :returns: successfully Try with Validation value value. Try is successful when Validation has no errors
        :rtype: Try[A]
        """
        from pymonet.monad_try import Try

        return Try(self.value, is_success=self.is_success()) def increase(value: int) -> int:
    """
    Return increased by 1 argument.

    :param value:
    :type value: Int
    :returns:
    :rtype: Int
    """
    return value + 1
from testers.monad_law_tester import MonadLawTester
from testers.functor_law_tester import FunctorLawTester
from testers.monad_transform_tester import MonadTransformTester
from testers.applicative_law_tester import ApplicativeLawTester
from pymonet.maybe import Maybe
from pymonet.box import Box
from pymonet.either import Left
from pymonet.monad_try import Try
from pymonet.validation import Validation
from pymonet.utils import increase
from hypothesis import given
from hypothesis.strategies import integers
import pytest





class MaybeSpy:

    def mapper(self, value):
        return value + 1

    def binder(self, value):
        return Maybe.just(value + 1)


@pytest.fixture()
def maybe_spy(mocker):
    spy = MaybeSpy()
    mocker.spy(spy, 'mapper')
    mocker.spy(spy, 'binder')

    return spy


@given(integers())
def test_maybe_eq_operator_should_compare_values(integer):

    assert Maybe.just(integer) == Maybe.just(integer)
    assert Maybe.just(None) == Maybe.just(None)

    assert Maybe.just(integer) != Maybe.nothing()


@given(integers())
def test_maybe_map_operator_should_be_applied_only_on_just_value(integer):
    assert Maybe.just(42).map(increase) == Maybe.just(43)
    assert Maybe.nothing().map(increase) == Maybe.nothing()


def test_maybe_map_should_not_call_mapper_when_monad_has_nothing(maybe_spy):
    Maybe.nothing().map(maybe_spy.binder)

    assert maybe_spy.binder.call_count == 0


def test_maybe_bind_should_retrun_result_of_mapper_called_with_maybe_value(maybe_spy):
    assert Maybe.just(42).bind(increase) == 43


def test_maybe_bind_should_not_call_mapper_when_monad_has_nothing(maybe_spy):
    Maybe.nothing().bind(maybe_spy.binder)

    assert maybe_spy.binder.call_count == 0


@given(integers())
def test_maybe_get_or_else_method_should_return_maybe_value_when_monad_is_not_empty(integer):
    assert Maybe.just(integer).get_or_else(0) is integer


@given(integers())
def test_maybe_get_or_else_method_should_return_argument_when_monad_is_empty(integer):
    assert Maybe.nothing().get_or_else(integer) is integer


@given(integers())
def test_maybe_is_nothing_should_return_proper_boolean(integer):
    assert Maybe.just(integer).is_nothing is False
    assert Maybe.nothing().is_nothing is True


def test_maybe_if_filter_returns_false_method_should_return_empty_maybe():
    assert Maybe.just(41).filter(lambda value: value % 2 == 0) == Maybe.nothing()


def test_maybe_if_filter_returns_true_method_should_return_self():
    assert Maybe.just(42).filter(lambda value: value % 2 == 0) == Maybe.just(42)


@given(integers())
def test_maybe_monad_law(integer):
    MonadLawTester(
        monad=Maybe.just,
        value=integer,
        mapper1=lambda value: Maybe.just(value + 1),
        mapper2=lambda value: Maybe.just(value + 2)
    ).test()


@given(integers())
def test_maybe_functor_law(integer):
    FunctorLawTester(
        functor=Maybe.just(integer),
        mapper1=lambda value: value + 1,
        mapper2=lambda value: value + 2
    ).test()


@given(integers())
def test_maybe_transform(integer):
    MonadTransformTester(monad=Maybe.just, value=integer).test(run_to_maybe_test=False)

    assert Maybe.nothing().to_box() == Box(None)
    assert Maybe.nothing().to_either() == Left(None)
    assert Maybe.nothing().to_lazy().get() is None
    assert Maybe.nothing().to_try() == Try(None, is_success=False)

    assert Maybe.nothing().to_validation() == Validation.success(None)