from PyObjCTools.TestSupport import *
import objc
from PyObjCTest.testbndl import PyObjC_TestClass3
import sys
import types

# Most useful systems will at least have 'NSObject'.
NSObject = objc.lookUpClass('NSObject')
NSArray = objc.lookUpClass('NSArray')
NSAutoreleasePool = objc.lookUpClass('NSAutoreleasePool')

class TestSubclassing(TestCase):
    def testMethodRaise(self):
        # Defining a method whose name is a keyword followed by two underscores
        # should define the method name without underscores in the runtime,
        # and this method should be accesible both with and without the
        # underscores.

        class RaiseClass (NSObject):
            def raise__(self):
                pass

        self.assertNotHasAttr(NSObject, 'raise__')
        self.assertNotHasAttr(NSObject, 'raise')

        self.assertHasAttr(RaiseClass, 'raise__')
        self.assertHasAttr(RaiseClass, 'raise')
        self.assertEquals(RaiseClass.raise__.selector, b'raise')
        self.assertEquals(getattr(RaiseClass, 'raise').selector, b'raise')

    def testMIObjC(self):
        try:
            class MIClass1(NSObject, NSArray):
                pass
            self.fail("Can multiple inherit from two objc classes")
        except TypeError:
            pass

    def testSubclassOfSubclass(self):
        class Level1Class (NSObject):
            def hello(self):
                return "level1"

        class Level2Class (Level1Class):
            def hello(self):
                return "level2"

            def superHello(self):
                return objc.super(Level2Class, self).hello()

            def description(self):
                return objc.super(Level2Class, self).description()

        obj = Level1Class.alloc().init()
        v = obj.hello()
        self.assertEquals(v, "level1")

        obj = Level2Class.alloc().init()
        v = obj.hello()
        self.assertEquals(v, "level2")

        v = obj.superHello()
        self.assertEquals(v, "level1")

        v = obj.description()
        # this may be a bit hardwired for comfort
        self.assertEquals(v.find("<Level2Class"), 0)

    def testMethodSignature(self):
        class Signature (NSObject):
            def test_x_(self, arg, x):
                pass
            test_x_ = objc.selector(test_x_, signature=b'v@:@i')

        v = Signature.new()

        self.assertIsInstance(v, Signature)

        self.assertEquals(v.methodSignatureForSelector_('foo:'), None)

        x = v.methodSignatureForSelector_('test:x:')
        self.assertIsNotNone(x)

        self.assertEquals(x.methodReturnType(), b'v')
        self.assertEquals(x.numberOfArguments(), 4)
        self.assertEquals(x.getArgumentTypeAtIndex_(0), b'@')
        self.assertEquals(x.getArgumentTypeAtIndex_(1), b':')
        self.assertEquals(x.getArgumentTypeAtIndex_(2), b'@')
        self.assertEquals(x.getArgumentTypeAtIndex_(3), b'i')


class TestSelectors(TestCase):
    def testSelectorRepr(self):
        class SelectorRepr(NSObject):
            def foo(self):
                pass

        self.assertTrue(repr(SelectorRepr.foo).startswith('<unbound selector foo of SelectorRepr at'))


class TestCopying (TestCase):

    def testCopy(self):
        class MyCopyClass (NSObject):
            def copyWithZone_(self, zone):
                # NSObject doesn't implement the copying protocol
                #o = super(MyCopyClass, self).copyWithZone_(zone)
                o = self.__class__.alloc().init()
                o.foobar = 2
                return o
            copyWithZone_ = objc.selector(
                copyWithZone_,
                signature=NSObject.copyWithZone_.signature,
                isClassMethod=0)


        # Make sure the runtime correctly marked our copyWithZone_
        # implementation.
        o = MyCopyClass.alloc().init()

        self.assertFalse((o.copyWithZone_.__metadata__()['classmethod']))
        self.assertTrue(o.copyWithZone_.__metadata__()['retval']['already_retained'])
        #self.assertTrue(o.copyWithZone_.callable == MyCopyClass.__dict__['copyWithZone_'].callable)

        o = MyCopyClass.alloc().init()
        o.foobar = 1

        self.assertEquals(o.foobar, 1)

        # Make a copy from ObjC (see testbundle.m)
        c = PyObjC_TestClass3.makeACopy_(o)

        self.assertIsInstance(c, MyCopyClass)
        self.assertEquals(c.foobar, 2)


    def testMultipleInheritance1(self):
        # New-style class mixin
        class MixinClass1 (object):
            def mixinMethod(self):
                return "foo"

        class MITestClass1 (NSObject, MixinClass1):
            def init(self):
                return NSObject.pyobjc_instanceMethods.init(self)

        self.assertHasAttr(MITestClass1, 'mixinMethod')

        o = MITestClass1.alloc().init()
        self.assertEquals(o.mixinMethod(), "foo")

    def testMultipleInheritance2(self):
        # old-style class mixin
        class MixinClass2:
            def mixinMethod(self):
                return "foo"

        class MITestClass2 (NSObject, MixinClass2):
            def init(self):
                return NSObject.pyobjc_instanceMethods.init(self)

        self.assertHasAttr(MITestClass2, 'mixinMethod')

        o = MITestClass2.alloc().init()
        self.assertEquals(o.mixinMethod(), "foo")

class TestClassMethods (TestCase):

    def testClassMethod(self):
        """ check that classmethod()-s are converted to selectors """

        class ClassMethodTest (NSObject):
            def clsMeth(self):
                return "hello"
            clsMeth = classmethod(clsMeth)

        self.assertIsInstance(ClassMethodTest.clsMeth, objc.selector)
        self.assertTrue(ClassMethodTest.clsMeth.isClassMethod)

    def testStaticMethod(self):
        """ check that staticmethod()-s are not converted to selectors """

        class StaticMethodTest (NSObject):
            def stMeth(self):
                return "hello"
            stMeth = staticmethod(stMeth)

        def func(): pass

        self.assertIsInstance(StaticMethodTest.stMeth, type(func))


class TestOverridingSpecials(TestCase):
    def testOverrideSpecialMethods(self):
        aList = [0]

        class ClassWithAlloc(NSObject):
            def alloc(cls):
                aList[0] += 1
                return objc.super(ClassWithAlloc, cls).alloc()

            
        self.assertEquals(aList[0], 0)
        o = ClassWithAlloc.alloc().init()
        self.assertEquals(aList[0], 1)
        self.assertIsInstance(o, NSObject)
        del o

        class ClassWithRetaining(NSObject):
            def retain(self):
                aList.append('retain')
                v =  objc.super(ClassWithRetaining, self).retain()
                return v

            def release(self):
                aList.append('release')
                return objc.super(ClassWithRetaining, self).release()

            def __del__(self):
                aList.append('__del__')


        del aList[:]
        o = ClassWithRetaining.alloc().init()
        v = o.retainCount()
        o.retain()
        self.assertEquals(aList, ['retain'])
        self.assertEquals(o.retainCount(), v+1)
        o.release()
        self.assertEquals(aList, ['retain', 'release'])
        self.assertEquals(o.retainCount(), v)
        del o

        self.assertEquals(aList, ['retain', 'release', 'release', '__del__'])

        # Test again, now remove all python references and create one
        # again.
        del aList[:]
        pool = NSAutoreleasePool.alloc().init()
        o = ClassWithRetaining.alloc().init()
        v = NSArray.arrayWithArray_([o])
        del o
        self.assertEquals(aList, ['retain'])
        o = v[0]
        self.assertEquals(aList, ['retain'])
        del v
        del o
        del pool
        
        self.assertEquals(aList, ['retain', 'release', 'release', '__del__'])

        class ClassWithRetainCount(NSObject):
            def retainCount(self):
                aList.append('retainCount')
                return objc.super(ClassWithRetainCount, self).retainCount()
        
        del aList[:]
        o = ClassWithRetainCount.alloc().init()
        self.assertEquals(aList, [])
        v = o.retainCount()
        self.assertIsInstance(v, int)
        self.assertEquals(aList, ['retainCount'])
        del o

    def testOverrideDealloc(self):
        aList = []

        class Dummy:
            def __del__(self):
                aList.append('__del__')

        self.assertEquals(aList, [])
        Dummy()
        self.assertEquals(aList, ['__del__'])

        class ClassWithDealloc(NSObject):
            def init(self):
                self = objc.super(ClassWithDealloc, self).init()
                if self is not None:
                    self.obj = Dummy()
                return self

            def dealloc(self):
                aList.append('dealloc')
                return objc.super(ClassWithDealloc, self).dealloc()

        del aList[:]
        o = ClassWithDealloc.alloc().init()
        self.assertEquals(aList, [])
        del o
        self.assertEquals(len(aList), 2)
        self.assertIn('dealloc', aList)
        self.assertIn('__del__', aList)

        class SubClassWithDealloc(ClassWithDealloc):
            def dealloc(self):
                aList.append('dealloc.dealloc')
                return objc.super(SubClassWithDealloc, self).dealloc()

        del aList[:]
        o = SubClassWithDealloc.alloc().init()
        self.assertEquals(aList, [])
        del o
        self.assertEquals(len(aList), 3)
        self.assertIn('dealloc.dealloc', aList)
        self.assertIn('dealloc', aList)
        self.assertIn('__del__', aList)

        class ClassWithDeallocAndDel(NSObject):
            def init(self):
                self = objc.super(ClassWithDeallocAndDel, self).init()
                if self is not None:
                    self.obj = Dummy()
                return self

            def dealloc(self):
                aList.append('dealloc')
                return objc.super(ClassWithDeallocAndDel, self).dealloc()

            def __del__(self):
                aList.append('mydel')

        del aList[:]
        o = ClassWithDeallocAndDel.alloc().init()
        self.assertEquals(aList, [])
        del o
        self.assertEquals(len(aList), 3)
        self.assertIn('mydel', aList)
        self.assertIn('dealloc', aList)
        self.assertIn('__del__', aList)

    def testMethodNames(self):

        class MethodNamesClass (NSObject):
            def someName_andArg_(self, name, arg):
                pass

            def _someName_andArg_(self, name, arg):
                pass


            def raise__(self):
                pass

            def froobnicate__(self, a, b):
                pass

        # XXX: workaround for a 'feature' in class-builder.m, that code 
        # ignores methods whose name starts with two underscores. That code
        # is not necessary, or the other ways of adding methods to a class
        # should be changed.
        def __foo_bar__(self, a, b, c):
            pass

        MethodNamesClass.__foo_bar__ = __foo_bar__

        self.assertEquals(MethodNamesClass.someName_andArg_.selector,
                b'someName:andArg:')
        self.assertEquals(MethodNamesClass._someName_andArg_.selector,
                b'_someName:andArg:')
        self.assertEquals(MethodNamesClass.__foo_bar__.selector,
                b'__foo_bar__')
        self.assertEquals(MethodNamesClass.raise__.selector,
                b'raise')
        self.assertEquals(MethodNamesClass.froobnicate__.selector,
                b'froobnicate::')

    def testOverrideRespondsToSelector(self):
        class OC_RespondsClass (NSObject):
            def initWithList_(self, lst):
                objc.super(OC_RespondsClass, self).init()
                self.lst = lst
                return self

            def respondsToSelector_(self, selector):
                self.lst.append(selector)
                return objc.super(OC_RespondsClass, self).respondsToSelector_(selector)

        lst = []
        o = OC_RespondsClass.alloc().initWithList_(lst)

        self.assertEquals(lst, [])

        b = o.respondsToSelector_('init')
        self.assertTrue(b)
        self.assertEquals(lst, ['init'])

        b = o.respondsToSelector_('alloc')
        self.assertFalse(b)
        self.assertEquals(lst, ['init', 'alloc'])

    def testOverrideInstancesRespondToSelector(self):
        lst = []
        class OC_InstancesRespondClass (NSObject):

            @classmethod
            def instancesRespondToSelector_(cls, selector):
                lst.append(selector)
                return objc.super(OC_InstancesRespondClass, cls).instancesRespondToSelector_(selector)

        self.assertEquals(lst, [])

        b = OC_InstancesRespondClass.instancesRespondToSelector_('init')
        self.assertTrue(b)
        self.assertEquals(lst, ['init'])

        b = OC_InstancesRespondClass.instancesRespondToSelector_('alloc')
        self.assertFalse(b)
        self.assertEquals(lst, ['init', 'alloc'])

    def testImplementingSetValueForKey(self):
        values = {}
        class CrashTest (NSObject):
            def setValue_forKey_(self, v, k):
                values[k] = v

        o = CrashTest.alloc().init()
        o.setValue_forKey_(42,"key")

        self.assertEqual(values, {"key":42})


if __name__ == '__main__':
    main()
