import unittest

from elisa.core.utils.bindable import Bindable, NotBoundError, NotBindable

class Dummy(object):
    pass


class TestSequenceFunctions(unittest.TestCase):
    
    def test_sub_binding(self):
        """
        test if the path binding on sub objects works with binding system
        """

        src = Bindable()
        dest = Dummy()
        my_first = Dummy()
        my_second = Dummy()

        # Set up the attribute
        dest.attr = my_first
        src.a = 1
        src.bind('a', dest, 'attr.subtest')
        self.assertEquals(dest.attr, my_first)
        self.assertEquals(dest.attr.subtest, 1)

        # Change it
        src.a = 36
        self.assertEquals(dest.attr, my_first)
        self.assertEquals(dest.attr.subtest, 36)
        self.assertEquals(my_first.subtest, 36)

        # Change the attributes value of dest
        dest.attr = my_second
        # FIXME: we don't expect my_second to now have the value, do we?
        # self.assertEquals(my_second.subtest, 36)

        src.a = 42
        self.assertEquals(dest.attr, my_second)
        self.assertEquals(dest.attr.subtest, 42)
        self.assertEquals(my_second.subtest, 42)
        self.assertEquals(my_first.subtest, 36)

        # Unbind it
        src.unbind('a', dest, 'attr.subtest')
        dest.attr = None
        src.a = 0

        # test for object without attribute
        dest.none = None
        src.bind('c', dest, 'none.c')
        self.assertRaises(AttributeError, setattr, src,'c',3)

        # test deleting of sub-element
        dest.sub = my_first
        src.bind('d', dest, 'sub.d')
        src.d = 5
        self.assertEquals(my_first.d, 5)
        del src.d

        self.assertRaises(AttributeError, getattr, my_first, 'd')

        src.unbind('d', dest, 'sub.d')

        # delete of deleted sub-element
        dest.sub = my_second
        src.f = 5
        src.bind('f', dest, 'sub.f')
        self.assertEquals(my_second.f, 5)
        src.f = 9
        self.assertEquals(my_second.f, 9)
        del my_second.f
        src.f = 10
        self.assertEquals(my_second.f, 10)

        del my_second.f
        self.assertRaises(AttributeError, delattr, src, 'f')

    def test_bind_private(self):
        """
        try to bind to a private attribute
        """
        src = Bindable()
        self.assertRaises(NotBindable,
                          src.bind,
                          '_test', ' ',  ' ')


    def test_bind_one_to_one(self):
        """
        test whether binding one attribute to one attribute works
        """
        src = Bindable()
        dest = Dummy()

        # previously existing attribute
        src.a = 1
        src.bind("a", dest, "da")
        self.assertEqual(dest.da, 1)
        src.a = 36
        self.assertEqual(dest.da, 36)

        # non existing attribute
        src.bind("b", dest, "db")
        self.assertRaises(AttributeError, getattr, dest, "db")
        src.b = 2
        self.assertEqual(dest.db, 2)
        src.b = 42
        self.assertEqual(dest.db, 42)

        # attribute deletion
        src.bind("c", dest, "dc")
        self.assertRaises(AttributeError, getattr, dest, "dc")
        src.c = 3
        del src.c
        self.assertRaises(AttributeError, getattr, src, "c")
        self.assertRaises(AttributeError, getattr, dest, "dc")

        # binding a non existing attribute to an existing one: the existing
        # one should get deleted
        dest.dd = "I exist"
        src.bind("d", dest, "dd")
        self.assertRaises(AttributeError, getattr, src, "dd")
        self.assertRaises(AttributeError, getattr, dest, "dd")


    def test_bind_one_to_many(self):
        """
        test whether binding one attribute to many objects works
        """
        src = Bindable()
        dests = [Dummy() for x in xrange(5)]

        # previously existing attribute
        src.a = 1
        for dest in dests:
            src.bind("a", dest, "da")
            self.assertEqual(dest.da, 1)
        src.a = 36
        for dest in dests:
            self.assertEqual(dest.da, 36)

        # non existing attribute
        for dest in dests:
            src.bind("b", dest, "db")
            self.assertRaises(AttributeError, getattr, dest, "db")
            self.assertEqual(dest.da, 36)
        src.b = 2
        for dest in dests:
            self.assertEqual(dest.db, 2)
        src.b = 42
        for dest in dests:
            self.assertEqual(dest.db, 42)

        # attribute deletion
        for dest in dests:
            src.bind("c", dest, "dc")
            self.assertRaises(AttributeError, getattr, dest, "dc")
        src.c = 3
        del src.c
        self.assertRaises(AttributeError, getattr, src, "c")
        for dest in dests:
            self.assertRaises(AttributeError, getattr, dest, "dc")

    def test_unbind(self):
        """
        test the unbinding
        """
        src = Bindable()
        dest = Dummy()

        # binding
        src.bind("a", dest, "da")
        src.a = 1
        self.assertEqual(dest.da, 1)

        # unbinding
        src.unbind("a", dest, "da")

        # modify source attribute
        src.a = 2
        self.assertEqual(dest.da, 1)

        # delete source attribute
        del src.a
        self.assertEqual(dest.da, 1)

        # test exceptions
        self.assertRaises(NotBoundError, src.unbind, 'd', dest, 'da')

        src.bind('a', dest, 'da')
        self.assertRaises(NotBoundError, src.unbind, 'a', dest, 'db')


    def test_delete(self):
        """
        test what happens on deleting
        """
        src = Bindable()
        dest = Dummy()

        # bind it
        src.bind('a', dest, 'da')
        src.a = 1
        self.assertEqual(dest.da, 1)

        del src.a
        self.assertRaises(AttributeError, getattr, dest, 'da')

        src.b = 4
        del src.b

        src.bind('c', dest, 'ct')
        src.c = 4
        self.assertEquals(dest.ct, 4)

        del dest.ct
        self.assertRaises(AttributeError, delattr, src, 'c')

        self.assertRaises(AttributeError, getattr, dest, 'ct')
        src.unbind('c', dest, 'ct')

    def test_multi_delete(self):
        """
        test if deleting of attributes on multiple bindings works as expected
        """

        src = Bindable()
        dest1 = Dummy()
        dest2 = Dummy()
        sub_1 = Dummy()

        dest1.a = sub_1

        src.bind('a', dest1, 'a.a')
        src.bind('a', dest1, 'b')
        src.bind('a', dest2, 'a')

        src.a = 1

        self.assertEquals(dest1.b, 1)
        self.assertEquals(sub_1.a, 1)
        self.assertEquals(dest2.a, 1)

        del dest1.b

        self.assertRaises(AttributeError, delattr, src, 'a')

        self.assertRaises(AttributeError, getattr, dest1, 'b')
        self.assertRaises(AttributeError, getattr, sub_1, 'a')
        self.assertRaises(AttributeError, getattr, dest2, 'b')

        src.a = 1
        self.assertEquals(dest1.b, 1)
        self.assertEquals(sub_1.a, 1)
        self.assertEquals(dest2.a, 1)

        dest1.a = None

        self.assertRaises(AttributeError, delattr, src, 'a')
        
        # deep bind test
        dest1.c = dest2
        dest2.c = sub_1

        src.bind('c', dest1, 'c.c.c')
        src.bind('c', dest1, 'd')
        src.c = 4
        self.assertEquals(sub_1.c, 4)
        self.assertEquals(dest1.d, 4)

        del src.c
        self.assertRaises(AttributeError, getattr, sub_1, 'c')
        self.assertRaises(AttributeError, getattr, dest1, 'd')

        src.c = 5
        self.assertEquals(sub_1.c, 5)
        self.assertEquals(dest1.d, 5)

        dest1.c = None

        self.assertRaises(AttributeError, delattr, src, 'c')
        
    def test_midleobject_missing(self):
        """
        test whether that all updates on the updates later in a chain are done
        even if one binding path is missing an object
        """

        src = Bindable()
        dest1 = Dummy()

        # this should not fail as we might create this path later
        src.bind('a', dest1, 'c.a.a')

        # setting has to fail as the middle object is still missing
        self.assertRaises(AttributeError, setattr, src, 'a', 5)

        # delete as well has to fail
        self.assertRaises(AttributeError, delattr, src, 'a')

    def test_unbind_object(self):
        """
        test the various unbinding things that could happen
        
        """
        src = Bindable()
        dest = Dummy()
        sub = Dummy()

        # simple binding
        src.bind('a', dest, 'da')
        src.a = 1
        self.assertEquals(dest.da, 1)

        # sub-binding
        dest.b = sub
        src.bind('b', dest, 'b.c')
        src.b = 3
        self.assertEquals(sub.c, 3)

        src.bind('a', dest, 'b.a')
        self.assertEquals(sub.a, 1)
        src.a = 4
    
        self.assertEquals(dest.da, 4)
        self.assertEquals(sub.a, 4)

        src.unbind_object(dest)
        src.a = 5
        src.b = 10

        self.assertEquals(dest.da, 4)
        self.assertEquals(sub.a, 4)
        self.assertEquals(sub.c, 3)

        del src.a
        del src.b

        self.assertEquals(dest.da, 4)
        self.assertEquals(sub.a, 4)
        self.assertEquals(sub.c, 3)

    def test_multiple_unbind_object(self):
        """
        test whether the unbinding of objects when having complex bindings
        works fine
        """
        src = Bindable()
        dest1 = Dummy()
        dest2 = Dummy()

        src.bind('a', dest1, 'a')
        src.bind('b', dest1, 'b')

        src.bind('a', dest2, 'a')
        src.bind('b', dest2, 'b')

        src.bind('a', dest1, 'ba')
        src.bind('b', dest1, 'bb')

        src.a = 1
        src.b = 2

        self.assertEquals(dest1.a, 1)
        self.assertEquals(dest1.ba, 1)
        self.assertEquals(dest1.b , 2)
        self.assertEquals(dest1.bb, 2)

        self.assertEquals(dest2.a, 1)
        self.assertEquals(dest2.b , 2)

        src.unbind_object(dest1)

        src.a = 2
        src.b = 4

        self.assertEquals(dest1.a, 1)
        self.assertEquals(dest1.ba, 1)
        self.assertEquals(dest1.b , 2)
        self.assertEquals(dest1.bb, 2)

        self.assertEquals(dest2.a, 2)
        self.assertEquals(dest2.b , 4)
