# Copyright (c) 2008-2011 testtools developers. See LICENSE for details.

from typing import ClassVar

from testtools import TestCase
from testtools.matchers import (
    DocTestMatches,
    Equals,
    LessThan,
    MatchesStructure,
    Mismatch,
    NotEquals,
)
from testtools.matchers._higherorder import (
    AfterPreprocessing,
    AllMatch,
    Annotate,
    AnnotatedMismatch,
    AnyMatch,
    MatchesAll,
    MatchesAny,
    MatchesPredicate,
    MatchesPredicateWithParams,
    Not,
)

from ..helpers import FullStackRunTest
from ..matchers.helpers import TestMatchersInterface


class TestAllMatch(TestCase, TestMatchersInterface):
    matches_matcher: ClassVar = AllMatch(LessThan(10))
    matches_matches: ClassVar = [
        [9, 9, 9],
        (9, 9),
        iter([9, 9, 9, 9, 9]),
    ]
    matches_mismatches: ClassVar = [
        [11, 9, 9],
        iter([9, 12, 9, 11]),
    ]

    str_examples: ClassVar = [
        ("AllMatch(LessThan(12))", AllMatch(LessThan(12))),
    ]

    describe_examples: ClassVar = [
        (
            "Differences: [\n11 >= 10\n10 >= 10\n]",
            [11, 9, 10],
            AllMatch(LessThan(10)),
        ),
    ]


class TestAnyMatch(TestCase, TestMatchersInterface):
    matches_matcher: ClassVar = AnyMatch(Equals("elephant"))
    matches_matches: ClassVar = [
        ["grass", "cow", "steak", "milk", "elephant"],
        (13, "elephant"),
        ["elephant", "elephant", "elephant"],
        {"hippo", "rhino", "elephant"},
    ]
    matches_mismatches: ClassVar = [
        [],
        ["grass", "cow", "steak", "milk"],
        (13, 12, 10),
        ["element", "hephalump", "pachyderm"],
        {"hippo", "rhino", "diplodocus"},
    ]

    str_examples: ClassVar = [
        ("AnyMatch(Equals('elephant'))", AnyMatch(Equals("elephant"))),
    ]

    describe_examples: ClassVar = [
        (
            "Differences: [\n11 != 7\n9 != 7\n10 != 7\n]",
            [11, 9, 10],
            AnyMatch(Equals(7)),
        ),
    ]


def parity(x):
    return x % 2


class TestAfterPreprocessing(TestCase, TestMatchersInterface):
    matches_matcher: ClassVar = AfterPreprocessing(parity, Equals(1))
    matches_matches: ClassVar = [3, 5]
    matches_mismatches: ClassVar = [2]

    str_examples: ClassVar = [
        (
            "AfterPreprocessing(<function parity>, Equals(1))",
            AfterPreprocessing(parity, Equals(1)),
        ),
    ]

    describe_examples: ClassVar = [
        (
            "0 != 1: after <function parity> on 2",
            2,
            AfterPreprocessing(parity, Equals(1)),
        ),
        ("0 != 1", 2, AfterPreprocessing(parity, Equals(1), annotate=False)),
    ]


class TestMatchersAnyInterface(TestCase, TestMatchersInterface):
    matches_matcher: ClassVar = MatchesAny(DocTestMatches("1"), DocTestMatches("2"))
    matches_matches: ClassVar = ["1", "2"]
    matches_mismatches: ClassVar = ["3"]

    str_examples: ClassVar = [
        (
            "MatchesAny(DocTestMatches('1\\n'), DocTestMatches('2\\n'))",
            MatchesAny(DocTestMatches("1"), DocTestMatches("2")),
        ),
    ]

    describe_examples: ClassVar = [
        (
            """Differences: [
Expected:
    1
Got:
    3

Expected:
    2
Got:
    3

]""",
            "3",
            MatchesAny(DocTestMatches("1"), DocTestMatches("2")),
        )
    ]


class TestMatchesAllInterface(TestCase, TestMatchersInterface):
    matches_matcher: ClassVar = MatchesAll(NotEquals(1), NotEquals(2))
    matches_matches: ClassVar = [3, 4]
    matches_mismatches: ClassVar = [1, 2]

    str_examples: ClassVar = [
        (
            "MatchesAll(NotEquals(1), NotEquals(2))",
            MatchesAll(NotEquals(1), NotEquals(2)),
        )
    ]

    describe_examples: ClassVar = [
        (
            """Differences: [
1 == 1
]""",
            1,
            MatchesAll(NotEquals(1), NotEquals(2)),
        ),
        (
            "1 == 1",
            1,
            MatchesAll(NotEquals(2), NotEquals(1), Equals(3), first_only=True),
        ),
    ]


class TestAnnotate(TestCase, TestMatchersInterface):
    matches_matcher: ClassVar = Annotate("foo", Equals(1))
    matches_matches: ClassVar = [1]
    matches_mismatches: ClassVar = [2]

    str_examples: ClassVar = [
        ("Annotate('foo', Equals(1))", Annotate("foo", Equals(1)))
    ]

    describe_examples: ClassVar = [("2 != 1: foo", 2, Annotate("foo", Equals(1)))]

    def test_if_message_no_message(self):
        # Annotate.if_message returns the given matcher if there is no
        # message.
        matcher = Equals(1)
        not_annotated = Annotate.if_message("", matcher)
        self.assertIs(matcher, not_annotated)

    def test_if_message_given_message(self):
        # Annotate.if_message returns an annotated version of the matcher if a
        # message is provided.
        matcher = Equals(1)
        expected = Annotate("foo", matcher)
        annotated = Annotate.if_message("foo", matcher)
        self.assertThat(
            annotated, MatchesStructure.fromExample(expected, "annotation", "matcher")
        )


class TestAnnotatedMismatch(TestCase):
    run_tests_with = FullStackRunTest

    def test_forwards_details(self):
        x = Mismatch("description", {"foo": "bar"})
        annotated = AnnotatedMismatch("annotation", x)
        self.assertEqual(x.get_details(), annotated.get_details())


class TestNotInterface(TestCase, TestMatchersInterface):
    matches_matcher: ClassVar = Not(Equals(1))
    matches_matches: ClassVar = [2]
    matches_mismatches: ClassVar = [1]

    str_examples: ClassVar = [
        ("Not(Equals(1))", Not(Equals(1))),
        ("Not(Equals('1'))", Not(Equals("1"))),
    ]

    describe_examples: ClassVar = [("1 matches Equals(1)", 1, Not(Equals(1)))]


def is_even(x):
    return x % 2 == 0


class TestMatchesPredicate(TestCase, TestMatchersInterface):
    matches_matcher: ClassVar = MatchesPredicate(is_even, "%s is not even")
    matches_matches: ClassVar = [2, 4, 6, 8]
    matches_mismatches: ClassVar = [3, 5, 7, 9]

    str_examples: ClassVar = [
        (
            "MatchesPredicate({!r}, {!r})".format(is_even, "%s is not even"),
            MatchesPredicate(is_even, "%s is not even"),
        ),
    ]

    describe_examples: ClassVar = [
        ("7 is not even", 7, MatchesPredicate(is_even, "%s is not even")),
    ]


def between(x, low, high):
    return low < x < high


class TestMatchesPredicateWithParams(TestCase, TestMatchersInterface):
    matches_matcher: ClassVar = MatchesPredicateWithParams(
        between, "{0} is not between {1} and {2}"
    )(1, 9)
    matches_matches: ClassVar = [2, 4, 6, 8]
    matches_mismatches: ClassVar = [0, 1, 9, 10]

    str_examples: ClassVar = [
        (
            "MatchesPredicateWithParams({!r}, {!r})({})".format(
                between, "{0} is not between {1} and {2}", "1, 2"
            ),
            MatchesPredicateWithParams(between, "{0} is not between {1} and {2}")(1, 2),
        ),
        (
            "Between(1, 2)",
            MatchesPredicateWithParams(
                between, "{0} is not between {1} and {2}", "Between"
            )(1, 2),
        ),
    ]

    describe_examples: ClassVar = [
        (
            "1 is not between 2 and 3",
            1,
            MatchesPredicateWithParams(between, "{0} is not between {1} and {2}")(2, 3),
        ),
    ]


def test_suite():
    from unittest import TestLoader

    return TestLoader().loadTestsFromName(__name__)
