from functools import partial

import pytest

from pkgcore.restrictions import values

from .utils import TestRestriction


class SillyBool(values.base):
    """Extra stupid version of AlwaysBool to test base.force_{True,False}."""

    def __init__(self, negate=False):
        object.__setattr__(self, "negate", negate)

    def match(self, something):
        return not self.negate


class TestBase(TestRestriction):
    def test_force(self):
        self.assertMatches(SillyBool(negate=False), None, [None] * 3)
        self.assertNotMatches(SillyBool(negate=True), None, [None] * 3)


class TestGetAttr(TestRestriction):
    """Test bits of GetAttrRestriction that differ from PackageRestriction."""

    def test_force(self):
        # TODO we could do with both more tests and a more powerful
        # force_* implementation. This just tests if the function
        # takes the right number of arguments.

        # TODO test negate handling
        succeeds = values.GetAttrRestriction("test", values.AlwaysTrue)
        fails = values.GetAttrRestriction("test", values.AlwaysFalse)

        class Dummy:
            test = True

        dummy = Dummy()

        class FakePackage:
            """XXX this is vastly too minimal."""

            value = dummy

        pkg = FakePackage()

        args = [pkg, "value", dummy]
        self.assertForceTrue(succeeds, args)
        self.assertNotForceFalse(succeeds, args)
        self.assertNotForceTrue(fails, args)
        self.assertForceFalse(fails, args)


class TestStrRegex(TestRestriction):
    kls = values.StrRegex

    @pytest.mark.parametrize("negated", (True, False))
    def test_match(self, negated):
        self.assertMatches(
            self.kls("foo.*r", match=True, negate=negated),
            "foobar",
            [None, None, "foobar"],
            negated=negated,
        )
        self.assertNotMatches(
            self.kls("foo.*r", match=True, negate=negated),
            "afoobar",
            [None, None, "afoobar"],
            negated=negated,
        )

    @pytest.mark.parametrize("negated", (True, False))
    def test_search(self, negated):
        self.assertMatches(
            self.kls("foo.*r", negate=negated),
            "asdfoobar",
            [None, None, "asdfoobar"],
            negated=negated,
        )
        self.assertNotMatches(
            self.kls("foo.*r", negate=negated),
            "afobar",
            [None, None, "afobar"],
            negated=negated,
        )

    def test_case_sensitivity(self):
        self.assertNotMatches(self.kls("foo"), "FoO", ["FOo"] * 3)
        self.assertMatches(self.kls("foo", False), "FoO", ["fOo"] * 3)

    def test_str(self):
        assert "search spork" == str(self.kls("spork"))
        assert "not search spork" == str(self.kls("spork", negate=True))
        assert "match spork" == str(self.kls("spork", match=True))
        assert "not match spork" == str(self.kls("spork", match=True, negate=True))

    def test_repr(self):
        for restr, string in (
            (self.kls("spork"), "<StrRegex 'spork' search @"),
            (self.kls("spork", match=True), "<StrRegex 'spork' match @"),
            (self.kls("spork", negate=True), "<StrRegex 'spork' negated search @"),
        ):
            assert repr(restr).startswith(string), (restr, string)


class TestStrExactMatch(TestRestriction):
    if values.StrExactMatch is values.StrExactMatch:
        kls = values.StrExactMatch
    else:

        class kls(values.StrExactMatch, values.base):
            __slots__ = ()

            intersect = values._StrExact_intersect
            __repr__ = values._StrExact__repr__
            __str__ = values._StrExact__str__

    kls = staticmethod(kls)

    @pytest.mark.parametrize("negated", (True, False))
    def test_case_sensitive(self, negated):
        self.assertMatches(
            self.kls("package", negate=negated),
            "package",
            ["package"] * 3,
            negated=negated,
        )
        self.assertNotMatches(
            self.kls("Package", negate=negated),
            "package",
            ["package"] * 3,
            negated=negated,
        )

    @pytest.mark.parametrize("negated", (True, False))
    def test_case_insensitive(self, negated):
        # note that we explicitly test True/1, and False/0
        # we test 1/0, since bool protocol is supported for those kwds-
        # thus we verify it, more specifically we verify the cpy
        # support.
        self.assertMatches(
            self.kls("package", case_sensitive=True, negate=negated),
            "package",
            ["package"] * 3,
            negated=negated,
        )
        self.assertMatches(
            self.kls("package", case_sensitive=1, negate=negated),
            "package",
            ["package"] * 3,
            negated=negated,
        )
        self.assertMatches(
            self.kls("Package", case_sensitive=False, negate=negated),
            "package",
            ["package"] * 3,
            negated=negated,
        )
        self.assertMatches(
            self.kls("Package", case_sensitive=0, negate=negated),
            "package",
            ["package"] * 3,
            negated=negated,
        )

    @pytest.mark.parametrize("negate", (True, False))
    def test__eq__(self, negate):
        assert self.kls("rsync", negate=negate) == self.kls("rsync", negate=negate)
        for x in "Ca":
            assert self.kls("rsync", negate=negate) != self.kls(
                "rsyn" + x, negate=negate
            )
        assert self.kls("Rsync", case_sensitive=False, negate=negate) == self.kls(
            "rsync", case_sensitive=False, negate=negate
        )


class TestStrGlobMatch(TestRestriction):
    kls = values.StrGlobMatch

    @pytest.mark.parametrize("negated", (True, False))
    def test_matching(self, negated):
        self.assertMatches(
            self.kls("pack", negate=negated),
            "package",
            ["package"] * 3,
            negated=negated,
        )
        self.assertNotMatches(
            self.kls("pack", negate=negated), "apack", ["apack"] * 3, negated=negated
        )
        # case sensitive...
        self.assertMatches(
            self.kls("pAcK", case_sensitive=False, negate=negated),
            "pack",
            ["pack"] * 3,
            negated=negated,
        )
        self.assertNotMatches(
            self.kls("pAcK", case_sensitive=True, negate=negated),
            "pack",
            ["pack"] * 3,
            negated=negated,
        )

        # check prefix.
        self.assertMatches(
            self.kls("pAck", case_sensitive=False, prefix=True, negate=negated),
            "packa",
            ["packa"] * 3,
            negated=negated,
        )

        self.assertNotMatches(
            self.kls("pack", prefix=False, negate=negated),
            "apacka",
            ["apacka"] * 3,
            negated=negated,
        )

        self.assertMatches(
            self.kls("pack", prefix=False, negate=negated),
            "apack",
            ["apack"] * 3,
            negated=negated,
        )

        # daft, but verifies it's not doing contains.
        self.assertNotMatches(
            self.kls("pack", prefix=False, negate=negated),
            "apacka",
            ["apacka"] * 3,
            negated=negated,
        )

        self.assertNotMatches(
            self.kls("pack", prefix=False, case_sensitive=False, negate=negated),
            "aPacka",
            ["aPacka"] * 3,
            negated=negated,
        )

    def test__eq__(self):
        assert self.kls("rsync", prefix=False) != self.kls("rsync", prefix=True)
        for negate in (True, False):
            assert self.kls("rsync", negate=negate) == self.kls("rsync", negate=negate)
            for x in "Ca":
                assert self.kls("rsync", negate=negate) != self.kls(
                    "rsyn" + x, negate=negate
                )
            assert self.kls("Rsync", case_sensitive=False, negate=negate) != self.kls(
                "rsync", case_sensitive=True, negate=negate
            )
            assert self.kls("rsync", case_sensitive=False, negate=negate) != self.kls(
                "rsync", case_sensitive=True, negate=negate
            )
            assert self.kls("rsync", case_sensitive=False, negate=negate) != self.kls(
                "rsync", case_sensitive=True, negate=not negate
            )
        assert self.kls("rsync", negate=True) != self.kls("rsync", negate=False)


class TestEqualityMatch(TestRestriction):
    kls = staticmethod(values.EqualityMatch)

    @pytest.mark.parametrize("negated", (True, False))
    @pytest.mark.parametrize(
        ("x", "y", "ret"),
        (
            ("asdf", "asdf", True),
            ("asdf", "fdsa", False),
            (1, 1, True),
            (1, 2, False),
            (tuple(range(2)), tuple(range(2)), True),
            (tuple(range(2)), tuple(reversed(range(2))), False),
            (True, True, True),
            (True, False, False),
            (False, True, False),
        ),
    )
    def test_match(self, x, y, ret, negated):
        self.assertMatches(
            self.kls(x, negate=negated), y, [y] * 3, negated=(ret ^ (not negated))
        )

    def test__eq__(self):
        for negate in (True, False):
            assert self.kls("asdf", negate=negate) == self.kls("asdf", negate=negate)
            assert self.kls(1, negate=negate) != self.kls(2, negate=negate)
        assert self.kls("asdf", negate=True) != self.kls("asdf", negate=False)

    def test__hash__(self):
        def f(*args, **kwds):
            return hash(self.kls(*args, **kwds))

        assert f("dar") == f("dar")
        assert f("dar") == f("dar", negate=False)
        assert f("dar", negate=True) != f("dar", negate=False)
        assert f("dar", negate=True) == f("dar", negate=True)
        assert f("dar") != f("dar2")
        assert f("dar", negate=True) != f("dar2")


class TestContainmentMatch(TestRestriction):
    kls = staticmethod(partial(values.ContainmentMatch, disable_inst_caching=True))

    def test_match(self):
        for x, y, ret in (
            (tuple(range(10)), tuple(range(10)), True),
            (tuple(range(10)), [], False),
            (tuple(range(10)), set(range(10)), True),
            (set(range(10)), tuple(range(10)), True),
        ):
            for negated in (False, True):
                self.assertMatches(
                    self.kls(x, negate=negated, disable_inst_caching=True),
                    y,
                    [y] * 3,
                    negated=(ret == negated),
                )

        for negated in (False, True):
            # intentionally differing for the force_* args; slips in
            # an extra data set for testing.
            self.assertMatches(
                self.kls(range(10), match_all=True, negate=negated),
                tuple(range(20)),
                [tuple(range(10))] * 3,
                negated=negated,
            )
            self.assertNotMatches(
                self.kls(range(10), match_all=True, negate=negated),
                tuple(range(5)),
                [tuple(range(5))] * 3,
                negated=negated,
            )

        self.assertNotMatches(self.kls("asdf"), "fdsa", ["fdas"] * 3)
        self.assertMatches(self.kls("asdf"), "asdf", ["asdf"] * 3)
        self.assertMatches(self.kls("asdf"), "asdffdsa", ["asdffdsa"] * 3)
        self.assertMatches(self.kls("b"), "aba", ["aba"] * 3)

    @pytest.mark.parametrize("negate", (True, False))
    def test__eq__(self, negate):
        assert self.kls(range(100), negate=negate) == self.kls(
            range(100), negate=negate
        ), f"range(100), negate={negate}"
        assert self.kls("1", negate=not negate) != self.kls("1", negate=negate)
        assert self.kls((1, 2, 3), match_all=True, negate=negate) == self.kls(
            (1, 2, 3), match_all=True, negate=negate
        )
        assert self.kls((1, 2), match_all=True, negate=negate) != self.kls(
            (1, 2, 3), match_all=True, negate=negate
        )
        assert self.kls([1, 2, 3], match_all=False, negate=negate) != self.kls(
            [1, 2, 3], match_all=True, negate=negate
        )


class TestFlatteningRestriction:
    @pytest.mark.parametrize("negate", (True, False))
    def test_basic(self, negate):
        inst = values.FlatteningRestriction(
            tuple, values.AnyMatch(values.EqualityMatch(None)), negate=negate
        )
        assert negate != inst.match([7, 8, [9, None]])
        assert negate == inst.match([7, 8, (9, None)])
        # Just check this does not raise
        assert str(inst)
        assert repr(inst)


class TestFunctionRestriction:
    @pytest.mark.parametrize("negate", (True, False))
    def test_basic(self, negate):
        def yes(val):
            return True

        def no(val):
            return False

        yes_restrict = values.FunctionRestriction(yes, negate=negate)
        no_restrict = values.FunctionRestriction(no, negate=negate)
        assert negate != yes_restrict.match(7)
        assert negate == no_restrict.match(7)
        for restrict in yes_restrict, no_restrict:
            # Just check this does not raise
            assert str(restrict)
            assert repr(restrict)


class TestAnyMatch:
    # Most of AnyMatch is tested through test_restriction.

    def test_force(self):
        restrict = values.AnyMatch(values.AlwaysTrue)
        assert restrict.force_True(None, None, tuple(range(2)))
        assert not restrict.force_False(None, None, tuple(range(2)))
