import re
import unittest

import param
import pytest

from .utils import check_defaults
# TODO: I copied the tests from testobjectselector, although I
# struggled to understand some of them. Both files should be reviewed
# and cleaned up together.

# TODO: tests copied from testobjectselector could use assertRaises
# context manager (and could be updated in testobjectselector too).

class TestListSelectorParameters(unittest.TestCase):

    def setUp(self):
        super().setUp()
        class P(param.Parameterized):
            e = param.ListSelector(default=[5],objects=[5,6,7])
            f = param.ListSelector(default=[10])
            h = param.ListSelector(default=None)
            g = param.ListSelector(default=None,objects=[7,8])
            i = param.ListSelector(default=[7],objects=[9],check_on_set=False)
            j = param.ListSelector(objects=[11], check_on_set=False, allow_None=True)

        self.P = P

    def _check_defaults(self, p):
        assert p.default is None
        assert p.allow_None is None
        assert p.objects == []
        assert p.compute_default_fn is None
        assert p.check_on_set is False
        assert p.names == {}

    def test_defaults_class(self):
        class P(param.Parameterized):
            s = param.ListSelector()

        check_defaults(P.param.s, label='S')
        self._check_defaults(P.param.s)

    def test_defaults_inst(self):
        class P(param.Parameterized):
            s = param.ListSelector()

        p = P()

        check_defaults(p.param.s, label='S')
        self._check_defaults(p.param.s)

    def test_defaults_unbound(self):
        s = param.ListSelector()

        check_defaults(s, label=None)
        self._check_defaults(s)

    def test_default_None(self):
        class Q(param.Parameterized):
            r = param.ListSelector(default=None)

    def test_set_object_constructor(self):
        p = self.P(e=[6])
        self.assertEqual(p.e, [6])

    def test_allow_None_is_None(self):
        p = self.P()
        assert p.param.e.allow_None is None
        assert p.param.f.allow_None is None
        assert p.param.g.allow_None is None
        assert p.param.h.allow_None is None
        assert p.param.i.allow_None is None


    def test_set_object_outside_bounds(self):
        p = self.P(e=[6])
        try:
            p.e = [9]
        except ValueError:
            pass
        else:
            raise AssertionError("Object set outside range.")

    def test_set_object_setattr(self):
        p = self.P(e=[6])
        p.f = [9]
        assert p.f == [9]
        assert p.param.f.objects == [10, 9]
        assert self.P.param.f.objects == [10]
        p.g = [7]
        assert p.g == [7]
        assert p.param.g.objects == self.P.param.g.objects
        assert p.param.g.objects == [7, 8]
        p.i = [12]
        assert p.i == [12]
        assert p.param.i.objects == [9, 7, 12]
        assert self.P.param.i.objects == [9, 7]

    def test_set_object_not_None(self):
        p = self.P(e=[6])
        p.g = [7]
        try:
            p.g = None
        except ValueError:
            pass
        else:
            raise AssertionError("Object set outside range.")

    def test_set_one_object_not_None(self):
        p = self.P(e=[6])
        p.g = [7]
        try:
            p.g = [None]
        except ValueError:
            pass
        else:
            raise AssertionError("Object set outside range.")

    def test_set_one_object_allow_None_check_on_set(self):
        p = self.P(e=[6])
        p.j = None
        assert p.j is None
        assert p.param.j.objects == [11]
        p.j = [12]
        assert p.j == [12]
        assert p.param.j.objects == [11, 12]

    def test_set_object_setattr_post_error(self):
        p = self.P(e=[6])
        p.f = [9]
        self.assertEqual(p.f, [9])
        p.g = [7]
        try:
            p.g = [None]
        except ValueError:
            pass
        else:
            raise AssertionError("Object set outside range.")

        self.assertEqual(p.g, [7])
        p.i = [12]
        self.assertEqual(p.i, [12])

    def test_initialization_out_of_bounds(self):
        try:
            class Q(param.Parameterized):
                q = param.ListSelector([5],objects=[4])
        except ValueError:
            pass
        else:
            raise AssertionError("ListSelector created outside range.")


    def test_initialization_no_bounds(self):
        try:
            class Q(param.Parameterized):
                q = param.ListSelector([5],objects=10)
        except TypeError:
            pass
        else:
            raise AssertionError("ListSelector created without range.")

    def test_bad_default(self):
        with pytest.raises(
            ValueError,
            match=re.escape("ListSelector parameter 'r' only takes list types, not 6."),
        ):
            class Q(param.Parameterized):
                r = param.ListSelector(default=6,check_on_set=True)

    def test_implied_check_on_set(self):
        with pytest.raises(
            ValueError,
            match=re.escape("ListSelector parameter 'r' only takes list types, not 7."),
        ):
            class Q(param.Parameterized):
                r = param.ListSelector(default=7,objects=[7,8])

    def test_default_not_checked(self):
        class Q(param.Parameterized):
            r = param.ListSelector(default=[6])

    def test_default_not_checked_to_be_iterable(self):
        with pytest.raises(
            ValueError,
            match=re.escape("ListSelector parameter 'r' only takes list types, not 6."),
        ):
            class Q(param.Parameterized):
                r = param.ListSelector(default=6)

    def test_set_checked_to_be_iterable(self):
        class Q(param.Parameterized):
            r = param.ListSelector(default=[6],check_on_set=False)

        with self.assertRaises(ValueError):
            Q.r = 6

    def test_compute_default(self):
        class Q(param.Parameterized):
            r = param.ListSelector(default=None, compute_default_fn=lambda: [1,2,3])

        self.assertEqual(Q.r, None)
        Q.param['r'].compute_default()
        self.assertEqual(Q.r, [1,2,3])
        self.assertEqual(Q.param['r'].objects, [1,2,3])

    def test_bad_compute_default(self):
        class Q(param.Parameterized):
            r = param.ListSelector(default=None,compute_default_fn=lambda:1)

        with self.assertRaises(TypeError):
            Q.param['r'].compute_default()

    def test_initialization_bad_iterable(self):
        with self.assertRaises(ValueError):
            class Q(param.Parameterized):
                j = param.ListSelector('ab', objects=['a', 'b', 'c', 'd'])

    def test_set_bad_iterable(self):
        class Q(param.Parameterized):
            r = param.ListSelector(objects=['a', 'b', 'c', 'd'])

        q = Q()
        with self.assertRaises(ValueError):
            q.r = 'ab'
