"""
Tests for the Key-Value Coding for hybrid python objects.

NOTE: Testcases here should be synchronized with the Key-Value Coding tests
in PyObjCTools.test.test_keyvalue and objc.test.test_keyvalue.

TODO:
    - This test uses C code, that code should be added to this package!
    - Tests that access properties in the parent Objective-C class!
    - More key-error tests, the tests don't cover all relevant code yet.
"""
import objc
from PyObjCTools.TestSupport import *
import sys
from PyObjCTest.testhelper import PyObjC_TestClass3 as STUB
from Foundation import *

class KeyValueClass1 (NSObject):
    def init(self):
        self = super(KeyValueClass1, self).init()
        self.key3 = 3
        self._key4 = u"4"
        self.__private = u"private"
        return self

    def addMultiple(self):
        self.multiple = KeyValueClass1.alloc().init()
        self.multiple.level2 = KeyValueClass1.alloc().init()
        self.multiple.level2.level3 = KeyValueClass1.alloc().init()
        self.multiple.level2.level3.keyA = u"hello"
        self.multiple.level2.level3.keyB = u"world"

    def getKey1(self):
        return 1

    def key2(self):
        return 2

    def setKey4_(self, value):
        self._key4 = value * 4

    def setKey5_(self, value):
        self.key5 = value * 5

    def keyRaisingValueError(self):
        raise ValueError, "42"

    def keyRaisingNSUnknownKeyException(self):
        return self.valueForKey_("thisKeyDoesNotExist")

    def keyReturningSameSlector(self):
        return self.keyReturningSameSelector

    def keyReturningOtherSelector(self):
        return self.getKey1

class KeyValueClass1Explicit (NSObject):
    def init(self):
        self = super(KeyValueClass1Explicit, self).init()
        self._values = {}
        self._values[u'key3'] = 3
        self._values[u'key4'] = u"4"
        self._values['_private'] = u"private"
        return self

    def addMultiple(self):
        self._values["multiple"] = KeyValueClass1Explicit.alloc().init()
        self._values["multiple"]._values["level2"] = KeyValueClass1Explicit.alloc().init()
        self._values["multiple"]._values["level2"]._values["level3"] = KeyValueClass1Explicit.alloc().init()
        self._values["multiple"]._values["level2"]._values["level3"]._values["keyA"] = u"hello"
        self._values["multiple"]._values["level2"]._values["level3"]._values["keyB"] = u"world"

    def valueForKey_(self, key):
        if key == "key1":
            return 1

        elif key == "key2":
            return 2

        return self._values[key]

    def storedValueForKey_(self, key):
        return self.valueForKey_(key)

    def setValue_forKey_(self, value, key):
        if key == "key4":
            value = value * 4
        elif key == "key5":
            value = value * 5

        self._values[key] = value

    def takeStoredValue_forKey_(self, value, key):
        self.setValue_forKey_(value, key)

    def takeValue_forKey_(self, value, key):
        self.setValue_forKey_(value, key)

class KeyValueClass4 (NSObject):
    __slots__ = ('foo', )

    def init(self):
        self = super(KeyValueClass4, self).init()
        self.foo = u"foobar"
        return self

    # Definition for property 'bar'. Use odd names for the methods
    # because the KeyValue support recognizes the usual names.
    def read_bar(self):
        return self.foo + self.foo

    def write_bar (self, value):
        self.foo = value + value

    bar = property(read_bar, write_bar)

    roprop = property(lambda self: u"read-only")

class KVOClass(NSObject):
    def automaticallyNotifiesObserversForKey_(self, aKey):
        return objc.NO

    def test(self): return u"test"


class KeyValueObserver (NSObject):
    def init(self):
        self.observed = []
        return self

    def observeValueForKeyPath_ofObject_change_context_(
            self, keyPath, object, change, context):
        self.observed.append( (keyPath, object, change) )


class PyKeyValueCoding (TestCase):
    def testNoPrivateVars(self):
        # Private instance variables ('anObject.__value') are not accessible using
        # key-value coding.
        o = KeyValueClass1.alloc().init()
        self.assertRaises(KeyError, STUB.keyValue_forObject_key_, 0, o, u"private")

    def testValueForKey(self):
        o = KeyValueClass1.alloc().init()
        o.addMultiple()

        self.assertEqual(STUB.keyValue_forObject_key_(0, o, u"key1"), 1)

        self.assertEqual(STUB.keyValue_forObject_key_(0, o, u"key2"), 2)
        self.assertEqual(STUB.keyValue_forObject_key_(0, o, u"key3"), 3)
        self.assertEqual(STUB.keyValue_forObject_key_(0, o, u"key4"), "4")
        self.assertEqual(STUB.keyValue_forObject_key_(0, o, u"multiple"), o.multiple)

        self.assertRaises(KeyError, STUB.keyValue_forObject_key_, 0, o, u"nokey")

        self.assertRaises(ValueError, STUB.keyValue_forObject_key_, 0, o,
                u"keyRaisingValueError")
        self.assertRaises(KeyError, STUB.keyValue_forObject_key_, 0, o,
                u"keyRaisingNSUnknownKeyException")
        self.assertRaises(KeyError, STUB.keyValue_forObject_key_, 0, o,
                u"keyReturningSameSelector")

        obj = STUB.keyValue_forObject_key_( 0, o, u"keyReturningOtherSelector")
        self.assertIsInstance(obj, objc.selector)
        self.assertEqual(obj.selector, b"getKey1")
        self.assertIs(obj.self, o )
    def testValueForKey2(self):
        o = KeyValueClass4.alloc().init()

        self.assertEqual(STUB.keyValue_forObject_key_(0, o, u"foo"), u"foobar")
        self.assertEqual(STUB.keyValue_forObject_key_(0, o, u"bar"), u"foobarfoobar")
        self.assertEqual(STUB.keyValue_forObject_key_(0, o, u"roprop"), u"read-only")


    def testStoredValueForKey(self):
        o = KeyValueClass1.alloc().init()
        o.addMultiple()

        self.assertEqual(STUB.keyValue_forObject_key_(2, o, u"key1"), 1)
        self.assertEqual(STUB.keyValue_forObject_key_(2, o, u"key2"), 2)
        self.assertEqual(STUB.keyValue_forObject_key_(2, o, u"key3"), 3)
        self.assertEqual(STUB.keyValue_forObject_key_(2, o, u"key4"), "4")
        self.assertEqual(STUB.keyValue_forObject_key_(2, o, u"multiple"), o.multiple)

        self.assertRaises(KeyError, STUB.keyValue_forObject_key_, 2, o, u"nokey")

    def testStoredValueForKey2(self):
        o = KeyValueClass4.alloc().init()

        self.assertEqual(STUB.keyValue_forObject_key_(2, o, u"foo"), u"foobar")
        self.assertEqual(STUB.keyValue_forObject_key_(2, o, u"bar"), u"foobarfoobar")
        self.assertEqual(STUB.keyValue_forObject_key_(2, o, u"roprop"), u"read-only")

    def testValueForKeyPath(self):
        o = KeyValueClass1.alloc().init()
        o.addMultiple()

        self.assertEqual(STUB.keyValue_forObject_key_(1, o, u"multiple"), o.multiple)
        self.assertEqual(STUB.keyValue_forObject_key_(1, o, u"multiple.level2"), o.multiple.level2)
        self.assertEqual(STUB.keyValue_forObject_key_(1, o, u"multiple.level2.level3.keyA"),
            o.multiple.level2.level3.keyA)
        self.assertEqual(STUB.keyValue_forObject_key_(1, o, u"multiple.level2.level3.keyB"),
            o.multiple.level2.level3.keyB)

        self.assertRaises(KeyError, STUB.keyValue_forObject_key_, 1, o, u"multiple.level2.nokey")

    @max_os_level('10.5')
    def testValuesForKeys(self):
        o = KeyValueClass1.alloc().init()

        self.assertEqual(STUB.keyValue_forObject_key_(3, o, [u"key1", u"key2", u"key3", u"key4"]), { u"key1":1, u"key2":2, u"key3": 3, u"key4": u"4"} )

        self.assertRaises(KeyError, STUB.keyValue_forObject_key_, 3, o, [u"key1", u"key3", u"nosuchkey"])

    @max_os_level('10.5')
    def testTakeValueForKey(self):
        o = KeyValueClass1.alloc().init()

        self.assertEqual(o.key3, 3)
        STUB.setKeyValue_forObject_key_value_(0, o, u'key3', u'drie')
        self.assertEqual(o.key3, u"drie")

        self.assertEqual(o._key4, u"4")
        STUB.setKeyValue_forObject_key_value_(0, o, u'key4', u'vier')
        self.assert_(not hasattr(o, u"key4"))
        self.assertEqual(o._key4, u"viervierviervier")

        o.key5 = 1
        STUB.setKeyValue_forObject_key_value_(0, o, u'key5', u'V')
        self.assertEqual(o.key5, u"VVVVV")

        self.assert_(not hasattr(o, u'key9'))
        STUB.setKeyValue_forObject_key_value_(0, o, u'key9', u'IX')
        self.assert_(hasattr(o, u'key9'))
        self.assertEqual(o.key9, u'IX')

    @max_os_level('10.5')
    def testTakeValueForKey2(self):
        o = KeyValueClass4.alloc().init()

        self.assertEqual(o.foo, u"foobar")
        STUB.setKeyValue_forObject_key_value_(0, o, u'foo', u'FOO')
        self.assertEqual(o.foo, u"FOO")

        self.assertRaises(KeyError, STUB.setKeyValue_forObject_key_value_, 0, o, u'key9', u'IX')


    def testTakeStoredValueForKey(self):
        o = KeyValueClass1.alloc().init()

        self.assertEqual(o.key3, 3)
        STUB.setKeyValue_forObject_key_value_(2, o, u'key3', u'drie')
        self.assertEqual(o.key3, u"drie")

        self.assertEqual(o._key4, u"4")
        STUB.setKeyValue_forObject_key_value_(2, o, u'key4', u'vier')
        self.assertEqual(o._key4, u"viervierviervier")

        o.key5 = 1
        STUB.setKeyValue_forObject_key_value_(2, o, u'key5', u'V')
        self.assertEqual(o.key5, u"VVVVV")

        self.assert_(not hasattr(o, u'key9'))
        STUB.setKeyValue_forObject_key_value_(2, o, u'key9', u'IX')
        self.assert_(hasattr(o, u'key9'))
        self.assertEqual(o.key9, u'IX')

    def testStoredTakeValueForKey2(self):
        o = KeyValueClass4.alloc().init()

        self.assertEqual(o.foo, u"foobar")
        STUB.setKeyValue_forObject_key_value_(2, o, u'foo', u'FOO')
        self.assertEqual(o.foo, u"FOO")

        self.assertRaises(KeyError, STUB.setKeyValue_forObject_key_value_, 2, o, u'key9', u'IX')
        self.assertRaises(KeyError, STUB.setKeyValue_forObject_key_value_, 2, o, u'roprop', u'IX')

    @max_os_level('10.5')
    def testTakeValuesFromDictionary(self):
        o = KeyValueClass1.alloc().init()

        self.assertEqual(o.key3, 3)
        self.assertEqual(o._key4, u"4")
        o.key5 = 1
        self.assert_(not hasattr(o, u'key9'))

        STUB.setKeyValue_forObject_key_value_(3, o, None,
            {
                u'key3': u'drie',
                u'key4': u'vier',
                u'key5': u'V',
                u'key9': u'IX',
            })

        self.assertEqual(o.key3, u"drie")
        self.assertEqual(o._key4, u"viervierviervier")
        self.assertEqual(o.key5, u"VVVVV")
        self.assert_(hasattr(o, u'key9'))
        self.assertEqual(o.key9, u'IX')

    @max_os_level('10.5')
    def testTakeValuesFromDictionary2(self):
        o = KeyValueClass4.alloc().init()

        self.assertEqual(o.foo, u"foobar")
        STUB.setKeyValue_forObject_key_value_(3, o, None, { u'foo': u'FOO' })
        self.assertEqual(o.foo, u"FOO")

        self.assertRaises(KeyError, STUB.setKeyValue_forObject_key_value_, 3, o, None, { u'key9':  u'IX' })
        self.assertRaises(KeyError, STUB.setKeyValue_forObject_key_value_, 3, o, None, { u'roprop':  u'IX' })

    @max_os_level('10.5')
    def testTakeValueForKeyPath(self):
        o = KeyValueClass1.alloc().init()
        o.addMultiple()

        self.assertEqual(o.multiple.level2.level3.keyA, u"hello")
        self.assertEqual(o.multiple.level2.level3.keyB, u"world")

        STUB.setKeyValue_forObject_key_value_(1, o, u"multiple.level2.level3.keyA", u"KeyAValue")
        self.assertEqual(o.multiple.level2.level3.keyA, u"KeyAValue")

        STUB.setKeyValue_forObject_key_value_(1, o, u"multiple.level2.level3.keyB", 9.999)
        self.assertEqual(o.multiple.level2.level3.keyB, 9.999)

    if hasattr(NSObject, u"willChangeValueForKey_"):
        # NSKeyValueObserving is only available on Panther and beyond
        def testKVO1(self):
            o = KVOClass.alloc().init()
            o.addObserver_forKeyPath_options_context_(self, u"test", 0, None)
            o.removeObserver_forKeyPath_(self, u"test")

        def testKVO2(self):
            """
            Check if observations work for python-based keys on ObjC classes
            """
            observer = KeyValueObserver.alloc().init()
            self.assertEqual(observer.observed, [])

            o = KeyValueClass1.alloc().init()

            o.addObserver_forKeyPath_options_context_(observer, u"key3", 0, 0)
            try:
                STUB.setKeyValue_forObject_key_value_(2, o, u'key3', u'drie')
                self.assertEqual(o.key3, u"drie")

                self.assertEqual(len(observer.observed), 1)

                keyPath, object, change = observer.observed[0]
                self.assertEqual(keyPath, u"key3")
                self.assert_(object is o)
                self.assertEqual(change, {NSKeyValueChangeKindKey: 1 })

            finally:
                o.removeObserver_forKeyPath_(observer, u'key3')

        def testKVO3(self):
            """
            Check if observations work for python-based keys on ObjC classes
            """
            observer = KeyValueObserver.alloc().init()
            self.assertEqual(observer.observed, [])

            o = KeyValueClass1.alloc().init()
            STUB.setKeyValue_forObject_key_value_(2, o, u'key3', u'three')

            o.addObserver_forKeyPath_options_context_(observer, u"key3",
                    NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld,
                    0)
            try:
                STUB.setKeyValue_forObject_key_value_(2, o, u'key3', u'drie')
                self.assertEqual(o.key3, u"drie")

                self.assertEqual(len(observer.observed), 1)

                keyPath, object, change = observer.observed[0]
                self.assertEqual(keyPath, u"key3")
                self.assert_(object is o)
                self.assertEqual(change,
                    {
                        NSKeyValueChangeKindKey:1,
                        NSKeyValueChangeNewKey:u'drie',
                        NSKeyValueChangeOldKey:u'three'
                    })

            finally:
                o.removeObserver_forKeyPath_(observer, u'key3')

class PyKeyValueCodingExplicit (TestCase):

    def testValueForKey(self):
        o = KeyValueClass1Explicit.alloc().init()
        o.addMultiple()

        self.assertEqual(STUB.keyValue_forObject_key_(0, o, u"key1"), 1)

        self.assertEqual(STUB.keyValue_forObject_key_(0, o, u"key2"), 2)
        self.assertEqual(STUB.keyValue_forObject_key_(0, o, u"key3"), 3)
        self.assertEqual(STUB.keyValue_forObject_key_(0, o, u"key4"), "4")
        self.assertEqual(STUB.keyValue_forObject_key_(0, o, u"multiple"), o._values['multiple'])

        self.assertRaises(KeyError, STUB.keyValue_forObject_key_, 0, o, u"nokey")

    def testStoredValueForKey(self):
        o = KeyValueClass1Explicit.alloc().init()
        o.addMultiple()

        self.assertEqual(STUB.keyValue_forObject_key_(2, o, u"key1"), 1)
        self.assertEqual(STUB.keyValue_forObject_key_(2, o, u"key2"), 2)
        self.assertEqual(STUB.keyValue_forObject_key_(2, o, u"key3"), 3)
        self.assertEqual(STUB.keyValue_forObject_key_(2, o, u"key4"), "4")
        self.assertEqual(STUB.keyValue_forObject_key_(2, o, u"multiple"), o._values['multiple'])

        self.assertRaises(KeyError, STUB.keyValue_forObject_key_, 2, o, u"nokey")

    def testValueForKeyPath(self):
        o = KeyValueClass1Explicit.alloc().init()
        o.addMultiple()

        self.assertEqual(STUB.keyValue_forObject_key_(1, o, u"multiple"), o._values['multiple'])
        self.assertEqual(STUB.keyValue_forObject_key_(1, o, u"multiple.level2"), o._values['multiple']._values['level2'])
        self.assertEqual(STUB.keyValue_forObject_key_(1, o, u"multiple.level2.level3.keyA"),
            o._values['multiple']._values['level2']._values['level3']._values['keyA'])
        self.assertEqual(STUB.keyValue_forObject_key_(1, o, u"multiple.level2.level3.keyB"),
            o._values['multiple']._values['level2']._values['level3']._values['keyB'])

        self.assertRaises(KeyError, STUB.keyValue_forObject_key_, 1, o, u"multiple.level2.nokey")

    @max_os_level('10.5')
    def testValuesForKeys(self):
        o = KeyValueClass1Explicit.alloc().init()

        self.assertEqual(STUB.keyValue_forObject_key_(3, o, [u"key1", u"key2", u"key3", u"key4"]), { u"key1":1, u"key2":2, u"key3": 3, u"key4": u"4"} )

        self.assertRaises(KeyError, STUB.keyValue_forObject_key_, 3, o, [u"key1", u"key3", u"nosuchkey"])

    def testTakeValueForKey(self):
        o = KeyValueClass1Explicit.alloc().init()

        self.assertEqual(o._values['key3'], 3)
        STUB.setKeyValue_forObject_key_value_(0, o, u'key3', u'drie')
        self.assertEqual(o._values['key3'], u"drie")

        self.assertEqual(o._values['key4'], u"4")
        STUB.setKeyValue_forObject_key_value_(0, o, u'key4', u'vier')
        self.assert_(not hasattr(o, u"key4"))
        self.assertEqual(o._values['key4'], u"viervierviervier")

        o._values['key5'] = 1
        STUB.setKeyValue_forObject_key_value_(0, o, u'key5', u'V')
        self.assertEqual(o._values['key5'], u"VVVVV")

        self.assert_(not hasattr(o, u'key9'))
        self.assert_('key9' not in o._values)
        STUB.setKeyValue_forObject_key_value_(0, o, u'key9', u'IX')
        self.assert_(not hasattr(o, u'key9'))
        self.assert_('key9' in o._values)
        self.assertEqual(o._values['key9'], u'IX')

    def testTakeStoredValueForKey(self):
        o = KeyValueClass1Explicit.alloc().init()

        self.assertEqual(o._values['key3'], 3)
        STUB.setKeyValue_forObject_key_value_(2, o, u'key3', u'drie')
        self.assertEqual(o._values['key3'], u"drie")

        self.assertEqual(o._values['key4'], u"4")
        STUB.setKeyValue_forObject_key_value_(2, o, u'key4', u'vier')
        self.assertEqual(o._values['key4'], u"viervierviervier")

        o.key5 = 1
        STUB.setKeyValue_forObject_key_value_(2, o, u'key5', u'V')
        self.assertEqual(o._values['key5'], u"VVVVV")

        self.assert_('key9' not in o._values)
        STUB.setKeyValue_forObject_key_value_(2, o, u'key9', u'IX')
        self.assert_('key9' in o._values)
        self.assertEqual(o._values['key9'], u'IX')

    @max_os_level('10.5')
    def testTakeValuesFromDictionary(self):
        o = KeyValueClass1Explicit.alloc().init()

        self.assertEqual(o._values['key3'], 3)
        self.assertEqual(o._values['key4'], u"4")
        o._values['key5'] = 1
        self.assert_('key9' not in o._values)

        STUB.setKeyValue_forObject_key_value_(3, o, None,
            {
                u'key3': u'drie',
                u'key4': u'vier',
                u'key5': u'V',
                u'key9': u'IX',
            })

        self.assertEqual(o._values['key3'], u"drie")
        self.assertEqual(o._values['key4'], u"viervierviervier")
        self.assertEqual(o._values['key5'], u"VVVVV")
        self.assertEqual(o._values['key9'], u'IX')

    @max_os_level('10.5')
    def testTakeValueForKeyPath(self):
        o = KeyValueClass1Explicit.alloc().init()
        o.addMultiple()

        self.assertEqual(o._values['multiple']._values['level2']._values['level3']._values['keyA'], u"hello")
        self.assertEqual(o._values['multiple']._values['level2']._values['level3']._values['keyB'], u"world")

        STUB.setKeyValue_forObject_key_value_(1, o, u"multiple.level2.level3.keyA", u"KeyAValue")
        self.assertEqual(o._values['multiple']._values['level2']._values['level3']._values['keyA'], u"KeyAValue")

        STUB.setKeyValue_forObject_key_value_(1, o, u"multiple.level2.level3.keyB", 9.999)
        self.assertEqual(o._values['multiple']._values['level2']._values['level3']._values['keyB'], 9.999)


class TestBaseExceptions (TestCase):
    """
    Check that NSObject implementation of Key-Value coding raises the
    exception that we expect it to raise.
    """
    def testValueForKey(self):
        o = NSObject.alloc().init()

        self.assertRaises(KeyError, o.valueForKey_, u"unknownKey")

    def testStoredValueForKey(self):
        o = NSObject.alloc().init()

        self.assertRaises(KeyError, o.storedValueForKey_, u"unknownKey")

    def testTakeStoredValue(self):
        o = NSObject.alloc().init()

        self.assertRaises(KeyError,
            o.takeStoredValue_forKey_, u"value", u"unknownKey")



if __name__ == "__main__":
    main()
