import pytest

from pkgcore.restrictions import boolean, restriction

true = restriction.AlwaysBool(node_type='foo', negate=True)
false = restriction.AlwaysBool(node_type='foo', negate=False)


class AlwaysForcableBool(boolean.base):

    __slots__ = ()

    def force_True(self, action, *args):
        yield True

    match = force_False = force_True


class base:

    kls = None

    def test_invalid_restrictions(self):
        with pytest.raises(TypeError):
            self.kls(42, node_type='foo')
        base = self.kls(node_type='foo')
        with pytest.raises(TypeError):
            base.add_restriction(42)
        with pytest.raises(TypeError):
            base.add_restriction()

    def test_init_finalize(self):
        final = self.kls(true, node_type='foo', finalize=True)
        # since it becomes a tuple, throws a AttributeError
        with pytest.raises(TypeError):
            final.add_restriction(false)

        final = self.kls(true, node_type='foo')
        # since it becomes a tuple, throws a AttributeError
        with pytest.raises(TypeError):
            final.add_restriction(false)

    def test_finalize(self):
        base = self.kls(true, node_type='foo', finalize=False)
        base.add_restriction(false)
        base.finalize()
        with pytest.raises(TypeError):
            base.add_restriction(true)

    def test_change_restrictions(self):
        base = self.kls(true, false)
        assert self.kls(false, true) == base.change_restrictions(false, true)
        assert self.kls(false, true) != base.change_restrictions(false, true, negate=True)
        assert self.kls(false, true, negate=True) == base.change_restrictions(false, true, negate=True)

    def test_add_restriction(self):
        with pytest.raises(TypeError):
            self.kls(true, finalize=True).add_restriction(false)
        with pytest.raises(TypeError):
            self.kls(node_type='foon').add_restriction(false)
        k = self.kls(finalize=False)
        k.add_restriction(false)
        assert k.restrictions == [false]

    # TODO total_len? what does it do?

class TestBase(base):

    kls = boolean.base

    def test_base(self):
        base = self.kls(true, false, node_type='foo')
        assert len(base) == 2
        assert list(base) == [true, false]
        with pytest.raises(NotImplementedError):
            base.match(false)
        # TODO is the signature for these correct?
        with pytest.raises(NotImplementedError):
            base.force_False(false)
        with pytest.raises(NotImplementedError):
            base.force_True(false)
        assert base[1] is false


# TODO these tests are way too limited
class TestAndRestriction(base):

    kls = boolean.AndRestriction

    def test_match(self):
        assert self.kls(true, true, node_type='foo').match(None)
        assert not self.kls(false, true, true, node_type='foo').match(None)
        assert not self.kls(true, false, true, node_type='foo').match(None)

    def test_negate_match(self):
        assert self.kls(false, true, node_type='foo', negate=True).match(None)
        assert self.kls(true, false, node_type='foo', negate=True).match(None)
        assert self.kls(false, false, node_type='foo', negate=True).match(None)
        assert not self.kls(true, true, node_type='foo', negate=True).match(None)

    def test_dnf_solutions(self):
        assert self.kls(true, true).dnf_solutions() == [[true, true]]
        assert self.kls(self.kls(true, true), true).dnf_solutions() == [[true, true, true]]
        assert (list(map(set, self.kls(
                    true, true,
                    boolean.OrRestriction(false, true)).dnf_solutions())) ==
            [set([true, true, false]), set([true, true, true])])
        assert self.kls().dnf_solutions() == [[]]

    def test_cnf_solutions(self):
        assert self.kls(true, true).cnf_solutions() == [[true], [true]]
        assert self.kls(self.kls(true, true), true).cnf_solutions() == [[true], [true], [true]]
        assert (list(self.kls(
                    true, true,
                    boolean.OrRestriction(false, true)).cnf_solutions()) ==
            list([[true], [true], [false, true]]))
        assert not self.kls().cnf_solutions()


class TestOrRestriction(base):

    kls = boolean.OrRestriction

    def test_match(self):
        assert self.kls(true, true, node_type='foo').match(None)
        assert self.kls(false, true, false, node_type='foo').match(None)
        assert self.kls(true, false, false, node_type='foo').match(None)
        assert self.kls(false, false, true, node_type='foo').match(None)
        assert not self.kls(false, false, node_type='foo').match(None)

    def test_negate_match(self):
        for x in ((true, false), (false, true), (true, true)):
            assert not self.kls(node_type='foo', negate=True, *x).match(None)
        assert self.kls(false, false, node_type='foo', negate=True).match(None)

    def test_dnf_solutions(self):
        assert self.kls(true, true).dnf_solutions() == [[true], [true]]
        assert (list(map(set, self.kls(
                    true, true,
                    boolean.AndRestriction(false, true)).dnf_solutions())) ==
            list(map(set, [[true], [true], [false, true]])))
        assert self.kls(self.kls(true, false), true).dnf_solutions() == [[true], [false], [true]]
        assert self.kls().dnf_solutions() == [[]]

    def test_cnf_solutions(self):
        assert self.kls(true, true).cnf_solutions() == [[true, true]]
        assert ([set(x) for x in self.kls(
                    true, true,
                    boolean.AndRestriction(false, true)).cnf_solutions()] ==
            [set(x) for x in [[true, false], [true, true]]])

        assert ([set(x) for x in self.kls(self.kls(
                        true, true,
                        boolean.AndRestriction(false, true))).cnf_solutions()] ==
            [set(x) for x in [[true, false], [true, true]]])

        assert (set(self.kls(
                    self.kls(true, false),
                    true).cnf_solutions()[0]) ==
            set([true, false, true]))

        assert not self.kls().cnf_solutions()


class TestJustOneRestriction(base):

    kls = boolean.JustOneRestriction

    def test_match(self):
        assert self.kls(true, false, node_type='foo').match(None)
        assert self.kls(false, true, false, node_type='foo').match(None)
        assert not self.kls(false, false,  node_type='foo').match(None)
        assert not self.kls(true, false, true, node_type='foo').match(None)
        assert not self.kls(true, true, true, node_type='foo').match(None)
