'''
Test properties attached to a widget
'''

import unittest
import pytest
from kivy.event import EventDispatcher
from functools import partial


class _TestProperty(EventDispatcher):
    pass


wid = _TestProperty()


@pytest.fixture(autouse=True)
def set_clock(kivy_clock):
    pass


@pytest.fixture()
def self():
    return unittest.TestCase()


@pytest.mark.parametrize('set_name', [True, False])
def test_base(self, set_name):
    from kivy.properties import Property

    a = Property(-1)
    if set_name:
        a.set_name(wid, 'a')
        a.link_eagerly(wid)
    else:
        a.link(wid, 'a')
        a.link_deps(wid, 'a')
    self.assertEqual(a.get(wid), -1)
    a.set(wid, 0)
    self.assertEqual(a.get(wid), 0)
    a.set(wid, 1)
    self.assertEqual(a.get(wid), 1)


@pytest.mark.parametrize('set_name', [True, False])
def test_observer(self, set_name):
    from kivy.properties import Property

    a = Property(-1)
    if set_name:
        a.set_name(wid, 'a')
        a.link_eagerly(wid)
    else:
        a.link(wid, 'a')
        a.link_deps(wid, 'a')
    self.assertEqual(a.get(wid), -1)
    global observe_called
    observe_called = 0

    def observe(obj, value):
        global observe_called
        observe_called = 1
    a.bind(wid, observe)

    a.set(wid, 0)
    self.assertEqual(a.get(wid), 0)
    self.assertEqual(observe_called, 1)
    observe_called = 0
    a.set(wid, 0)
    self.assertEqual(a.get(wid), 0)
    self.assertEqual(observe_called, 0)
    a.set(wid, 1)
    self.assertEqual(a.get(wid), 1)
    self.assertEqual(observe_called, 1)


@pytest.mark.parametrize('set_name', [True, False])
def test_objectcheck(self, set_name):
    from kivy.properties import ObjectProperty

    a = ObjectProperty(False)
    if set_name:
        a.set_name(wid, 'a')
        a.link_eagerly(wid)
    else:
        a.link(wid, 'a')
        a.link_deps(wid, 'a')
    self.assertEqual(a.get(wid), False)
    a.set(wid, True)
    self.assertEqual(a.get(wid), True)


@pytest.mark.parametrize('set_name', [True, False])
def test_stringcheck(self, set_name):
    from kivy.properties import StringProperty

    a = StringProperty()
    if set_name:
        a.set_name(wid, 'a')
        a.link_eagerly(wid)
    else:
        a.link(wid, 'a')
        a.link_deps(wid, 'a')
    self.assertEqual(a.get(wid), '')
    a.set(wid, 'hello')
    self.assertEqual(a.get(wid), 'hello')

    try:
        a.set(wid, 88)  # number shouldn't be accepted
        self.fail('string accept number, fail.')
    except ValueError:
        pass


@pytest.mark.parametrize('set_name', [True, False])
def test_numericcheck(self, set_name):
    from kivy.properties import NumericProperty

    a = NumericProperty()
    if set_name:
        a.set_name(wid, 'a')
        a.link_eagerly(wid)
    else:
        a.link(wid, 'a')
        a.link_deps(wid, 'a')
    self.assertEqual(a.get(wid), 0)
    a.set(wid, 99)
    self.assertEqual(a.get(wid), 99)

    # try:
    #    a.set(wid, '')  # string shouldn't be accepted
    #    self.fail('number accept string, fail.')
    # except ValueError:
    #    pass


@pytest.mark.parametrize('set_name', [True, False])
def test_listcheck(self, set_name):
    from kivy.properties import ListProperty

    a = ListProperty()
    if set_name:
        a.set_name(wid, 'a')
        a.link_eagerly(wid)
    else:
        a.link(wid, 'a')
        a.link_deps(wid, 'a')
    self.assertEqual(a.get(wid), [])
    a.set(wid, [1, 2, 3])
    self.assertEqual(a.get(wid), [1, 2, 3])


@pytest.mark.parametrize('set_name', [True, False])
def test_dictcheck(self, set_name):
    from kivy.properties import DictProperty

    a = DictProperty()
    if set_name:
        a.set_name(wid, 'a')
        a.link_eagerly(wid)
    else:
        a.link(wid, 'a')
        a.link_deps(wid, 'a')
    self.assertEqual(a.get(wid), {})
    a.set(wid, {'foo': 'bar'})
    self.assertEqual(a.get(wid), {'foo': 'bar'})


@pytest.mark.parametrize('set_name', [True, False])
def test_propertynone(self, set_name):
    from kivy.properties import NumericProperty

    a = NumericProperty(0, allownone=True)
    if set_name:
        a.set_name(wid, 'a')
        a.link_eagerly(wid)
    else:
        a.link(wid, 'a')
        a.link_deps(wid, 'a')
    self.assertEqual(a.get(wid), 0)
    try:
        a.set(wid, None)
        self.assertEqual(a.get(wid), None)
    except ValueError:
        pass
    a.set(wid, 1)
    self.assertEqual(a.get(wid), 1)


@pytest.mark.parametrize('set_name', [True, False])
def test_reference(self, set_name):
    from kivy.properties import NumericProperty, ReferenceListProperty

    x = NumericProperty(0)
    if set_name:
        x.set_name(wid, 'x')
        x.link_eagerly(wid)
    else:
        x.link(wid, 'x')
        x.link_deps(wid, 'x')
    y = NumericProperty(0)
    if set_name:
        y.set_name(wid, 'y')
        y.link_eagerly(wid)
    else:
        y.link(wid, 'y')
        y.link_deps(wid, 'y')
    pos = ReferenceListProperty(x, y)
    if set_name:
        pos.set_name(wid, 'pos')
        pos.link_eagerly(wid)
    else:
        pos.link(wid, 'pos')
        pos.link_deps(wid, 'pos')

    self.assertEqual(x.get(wid), 0)
    self.assertEqual(y.get(wid), 0)
    self.assertEqual(pos.get(wid), [0, 0])

    x.set(wid, 50)
    self.assertEqual(pos.get(wid), [50, 0])

    y.set(wid, 50)
    self.assertEqual(pos.get(wid), [50, 50])

    pos.set(wid, [0, 0])
    self.assertEqual(pos.get(wid), [0, 0])
    self.assertEqual(x.get(wid), 0)
    self.assertEqual(y.get(wid), 0)

    # test observer
    global observe_called
    observe_called = 0

    def observe(obj, value):
        global observe_called
        observe_called = 1
    pos.bind(wid, observe)

    self.assertEqual(observe_called, 0)
    x.set(wid, 99)
    self.assertEqual(observe_called, 1)


@pytest.mark.parametrize('set_name', [True, False])
def test_reference_child_update(self, set_name):
    from kivy.properties import NumericProperty, ReferenceListProperty

    x = NumericProperty(0)
    if set_name:
        x.set_name(wid, 'x')
        x.link_eagerly(wid)
    else:
        x.link(wid, 'x')
        x.link_deps(wid, 'x')
    y = NumericProperty(0)
    if set_name:
        y.set_name(wid, 'y')
        y.link_eagerly(wid)
    else:
        y.link(wid, 'y')
        y.link_deps(wid, 'y')
    pos = ReferenceListProperty(x, y)
    if set_name:
        pos.set_name(wid, 'pos')
        pos.link_eagerly(wid)
    else:
        pos.link(wid, 'pos')
        pos.link_deps(wid, 'pos')

    pos.get(wid)[0] = 10
    self.assertEqual(pos.get(wid), [10, 0])

    pos.get(wid)[:] = (20, 30)
    self.assertEqual(pos.get(wid), [20, 30])


@pytest.mark.parametrize('set_name', [True, False])
def test_dict(self, set_name):
    from kivy.properties import DictProperty

    x = DictProperty()
    if set_name:
        x.set_name(wid, 'x')
        x.link_eagerly(wid)
    else:
        x.link(wid, 'x')
        x.link_deps(wid, 'x')

    # test observer
    global observe_called
    observe_called = 0

    def observe(obj, value):
        global observe_called
        observe_called = 1

    x.bind(wid, observe)

    observe_called = 0
    x.get(wid)['toto'] = 1
    self.assertEqual(observe_called, 1)

    observe_called = 0
    x.get(wid)['toto'] = 2
    self.assertEqual(observe_called, 1)

    observe_called = 0
    x.get(wid)['youupi'] = 2
    self.assertEqual(observe_called, 1)

    observe_called = 0
    del x.get(wid)['toto']
    self.assertEqual(observe_called, 1)

    observe_called = 0
    x.get(wid).update({'bleh': 5})
    self.assertEqual(observe_called, 1)


@pytest.mark.parametrize('set_name', [True, False])
def test_bounded_numeric_property(self, set_name):
    from kivy.properties import BoundedNumericProperty

    bnp = BoundedNumericProperty(0.0, min=0.0, max=3.5)

    if set_name:
        bnp.set_name(wid, 'bnp')
        bnp.link_eagerly(wid)
    else:
        bnp.link(wid, 'bnp')
        bnp.link_deps(wid, 'bnp')

    bnp.set(wid, 1)
    bnp.set(wid, 0.0)
    bnp.set(wid, 3.1)
    bnp.set(wid, 3.5)
    self.assertRaises(ValueError, partial(bnp.set, wid, 3.6))
    self.assertRaises(ValueError, partial(bnp.set, wid, -3))


@pytest.mark.parametrize('set_name', [True, False])
def test_bounded_numeric_property_error_value(self, set_name):
    from kivy.properties import BoundedNumericProperty

    bnp = BoundedNumericProperty(0, min=-5, max=5, errorvalue=1)
    if set_name:
        bnp.set_name(wid, 'bnp')
        bnp.link_eagerly(wid)
    else:
        bnp.link(wid, 'bnp')
        bnp.link_deps(wid, 'bnp')

    bnp.set(wid, 1)
    self.assertEqual(bnp.get(wid), 1)

    bnp.set(wid, 5)
    self.assertEqual(bnp.get(wid), 5)

    bnp.set(wid, 6)
    self.assertEqual(bnp.get(wid), 1)

    bnp.set(wid, -5)
    self.assertEqual(bnp.get(wid), -5)

    bnp.set(wid, -6)
    self.assertEqual(bnp.get(wid), 1)


@pytest.mark.parametrize('set_name', [True, False])
def test_bounded_numeric_property_error_handler(self, set_name):
    from kivy.properties import BoundedNumericProperty

    bnp = BoundedNumericProperty(
        0, min=-5, max=5,
        errorhandler=lambda x: 5 if x > 5 else -5)

    if set_name:
        bnp.set_name(wid, 'bnp')
        bnp.link_eagerly(wid)
    else:
        bnp.link(wid, 'bnp')
        bnp.link_deps(wid, 'bnp')

    bnp.set(wid, 1)
    self.assertEqual(bnp.get(wid), 1)

    bnp.set(wid, 5)
    self.assertEqual(bnp.get(wid), 5)

    bnp.set(wid, 10)
    self.assertEqual(bnp.get(wid), 5)

    bnp.set(wid, -5)
    self.assertEqual(bnp.get(wid), -5)

    bnp.set(wid, -10)
    self.assertEqual(bnp.get(wid), -5)


@pytest.mark.parametrize('set_name', [True, False])
def test_numeric_string_with_units_check(self, set_name):
    from kivy.properties import NumericProperty
    from kivy.metrics import Metrics

    a = NumericProperty()
    if set_name:
        a.set_name(wid, 'a')
        a.link_eagerly(wid)
    else:
        a.link(wid, 'a')
        a.link_deps(wid, 'a')
    self.assertEqual(a.get(wid), 0)

    a.set(wid, '55dp')
    density = Metrics.density
    self.assertEqual(a.get(wid), 55 * density)
    self.assertEqual(a.get_format(wid), 'dp')

    a.set(wid, u'55dp')
    self.assertEqual(a.get(wid), 55 * density)
    self.assertEqual(a.get_format(wid), 'dp')

    a.set(wid, '99in')
    self.assertEqual(a.get(wid), 9504.0 * density)
    self.assertEqual(a.get_format(wid), 'in')

    a.set(wid, u'99in')
    self.assertEqual(a.get(wid), 9504.0 * density)
    self.assertEqual(a.get_format(wid), 'in')


@pytest.mark.parametrize('set_name', [True, False])
def test_numeric_string_without_units(self, set_name):
    from kivy.properties import NumericProperty

    a = NumericProperty()
    if set_name:
        a.set_name(wid, 'a')
        a.link_eagerly(wid)
    else:
        a.link(wid, 'a')
        a.link_deps(wid, 'a')
    self.assertEqual(a.get(wid), 0)

    a.set(wid, '2')
    self.assertEqual(a.get(wid), 2)


def test_property_rebind(self):
    from kivy.uix.label import Label
    from kivy.uix.togglebutton import ToggleButton
    from kivy.lang import Builder
    from kivy.properties import ObjectProperty, DictProperty, AliasProperty
    from kivy.clock import Clock

    class ObjWidget(Label):
        button = ObjectProperty(None, rebind=True, allownone=True)

    class ObjWidgetRebindFalse(Label):
        button = ObjectProperty(None, rebind=False, allownone=True)

    class DictWidget(Label):
        button = DictProperty({'button': None}, rebind=True,
                              allownone=True)

    class DictWidgetFalse(Label):
        button = DictProperty({'button': None}, rebind=False)

    class AliasWidget(Label):
        _button = None

        def setter(self, value):
            self._button = value
            return True

        def getter(self):
            return self._button
        button = AliasProperty(getter, setter, rebind=True)

    Builder.load_string('''
<ObjWidget>:
    text: self.button.state if self.button is not None else 'Unset'

<ObjWidgetRebindFalse>:
    text: self.button.state if self.button is not None else 'Unset'

<AliasWidget>:
    text: self.button.state if self.button is not None else 'Unset'

<DictWidget>:
    text: self.button.button.state if self.button.button is not None\
 else 'Unset'

<DictWidgetFalse>:
    text: self.button.button.state if self.button.button is not None\
 else 'Unset'
''')

    obj = ObjWidget()
    obj_false = ObjWidgetRebindFalse()
    dict_rebind = DictWidget()
    dict_false = DictWidgetFalse()
    alias_rebind = AliasWidget()
    button = ToggleButton()
    Clock.tick()
    self.assertEqual(obj.text, 'Unset')
    self.assertEqual(obj_false.text, 'Unset')
    self.assertEqual(dict_rebind.text, 'Unset')
    self.assertEqual(dict_false.text, 'Unset')
    self.assertEqual(alias_rebind.text, 'Unset')

    obj.button = button
    obj_false.button = button
    dict_rebind.button.button = button
    dict_false.button.button = button
    alias_rebind.button = button
    Clock.tick()
    self.assertEqual(obj.text, 'normal')
    self.assertEqual(obj_false.text, 'normal')
    self.assertEqual(dict_rebind.text, 'normal')
    self.assertEqual(dict_false.text, 'Unset')
    self.assertEqual(alias_rebind.text, 'normal')

    button.state = 'down'
    Clock.tick()
    self.assertEqual(obj.text, 'down')
    self.assertEqual(obj_false.text, 'normal')
    self.assertEqual(dict_rebind.text, 'down')
    self.assertEqual(dict_false.text, 'Unset')
    self.assertEqual(alias_rebind.text, 'down')

    button.state = 'normal'
    Clock.tick()
    self.assertEqual(obj.text, 'normal')
    self.assertEqual(obj_false.text, 'normal')
    self.assertEqual(dict_rebind.text, 'normal')
    self.assertEqual(dict_false.text, 'Unset')
    self.assertEqual(alias_rebind.text, 'normal')

    obj.button = None
    obj_false.button = None
    dict_rebind.button.button = None
    dict_false.button.button = None
    alias_rebind.button = None
    Clock.tick()
    self.assertEqual(obj.text, 'Unset')
    self.assertEqual(obj_false.text, 'Unset')
    self.assertEqual(dict_rebind.text, 'Unset')
    self.assertEqual(dict_false.text, 'Unset')
    self.assertEqual(alias_rebind.text, 'Unset')


@pytest.mark.parametrize('set_name', [True, False])
def test_color_property(self, set_name):
    from kivy.properties import ColorProperty

    color = ColorProperty()
    if set_name:
        color.set_name(wid, 'color')
        color.link_eagerly(wid)
    else:
        color.link(wid, 'color')
        color.link_deps(wid, 'color')
    self.assertEqual(color.get(wid), [1, 1, 1, 1])

    color2 = ColorProperty()
    if set_name:
        color2.set_name(wid, 'color2')
        color2.link_eagerly(wid)
    else:
        color2.link(wid, 'color2')
        color2.link_deps(wid, 'color2')
    self.assertEqual(color2.get(wid), [1, 1, 1, 1])

    color.set(wid, 'yellow')
    self.assertEqual(color.get(wid), [1.0, 1.0, 0.0, 1.0])

    color.set(wid, "#00ff00")
    self.assertEqual(color.get(wid), [0, 1, 0, 1])

    color.set(wid, "#7f7fff7f")
    self.assertEqual(color.get(wid)[0], 127 / 255.)
    self.assertEqual(color.get(wid)[1], 127 / 255.)
    self.assertEqual(color.get(wid)[2], 1)
    self.assertEqual(color.get(wid)[3], 127 / 255.)

    color.set(wid, (1, 1, 0))
    self.assertEqual(color.get(wid), [1, 1, 0, 1])
    color.set(wid, (1, 1, 0, 0))
    self.assertEqual(color.get(wid), [1, 1, 0, 0])

    color.set(wid, [1, 1, 1, 1])
    color_value = color.get(wid)
    color_value[0] = 0.5
    self.assertEqual(color.get(wid), [0.5, 1, 1, 1])

    self.assertEqual(color2.get(wid), [1, 1, 1, 1])
    color2.set(wid, color.get(wid))
    self.assertEqual(color2.get(wid), [0.5, 1, 1, 1])

    color.set(wid, [1, 1, 1, 1])
    color_value = color.get(wid)
    color_value[:] = [0, 1, 0, 1]
    self.assertEqual(color.get(wid), [0, 1, 0, 1])


@pytest.mark.parametrize('watch_before_use', [True, False])
def test_alias_property_without_setter(self, watch_before_use):
    from kivy.properties import AliasProperty

    expected_value = 5

    class CustomAlias(EventDispatcher):

        def _get_prop(self):
            self.getter_called += 1
            return expected_value

        prop = AliasProperty(_get_prop, None, watch_before_use=watch_before_use)

        def __init__(self, **kwargs):
            super(CustomAlias, self).__init__(**kwargs)
            self.getter_called = 0

    # Initial checks
    wid = CustomAlias()
    self.assertEqual(wid.getter_called, 0)

    # Get value, should call getter once
    value = wid.prop
    self.assertEqual(value, expected_value)
    self.assertEqual(wid.getter_called, 1)

    # Setter should raise an AttributeError
    self.assertRaises(AttributeError, partial(setattr, wid, 'prop', 1))


@pytest.mark.parametrize('watch_before_use', [True, False])
def test_alias_property(self, watch_before_use):
    from kivy.properties import AliasProperty

    class CustomAlias(EventDispatcher):

        def _get_prop(self):
            self.getter_called += 1

        def _set_prop(self, value):
            self.setter_called += 1

        prop = AliasProperty(
            _get_prop, _set_prop, watch_before_use=watch_before_use)

        def __init__(self, **kwargs):
            super(CustomAlias, self).__init__(**kwargs)
            self.getter_called = 0
            self.setter_called = 0
            self.callback_called = 0

    def callback(widget, value):
        widget.callback_called += 1

    # Initial checks
    wid = CustomAlias()
    wid.bind(prop=callback)
    self.assertEqual(wid.getter_called, 0)
    self.assertEqual(wid.setter_called, 0)
    self.assertEqual(wid.callback_called, 0)

    # Set property, should call setter to set the value
    # Getter and callback should not be called because `_set_prop` doesn't
    # returns True
    wid.prop = 1
    self.assertEqual(wid.getter_called, 0)
    self.assertEqual(wid.setter_called, 1)
    self.assertEqual(wid.callback_called, 0)

    # Set property to same value as before, should only call setter
    wid.prop = 1
    self.assertEqual(wid.getter_called, 0)
    self.assertEqual(wid.setter_called, 2)
    self.assertEqual(wid.callback_called, 0)

    # Get value of the property, should call getter once
    self.assertEqual(wid.prop, None)
    self.assertEqual(wid.getter_called, 1)
    self.assertEqual(wid.setter_called, 2)
    self.assertEqual(wid.callback_called, 0)


@pytest.mark.parametrize('watch_before_use', [True, False])
def test_alias_property_cache_true(self, watch_before_use):
    from kivy.properties import AliasProperty

    expected_value = 5

    class CustomAlias(EventDispatcher):

        def _get_prop(self):
            self.getter_called += 1
            return expected_value

        def _set_prop(self, value):
            self.setter_called += 1
            return True

        prop = AliasProperty(
            _get_prop, _set_prop, cache=True, watch_before_use=watch_before_use)

        def __init__(self, **kwargs):
            super(CustomAlias, self).__init__(**kwargs)
            self.getter_called = 0
            self.setter_called = 0

    # Initial checks
    wid = CustomAlias()
    self.assertEqual(wid.getter_called, 0)
    self.assertEqual(wid.setter_called, 0)

    # Get value of the property, should call getter once
    value = wid.prop
    self.assertEqual(value, expected_value)
    self.assertEqual(wid.getter_called, 1)
    self.assertEqual(wid.setter_called, 0)

    # Get value of the property, should return cached value
    # Getter should not be called
    value = wid.prop
    self.assertEqual(value, expected_value)
    self.assertEqual(wid.getter_called, 1)
    self.assertEqual(wid.setter_called, 0)

    # Set value of property, should call getter and setter
    wid.prop = 10
    value = wid.prop
    self.assertEqual(value, expected_value)
    self.assertEqual(wid.setter_called, 1)
    self.assertEqual(wid.getter_called, 2)


@pytest.mark.parametrize('watch_before_use', [True, False])
def test_alias_property_with_bind(self, watch_before_use):
    from kivy.properties import NumericProperty, AliasProperty

    class CustomAlias(EventDispatcher):

        x = NumericProperty(0)
        width = NumericProperty(100)

        def get_right(self):
            return self.x + self.width

        def set_right(self, value):
            self.x = value - self.width

        right = AliasProperty(
            get_right, set_right, bind=('x', 'width'),
            watch_before_use=watch_before_use)

        def __init__(self, **kwargs):
            super(CustomAlias, self).__init__(**kwargs)
            self.callback_called = 0

    # Assert values when setting x, width or right properties
    wid = CustomAlias()
    self.assertEqual(wid.right, 100)
    wid.x = 500
    self.assertEqual(wid.right, 600)
    wid.width = 50
    self.assertEqual(wid.right, 550)
    wid.right = 100
    self.assertEqual(wid.width, 50)
    self.assertEqual(wid.x, 50)

    def callback(widget, value):
        widget.callback_called += 1

    wid.bind(right=callback)

    # Callback should be called only when property changes
    wid.x = 100
    self.assertEqual(wid.callback_called, 1)
    wid.x = 100
    self.assertEqual(wid.callback_called, 1)
    wid.width = 900
    self.assertEqual(wid.callback_called, 2)
    wid.right = 700
    self.assertEqual(wid.callback_called, 3)
    wid.right = 700
    self.assertEqual(wid.callback_called, 3)


@pytest.mark.parametrize('watch_before_use', [True, False])
def test_alias_property_with_force_dispatch_true(self, watch_before_use):
    from kivy.properties import AliasProperty

    class CustomAlias(EventDispatcher):

        def _get_prop(self):
            self.getter_called += 1

        def _set_prop(self, value):
            self.setter_called += 1

        prop = AliasProperty(
            _get_prop, _set_prop, force_dispatch=True,
            watch_before_use=watch_before_use)

        def __init__(self, **kwargs):
            super(CustomAlias, self).__init__(**kwargs)
            self.getter_called = 0
            self.setter_called = 0
            self.callback_called = 0

    def callback(widget, value):
        widget.callback_called += 1

    # Initial checks
    wid = CustomAlias()
    wid.bind(prop=callback)
    self.assertEqual(wid.getter_called, 0)
    self.assertEqual(wid.setter_called, 0)
    self.assertEqual(wid.callback_called, 0)

    # Set property, should call setter to set the value and getter to
    # to get the value for dispatch call
    wid.prop = 1
    self.assertEqual(wid.getter_called, 1)
    self.assertEqual(wid.setter_called, 1)
    self.assertEqual(wid.callback_called, 1)

    # Set property to same value as before, setter and getter and callback
    # are called
    wid.prop = 1
    self.assertEqual(wid.getter_called, 2)
    self.assertEqual(wid.setter_called, 2)
    self.assertEqual(wid.callback_called, 2)


@pytest.mark.parametrize('watch_before_use', [True, False])
def test_alias_property_cache_true_with_bind(self, watch_before_use):
    from kivy.properties import NumericProperty, AliasProperty

    class CustomAlias(EventDispatcher):

        base_value = NumericProperty(1)

        def _get_prop(self):
            self.getter_called += 1
            return self.base_value * 2

        def _set_prop(self, value):
            self.base_value = value / 2

        prop = AliasProperty(_get_prop, _set_prop,
                             bind=('base_value',),
                             cache=True, watch_before_use=watch_before_use)

        def __init__(self, **kwargs):
            super(CustomAlias, self).__init__(**kwargs)
            self.getter_called = 0

    # Initial checks
    wid = CustomAlias()
    self.assertEqual(wid.getter_called, 0)
    self.assertEqual(wid.base_value, 1)
    self.assertEqual(wid.getter_called, 0)

    # Change the base value, should trigger an update for the cache
    wid.base_value = 4
    self.assertEqual(wid.getter_called, int(watch_before_use))

    # Now read the value again, should use the cache
    self.assertEqual(wid.prop, 8)
    self.assertEqual(wid.getter_called, 1)

    # Change the prop itself, should trigger an update for the cache
    wid.prop = 4
    self.assertEqual(wid.getter_called, 2)
    self.assertEqual(wid.base_value, 2)
    self.assertEqual(wid.prop, 4)
    self.assertEqual(wid.getter_called, 2)


@pytest.mark.parametrize('watch_before_use', [True, False])
def test_alias_property_cache_true_force_dispatch_true(self, watch_before_use):
    from kivy.properties import AliasProperty

    class CustomAlias(EventDispatcher):

        def _get_prop(self):
            self.getter_called += 1
            return self.base_value * 2

        def _set_prop(self, value):
            self.setter_called += 1
            self.base_value = value / 2
            return True

        prop = AliasProperty(
            _get_prop, _set_prop, cache=True, force_dispatch=True,
            watch_before_use=watch_before_use)

        def __init__(self, **kwargs):
            super(CustomAlias, self).__init__(**kwargs)
            self.base_value = 1
            self.getter_called = 0
            self.setter_called = 0
            self.callback_called = 0

    def callback(widget, value):
        widget.callback_called += 1

    wid = CustomAlias()
    wid.bind(prop=callback)

    # Initial checks
    self.assertEqual(wid.base_value, 1)
    self.assertEqual(wid.getter_called, 0)
    self.assertEqual(wid.setter_called, 0)
    self.assertEqual(wid.callback_called, 0)

    # Set alias property some value, should call setter and then getter to
    # pass the value to callback
    wid.prop = 16
    self.assertEqual(wid.base_value, 8)
    self.assertEqual(wid.getter_called, 1)
    self.assertEqual(wid.setter_called, 1)
    self.assertEqual(wid.callback_called, 1)

    # Same as the step above, should call setter, getter and callback
    wid.prop = 16
    self.assertEqual(wid.base_value, 8)
    self.assertEqual(wid.getter_called, 2)
    self.assertEqual(wid.setter_called, 2)
    self.assertEqual(wid.callback_called, 2)

    # Get the value of property, should use cached value
    value = wid.prop
    self.assertEqual(value, 16)
    self.assertEqual(wid.getter_called, 2)
    self.assertEqual(wid.setter_called, 2)
    self.assertEqual(wid.callback_called, 2)


def test_dictproperty_is_none():
    from kivy.properties import DictProperty

    d1 = DictProperty(None)
    d1.set_name(wid, 'd1')
    d1.link_eagerly(wid)
    assert d1.get(wid) is None

    d2 = DictProperty({'a': 1, 'b': 2}, allownone=True)
    d2.set_name(wid, 'd2')
    d2.link_eagerly(wid)
    d2.set(wid, None)
    assert d2.get(wid) is None


def test_listproperty_is_none():
    from kivy.properties import ListProperty

    l1 = ListProperty(None)
    l1.set_name(wid, 'l1')
    l1.link_eagerly(wid)
    assert l1.get(wid) is None

    l2 = ListProperty([1, 2, 3], allownone=True)
    l2.set_name(wid, 'l2')
    l2.link_eagerly(wid)
    l2.set(wid, None)
    assert l2.get(wid) is None


def test_numeric_property_dp(kivy_metrics):
    from kivy.event import EventDispatcher
    from kivy.properties import NumericProperty
    kivy_metrics.density = 1

    class Number(EventDispatcher):

        with_dp = NumericProperty(5)

        no_dp = NumericProperty(10)

        default_dp = NumericProperty('10dp')

    number = Number()
    counter = {'with_dp': 0, 'no_dp': 0, 'default_dp': 0}

    def callback(name, *args):
        counter[name] += 1

    number.fbind('with_dp', callback, 'with_dp')
    number.fbind('no_dp', callback, 'no_dp')
    number.fbind('default_dp', callback, 'default_dp')

    assert not counter['with_dp']
    assert not counter['no_dp']
    assert not counter['default_dp']
    assert number.with_dp == 5
    assert number.no_dp == 10
    assert number.default_dp == 10

    number.with_dp = 10
    assert counter['with_dp'] == 1
    assert number.with_dp == 10

    kivy_metrics.density = 2

    assert counter['with_dp'] == 1
    assert not counter['no_dp']
    assert counter['default_dp'] == 1
    assert number.with_dp == 10
    assert number.no_dp == 10
    assert number.default_dp == 20

    number.with_dp = '20dp'
    number.no_dp = 20

    assert counter['with_dp'] == 2
    assert counter['no_dp'] == 1
    assert counter['default_dp'] == 1
    assert number.with_dp == 40
    assert number.no_dp == 20
    assert number.default_dp == 20

    kivy_metrics.density = 1

    assert counter['with_dp'] == 3
    assert counter['no_dp'] == 1
    assert counter['default_dp'] == 2
    assert number.with_dp == 20
    assert number.no_dp == 20
    assert number.default_dp == 10


def test_variable_list_property_dp_default(kivy_metrics):
    from kivy.event import EventDispatcher
    from kivy.properties import VariableListProperty
    kivy_metrics.density = 1

    class Number(EventDispatcher):

        a = VariableListProperty(['10dp', (20, 'dp'), 3, 4.0])

    number = Number()
    counter = 0

    def callback(name, *args):
        nonlocal counter
        counter += 1

    number.fbind('a', callback)
    assert list(number.a) == [10, 20, 3, 4]
    assert not counter

    kivy_metrics.density = 2

    assert counter == 1
    assert list(number.a) == [20, 40, 3, 4]

    kivy_metrics.density = 1

    assert counter == 2
    assert list(number.a) == [10, 20, 3, 4]


def test_variable_list_property_dp(kivy_metrics):
    from kivy.event import EventDispatcher
    from kivy.properties import VariableListProperty
    kivy_metrics.density = 1

    class Number(EventDispatcher):

        a = VariableListProperty([0, 20, 3, 4])

    number = Number()
    counter = 0

    def callback(name, *args):
        nonlocal counter
        counter += 1

    number.fbind('a', callback)
    assert list(number.a) == [0, 20, 3, 4]
    assert not counter

    number.a = ['10dp', (20, 'dp'), 3, 4.0]
    assert list(number.a) == [10, 20, 3, 4]
    assert counter == 1

    kivy_metrics.density = 2

    assert counter == 2
    assert list(number.a) == [20, 40, 3, 4]

    kivy_metrics.density = 1

    assert counter == 3
    assert list(number.a) == [10, 20, 3, 4]


def test_property_duplicate_name():
    from kivy.event import EventDispatcher
    from kivy.properties import ObjectProperty

    class Event(EventDispatcher):

        a = ObjectProperty(5)

    event = Event()
    counter = 0
    counter2 = 0

    def callback(*args):
        nonlocal counter
        counter += 1

    def callback2(*args):
        nonlocal counter2
        counter2 += 1

    event.fbind('a', callback)

    event.create_property('a', None)
    event.fbind('a', callback2)

    event.a = 12
    assert not counter
    assert counter2 == 1


def test_property_rename_duplicate():
    from kivy.event import EventDispatcher
    from kivy.properties import ObjectProperty

    class Event(EventDispatcher):

        b = ObjectProperty(5)
        a = b

    event = Event()
    counter = 0
    counter2 = 0

    def callback(*args):
        nonlocal counter
        counter += 1

    def callback2(*args):
        nonlocal counter2
        counter2 += 1

    event.fbind('a', callback)
    event.fbind('b', callback2)

    event.a = 12
    assert counter == 1
    assert counter2 == 1
    assert event.a == 12
    assert event.b == 12

    event.b = 14
    assert counter == 2
    assert counter2 == 2
    assert event.a == 14
    assert event.b == 14


def test_override_prop_inheritance():
    from kivy.event import EventDispatcher
    from kivy.properties import ObjectProperty, AliasProperty
    counter = 0

    class Parent(EventDispatcher):

        prop = ObjectProperty()

    class Child(Parent):

        def inc(self, *args):
            nonlocal counter
            counter += 1
            return counter

        prop = AliasProperty(inc)

    parent = Parent()
    child = Child()

    parent.prop = 44
    assert parent.prop == 44
    assert counter == 0
    assert child.prop == 1
    assert counter == 1
    assert parent.prop == 44
    assert isinstance(parent.property('prop'), ObjectProperty)
    assert isinstance(child.property('prop'), AliasProperty)


@pytest.mark.parametrize('by_val', [True, False])
def test_manually_create_property(by_val):
    from kivy.event import EventDispatcher
    from kivy.properties import StringProperty

    class Event(EventDispatcher):
        pass

    event = Event()
    assert not hasattr(event, 'a')
    if by_val:
        event.create_property('a', 'hello')
    else:
        event.apply_property(a=StringProperty('hello'))
    args = 0

    def callback(obj, val):
        nonlocal args
        args = obj, val

    event.fbind('a', callback)
    assert event.a == 'hello'
    event.a = 'bye'
    assert event.a == 'bye'
    assert args == (event, 'bye')

    event2 = Event()
    assert event2.a == 'hello'
    event2.fbind('a', callback)
    event2.a = 'goodbye'
    assert event2.a == 'goodbye'
    assert args == (event2, 'goodbye')


def test_inherit_property():
    from kivy.event import EventDispatcher
    from kivy.properties import StringProperty

    class Event(EventDispatcher):

        a = StringProperty('hello')

    class Event2(Event):

        b = StringProperty('hello2')

    event = Event2()
    args = 0

    def callback(obj, val):
        nonlocal args
        args = obj, val

    event.fbind('a', callback)
    event.fbind('b', callback)
    assert event.a == 'hello'
    assert event.b == 'hello2'

    event.a = 'bye'
    assert event.a == 'bye'
    assert args == (event, 'bye')

    event.b = 'goodbye'
    assert event.b == 'goodbye'
    assert args == (event, 'goodbye')


def test_unknown_property():
    from kivy.properties import NumericProperty

    class MyWidget(EventDispatcher):
        width = NumericProperty(0)

    with pytest.raises(TypeError) as cm:
        MyWidget(width=12, unkn="abc")
    assert "Properties ['unkn'] passed to __init__ may not be existing " \
           "property names. Valid properties are ['width']" \
           == str(cm.value)


def test_known_property_multiple_inheritance():

    class Behavior:
        def __init__(self, name):
            print(f'Behavior: {self}, name={name}')
            super().__init__()

    class Widget2(Behavior, EventDispatcher):
        pass

    class Widget3(EventDispatcher, Behavior):
        pass

    with pytest.raises(TypeError) as cm:
        EventDispatcher(name='Pasta')
    assert "Properties ['name'] passed to __init__ may not be existing" \
           in str(cm.value)

    Widget2(name='Pasta')  # does not raise a ValueError
    Widget3(name='Pasta')  # does not raise a ValueError


def test_pass_other_typeerror():

    class Behavior:
        def __init__(self, name):
            super().__init__()
            raise TypeError("this is a typeerror unrelated to object")

    class Widget2(Behavior, EventDispatcher):
        pass

    class Widget3(EventDispatcher, Behavior):
        pass

    for cls in [Widget2, Widget3]:
        with pytest.raises(TypeError) as cm:
            cls(name='Pasta')
        assert "this is a typeerror unrelated to object" == str(cm.value)


def test_object_init_error():  # the above 3 test rely on this
    class TestCls(object):
        def __init__(self, **kwargs):
            super(TestCls, self).__init__(**kwargs)

    with pytest.raises(TypeError) as cm:
        TestCls(name='foo')
    assert str(cm.value).startswith("object.__init__() takes")
