"""
These tests check if method prototypes match method signatures.

TODO: a separate test file that tests calls with optional arguments in
method defintions:
        class MyClass (NSObject):
            def myMethod_(self, arg=1):
                pass
        o = MyClass.alloc().init()
        o.myMethod_(2)
        o.myMethod_() # should also work, arg == 1
"""

from PyObjCTools.TestSupport import *
from PyObjCTest.fnd import NSObject

class TestInheritedProtoype (TestCase):
    #
    # These tests check for methods that are inherited from a superclass and
    # therefore have an explict method signature. The number of arguments in
    # the actual method implementation must match that signature.
    #
    def setUp(self):
        import warnings
        warnings.filterwarnings('error', category=DeprecationWarning)

    def tearDown(self):
        import warnings
        del warnings.filters[0]


    def testCorrectArgCount(self):
        # OK: same number of arguments

        class OC_InPro_CorrectArgCount1 (NSObject):
            def init(self):
                pass

        class OC_InPro_CorrectArgCount2 (NSObject):
            def replacementObjectForArchiver_(self, archiver):
                pass

        class OC_InPro_CorrectArgCount3 (NSObject):
            def replacementObjectForArchiver_(self, archiver=None):
                pass

    def testTooFewArguments(self):
        # BAD: too few arguments, should raise error

        try:
            class OC_InPro_TooFew1 (NSObject):
                def init():
                    pass

            self.fail()
        except objc.BadPrototypeError:
            pass

        try:
            class OC_InPro_TooFew2 (NSObject):
                def replacementObjectForArchiver_(self):
                    pass

            self.fail()
        except objc.BadPrototypeError:
            pass

        try:
            class OC_InPro_TooFew3 (NSObject):
                def replacementObjectForArchiver_():
                    pass

            self.fail()
        except objc.BadPrototypeError:
            pass

    def testTooManyArguments(self):
        # BAD: too many arguments, should raise error

        try:
            class OC_InPro_TooMany1 (NSObject):
                def init(self, arg):
                    pass

            self.fail()
        except objc.BadPrototypeError:
            pass

        try:
            class OC_InPro_TooMany2 (NSObject):
                def init(self, arg, arg2):
                    pass

            self.fail()
        except objc.BadPrototypeError:
            pass

        try:
            class OC_InPro_TooMany3 (NSObject):
                def replacementObjectForArchiver_(self, archiver, extra):
                    pass

            self.fail()
        except objc.BadPrototypeError:
            pass

        try:
            class OC_InPro_TooMany4 (NSObject):
                def replacementObjectForArchiver_(self, archiver, opt=3):
                    pass

            self.fail()
        except objc.BadPrototypeError:
            pass

    def testBadOverriddenSignature(self):
        # BAD: signature doesn't match super class signature
        try:
            class OC_InPro_BadSig1 (NSObject):
                def init(self):
                    pass
                init = objc.selector(init, signature=b'v@:')

            self.fail()

        except objc.BadPrototypeError:
            pass

        try:
            class OC_InPro_BadSig2 (NSObject):
                def init(self, arg1, arg2):
                    pass
                init = objc.selector(init, signature=b'v@:@@')

            self.fail()

        except objc.BadPrototypeError:
            pass


        # This one is fine, just to ensure PyObjC isn't too
        # strict.
        class OC_InPro_BadSig3 (NSObject):
            def init(self):
                pass
            init = objc.selector(init, signature=b'@@:')


    def testAllArgsOptional(self):
        # Dodgy, all arguments are optional using '*args, **kwds'
        #
        # This should be accepted because simple decorators will use
        # a method signature like this and we don't want errors or warnings
        # for that.
        #
        # NOTE: see also the 'decorator' library, that allows you to
        # use decorators without ending up with an ugly signature.
        class OC_InPro_AllOpt1 (NSObject):
            def init(*args, **kwds):
                pass

        class OC_InPro_AllOpt2 (NSObject):
            def replacementObjectForArchiver_(*args, **kwds):
                pass


        # Also allow versions with an explicit self argument, those
        # are commonly used as well.
        class OC_InPro_AllOpt3 (NSObject):
            def init(self, *args, **kwds):
                pass

        class OC_InPro_AllOpt4 (NSObject):
            def replacementObjectForArchiver_(self, *args, **kwds):
                pass

    def testOptionalArgs(self):
        # BAD: optional arguments, which don't exist in Objective-C
        try:
            class OC_InPro_OptArgs1 (NSObject):
                def replacementObjectForArchiver_(self, *args):
                    pass

            self.fail()
        except objc.BadPrototypeError:
            pass

        try:
            class OC_InPro_OptArgs2 (NSObject):
                def replacementObjectForArchiver_(self, **kwds):
                    pass

            self.fail()
        except objc.BadPrototypeError:
            pass

class TestExplicitPrototype (TestCase):
    #
    # These tests check for methods with an explict method signature in the
    # python code (not inheritted). The python code should match the provided
    # signature.
    def setUp(self):
        import warnings
        warnings.filterwarnings('error', category=DeprecationWarning)

    def tearDown(self):
        import warnings
        del warnings.filters[0]

    def testCorrectArgCount(self):
        # OK: same number of arguments

        class OC_ExplProto_CorrectArgCount1 (NSObject):

            def noargsmethod(self):
                pass
            noargsmethod = objc.selector(noargsmethod, signature=b'v@:')

        class OC_ExplProto_CorrectArgCount2 (NSObject):
            def oneargmethod_(self, archiver):
                pass
            oneargmethod_ = objc.selector(oneargmethod_, signature=b'v@:i')

        class OC_ExplProto_CorrectArgCount3 (NSObject):
            def oneargmethod2_(self, archiver=None):
                pass
            oneargmethod2_ = objc.selector(oneargmethod2_, signature=b'v@:i')

    def testSignatureDoesNotMatchColons(self):
        # OK: the signature specifies more arguments than the implicit or
        # explicit selector seems to need.

        class OC_ExplProto_ColonVsCount1 (NSObject):
            def twoargmethod(self, arg1, arg2):
                pass
            twoargmethod = objc.selector(twoargmethod)

        class OC_ExplProto_ColonVsCount2 (NSObject):
            def twoargmethod(self, arg1, arg2):
                pass
            twoargmethod = objc.selector(twoargmethod, signature=b'v@:@@')

        class OC_ExplProto_ColonVsCount3 (NSObject):
            def twoargmethod(self, arg1, arg2):
                pass
            twoargmethod = objc.selector(twoargmethod, selector=b'twoargs')

        #class OC_ExplProto_ColonVsCount4 (NSObject):
        #    def noargmethod_(self):
        #        pass
        #    noargmethod_ = objc.selector(noargmethod_)

        #class OC_ExplProto_ColonVsCount5 (NSObject):
        #    def noargmethod_(self):
        #        pass
        #    noargmethod_ = objc.selector(noargmethod_, signature=b'v@:')

        #class OC_ExplProto_ColonVsCount6 (NSObject):
        #    def noargmethod_(self):
        #        pass
        #    noargmethod_ = objc.selector(noargmethod_, selector='doit:')


    def testTooFewArguments(self):
        # BAD: too few arguments, should raise error

        try:
            class OC_ExplProto_TooFew1 (NSObject):

                def oneargmethod3_(self):
                    pass
                oneargmethod3_ = objc.selector(oneargmethod3_, signature=b'i@:f')

            self.fail()
        except objc.BadPrototypeError:
            pass

    def testTooManyArguments(self):
        # BAD: too many arguments, should raise error

        try:
            class OC_ExplProto_TooMany1 (NSObject):
                def oneargmethod4_(self, a, b):
                    pass
                oneargmethod4_ = objc.selector(oneargmethod4_, signature=b'i@:f')

            self.fail()
        except objc.BadPrototypeError:
            pass

    def testAllArgsOptional(self):
        # Dodgy, all arguments are optional using '*args, **kwds'
        #
        # This should be accepted because simple decorators will use
        # a method signature like this and we don't want errors or warnings
        # for that.
        #
        # NOTE: see also the 'decorator' library, that allows you to
        # use decorators without ending up with an ugly signature.
        class OC_ExplProto_AllOpt1 (NSObject):
            def oneargmethod_(*args, **kwds):
                pass
            oneargmethod_ = objc.selector(oneargmethod_, signature=b'i@:i')

    def testOptionalArgs(self):
        # BAD: optional arguments, which don't exist in Objective-C
        try:
            class OC_ExplProto_OptArgs1 (NSObject):
                def oneargmethod_(self, *args):
                    pass
                oneargmethod_ = objc.selector(oneargmethod_, signature=b'i@:i')

            self.fail()
        except objc.BadPrototypeError:
            pass

        try:
            class OC_ExplProto_OptArgs2 (NSObject):
                def oneargmethod_(self, **kwds):
                    pass
                oneargmethod_ = objc.selector(oneargmethod_, signature=b'i@:i')

            self.fail()
        except objc.BadPrototypeError:
            pass

    def testOutputArgumentsPresent(self):
        # OK: Output arguments, all arguments are present
        class OC_ExplProto_OutputPresent1 (NSObject):

            def oneoutput_(self, output):
                pass
            oneoutput_ = objc.selector(oneoutput_, signature=b'i@:^@')

        class OC_ExplProto_OutputPresent2 (NSObject):

            def oneinput_output_(self, input, output):
                pass
            oneinput_output_ = objc.selector(oneinput_output_,
                    signature=b'i@:f^@')

    def testOutputArgumentsAbsent(self):
        # BAD: Output arguments, output not in prototype
        #
        # NOTE: this was a warning in PyObjC 2.0 and the only
        # valid way to work in PyObjC 1.x.
        try:
            class OC_ExplProto_OutputAbsent1 (NSObject):

                def oneoutput_(self):
                    pass
                oneoutput_ = objc.selector(oneoutput_, signature=b'i@:^@')

            self.fail()

        except objc.BadPrototypeError:
            pass

        try:
            class OC_ExplProto_OutputAbsent2 (NSObject):

                def oneinput_output_(self, input):
                    pass
                oneinput_output_ = objc.selector(oneinput_output_,
                        signature=b'i@:i^@')

            self.fail()

        except objc.BadPrototypeError:
            pass

class TestImplicitSignature (TestCase):
    #
    # These tests check for methods that aren't inheritted and don't have
    # an explicit prototype either
    #

    def setUp(self):
        import warnings
        warnings.filterwarnings('error', category=DeprecationWarning)

    def tearDown(self):
        import warnings
        del warnings.filters[0]

    def testColonMatch(self):
        # OK: the number of underscores matches the number of arguments
        class OC_ImplProto_ColonMatch1 (NSObject):
            def simplemethod(self):
                pass
        self.assertEqual(OC_ImplProto_ColonMatch1.simplemethod.selector, b'simplemethod')
        self.assertEqual(OC_ImplProto_ColonMatch1.simplemethod.signature, b'v@:')

        class OC_ImplProto_ColonMatch2 (NSObject):
            def simplemethod_arg2_(self, a, b):
                return 1
        self.assertEqual(OC_ImplProto_ColonMatch2.simplemethod_arg2_.selector, b'simplemethod:arg2:')
        self.assertEqual(OC_ImplProto_ColonMatch2.simplemethod_arg2_.signature, b'@@:@@')

    def testTooFewColons(self):
        # OK: the number of implied colons is smaller than the actual number of
        # arguments.
        #
        # This is fine because you want to use the regular python naming
        # conventions for methods that won't be called from Objective-C,
        # that keeps Python code as nice as possible.
        class OC_ImplProto_TooFew1 (NSObject):
            def myMethod(self, arg1, arg2=4):
                pass
        self.assertEqual(OC_ImplProto_TooFew1.myMethod.selector, b'myMethod')
        self.assertEqual(OC_ImplProto_TooFew1.myMethod.signature, b'v@:@@')

    def testTooManyColons(self):
        # OK: the number of implied colons is larger than the actual number
        # of arguments
        #
        # (see 'testTooFewColons', same argument but other coding style)
        class OC_ImplProto_TooMany2 (NSObject):
            def run_to_completion(self):
                pass
        self.assertEqual(OC_ImplProto_TooMany2.run_to_completion.selector, b'run_to_completion')
        self.assertEqual(OC_ImplProto_TooMany2.run_to_completion.signature, b'v@:')

    def testImpliedColonTooFew(self):
        # BAD: a method that is obviously intented to be an objective-C method, but
        # has too few arguments.
        try:
            class OC_ImplProto_TooFew2 (NSObject):
                def setFoo_(self):
                    pass
            self.fail()

        except objc.BadPrototypeError:
            pass

        # OK: leading underscore won't be converted to a colon
        class OC_ImplProto_TooFew3 (NSObject):
            def _setFoo_(self, value):
                pass

    def testImpliedColonTooMany(self):
        # BAD: a method that is obviously intended to be an objective-C method,
        # but has too many arguments.

        try:
            class OC_ImplProto_TooMany1 (NSObject):
                def setFoo_(self, value, other):
                    pass
            self.fail()
        except objc.BadPrototypeError:
            pass

        try:
            class OC_ImplProto_TooMany2 (NSObject):
                def setFoo_(self, value, other=3):
                    pass
            self.fail()
        except objc.BadPrototypeError:
            pass

        try:
            class OC_ImplProto_TooMany3 (NSObject):
                def setFoo_(self, value, *rest):
                    pass
            self.fail()
        except objc.BadPrototypeError:
            pass

        try:
            class OC_ImplProto_TooMany4 (NSObject):
                def setFoo_(self, value, **rest):
                    pass
            self.fail()
        except objc.BadPrototypeError:
            pass


    def testMethodVariations(self):
        # OK: all methods with an implied signature are fine
        #
        # That is, as long as the implied selector doesn't contain
        # colons. If the implied selector does contain colons the
        # method must have the right number of parameters, that
        # should help us to avoid obvious errors.
        class OC_ImplProto_Variations (NSObject):
            def method1(self): pass
            def method2(self): return 1
            def method1_(self, arg): pass
            def methodWithX_andY_(self, x, y): pass
            def method_with_embedded_underscores(self, a): pass
            def __magic__(self): pass
            def _leadingColon(self): pass
            def _leadingColon_(self, arg): return 1
            def methodWithArg_(self, arg): pass

        # Check method signatures
        self.assertEqual(OC_ImplProto_Variations.method1.selector, b"method1")
        self.assertEqual(OC_ImplProto_Variations.method2.selector, b"method2")
        self.assertEqual(OC_ImplProto_Variations.method1_.selector, b"method1:")
        self.assertEqual(OC_ImplProto_Variations.methodWithX_andY_.selector, b"methodWithX:andY:")
        self.assertEqual(OC_ImplProto_Variations.method_with_embedded_underscores.selector, b"method_with_embedded_underscores")
        #self.assertEqual(OC_ImplProto_Variations.__magic__.selector, b"__magic__")
        self.assertEqual(OC_ImplProto_Variations._leadingColon.selector, b"_leadingColon")
        self.assertEqual(OC_ImplProto_Variations._leadingColon_.selector, b"_leadingColon:")
        self.assertEqual(OC_ImplProto_Variations.methodWithArg_.selector, b"methodWithArg:")

        # And the implied type signature
        self.assertEqual(OC_ImplProto_Variations.method1.signature, b"v@:")
        self.assertEqual(OC_ImplProto_Variations.method2.signature, b"@@:")
        self.assertEqual(OC_ImplProto_Variations.method1_.signature, b"v@:@")
        self.assertEqual(OC_ImplProto_Variations.methodWithX_andY_.signature, b"v@:@@")
        self.assertEqual(OC_ImplProto_Variations.method_with_embedded_underscores.signature, b"v@:@")
        #self.assertEqual(OC_ImplProto_Variations.__magic__.signature, b"v@:")
        self.assertEqual(OC_ImplProto_Variations._leadingColon.signature, b"v@:")
        self.assertEqual(OC_ImplProto_Variations._leadingColon_.signature, b"@@:@")
        self.assertEqual(OC_ImplProto_Variations.methodWithArg_.signature, b"v@:@")

if __name__ == "__main__":
    main()
