"""
Define a category on NSObject with some useful methods.
"""
import objc
import sys

if sys.version_info[0] == 2:
    def _str(v): return v
    exec("""\
def _raise(exc_type, exc_value, exc_trace):
    raise exc_type, exc_value, exc_trace
""")
else:
    def _str(v): 
        if isinstance(v, str):
            return v
        return v.decode('ascii')

    def _raise(exc_type, exc_value, exc_trace):
        raise exc_type(exc_value).with_traceback(exc_trace)


NSObject = objc.lookUpClass('NSObject')

class NSObject (objc.Category(NSObject)):

    @objc.namedSelector(b"_pyobjc_performOnThread:")
    def _pyobjc_performOnThread_(self, callinfo):
        try:
            sel, arg = callinfo
            # XXX: PyObjC's methodForSelector implementation doesn't work
            # with Python methods, using getattr instead
            #m = self.methodForSelector_(sel)
            m = getattr(self, _str(sel))
            m(arg)
        except:
            import traceback
            traceback.print_exc(file=sys.stderr)

    @objc.namedSelector(b"_pyobjc_performOnThreadWithResult:")
    def _pyobjc_performOnThreadWithResult_(self, callinfo):
        try:
            sel, arg, result = callinfo
            #m = self.methodForSelector_(sel)
            m = getattr(self, _str(sel))
            r = m(arg)
            result.append((True, r))
        except:
            result.append((False, sys.exc_info()))


    if hasattr(NSObject, "performSelector_onThread_withObject_waitUntilDone_"):
        @objc.namedSelector(b"pyobjc_performSelector:onThread:withObject:waitUntilDone:")
        def pyobjc_performSelector_onThread_withObject_waitUntilDone_(
                self, aSelector, thread, arg, wait):
            """
            A version of performSelector:onThread:withObject:waitUntilDone: that
            will log exceptions in the called method (instead of aborting the
            NSRunLoop on the other thread).
            """
            self.performSelector_onThread_withObject_waitUntilDone_(
                    b'_pyobjc_performOnThread:', thread, (aSelector, arg), wait)

        @objc.namedSelector(b"pyobjc_performSelector:onThread:withObject:waitUntilDone:modes:")
        def pyobjc_performSelector_onThread_withObject_waitUntilDone_modes_(
                self, aSelector, thread, arg, wait, modes):
            """
            A version of performSelector:onThread:withObject:waitUntilDone:modes:
            that will log exceptions in the called method (instead of aborting the
            NSRunLoop on the other thread).
            """
            self.performSelector_onThread_withObject_waitUntilDone_modes_(
                b'_pyobjc_performOnThread:', thread, (aSelector, arg), wait, modes)

    @objc.namedSelector(b"pyobjc_performSelector:withObject:afterDelay:")
    def pyobjc_performSelector_withObject_afterDelay_(
            self, aSelector, arg, delay):
        """
        A version of performSelector:withObject:afterDelay:
        that will log exceptions in the called method (instead of aborting the
        NSRunLoop).
        """
        self.performSelector_withObject_afterDelay_(
            b'_pyobjc_performOnThread:', (aSelector, arg), delay)

    @objc.namedSelector(b"pyobjc_performSelector:withObject:afterDelay:inModes:")
    def pyobjc_performSelector_withObject_afterDelay_inModes_(
            self, aSelector, arg, delay, modes):
        """
        A version of performSelector:withObject:afterDelay:inModes:
        that will log exceptions in the called method (instead of aborting the
        NSRunLoop).
        """
        self.performSelector_withObject_afterDelay_inModes_(
            b'_pyobjc_performOnThread:', (aSelector, arg), delay, modes)

    if hasattr(NSObject, "performSelectorInBackground_withObject_"):
        @objc.namedSelector(b"pyobjc_performSelectorInBackground:withObject:")
        def pyobjc_performSelectorInBackground_withObject_(
                self, aSelector, arg):
            """
            A version of performSelectorInBackground:withObject:
            that will log exceptions in the called method (instead of aborting the
            NSRunLoop).
            """
            self.performSelectorInBackground_withObject_(
                b'_pyobjc_performOnThread:', (aSelector, arg))

    @objc.namedSelector(b"pyobjc_performSelectorOnMainThread:withObject:waitUntilDone:")
    def pyobjc_performSelectorOnMainThread_withObject_waitUntilDone_(
            self, aSelector, arg, wait):
        """
        A version of performSelectorOnMainThread:withObject:waitUntilDone:
        that will log exceptions in the called method (instead of aborting the
        NSRunLoop in the main thread).
        """
        self.performSelectorOnMainThread_withObject_waitUntilDone_(
            b'_pyobjc_performOnThread:', (aSelector, arg), wait)

    @objc.namedSelector(b"pyobjc_performSelectorOnMainThread:withObject:waitUntilDone:modes:")
    def pyobjc_performSelectorOnMainThread_withObject_waitUntilDone_modes_(
            self, aSelector, arg, wait, modes):
        """
        A version of performSelectorOnMainThread:withObject:waitUntilDone:modes:
        that will log exceptions in the called method (instead of aborting the
        NSRunLoop in the main thread).
        """
        self.performSelectorOnMainThread_withObject_waitUntilDone_modes_(
            b'_pyobjc_performOnThread:', (aSelector, arg), wait, modes)


    # And some a some versions that return results

    @objc.namedSelector(b"pyobjc_performSelectorOnMainThread:withObject:modes:")
    def pyobjc_performSelectorOnMainThread_withObject_modes_(
            self, aSelector, arg, modes):
        """
        Simular to performSelectorOnMainThread:withObject:waitUntilDone:modes:,
        but:

        - always waits until done
        - returns the return value of the called method
        - if the called method raises an exception, this will raise the same
           exception
        """
        result = []
        self.performSelectorOnMainThread_withObject_waitUntilDone_modes_(
            b'_pyobjc_performOnThreadWithResult:',
            (aSelector, arg, result), True, modes)
        isOK, result = result[0]

        if isOK:
            return result
        else:
            exc_type, exc_value, exc_trace = result
            _raise(exc_type, exc_value, exc_trace)

    @objc.namedSelector(b"pyobjc_performSelectorOnMainThread:withObject:")
    def pyobjc_performSelectorOnMainThread_withObject_(
            self, aSelector, arg):
        result = []
        self.performSelectorOnMainThread_withObject_waitUntilDone_(
            b'_pyobjc_performOnThreadWithResult:',
            (aSelector, arg, result), True)
        isOK, result = result[0]

        if isOK:
            return result
        else:
            exc_type, exc_value, exc_trace = result
            _raise(exc_type, exc_value, exc_trace)

    if hasattr(NSObject, "performSelector_onThread_withObject_waitUntilDone_"):
        # These methods require Leopard, don't define them if the
        # platform functionality isn't present.

        @objc.namedSelector(b"pyobjc_performSelector:onThread:withObject:modes:")
        def pyobjc_performSelector_onThread_withObject_modes_(
                self, aSelector, thread, arg, modes):
            result = []
            self.performSelector_onThread_withObject_waitUntilDone_modes_(
                b'_pyobjc_performOnThreadWithResult:', thread,
                (aSelector, arg, result), True, modes)
            isOK, result = result[0]

            if isOK:
                return result
            else:
                exc_type, exc_value, exc_trace = result
                _raise(exc_type, exc_value, exc_trace)

        @objc.namedSelector(b"pyobjc_performSelector:onThread:withObject:")
        def pyobjc_performSelector_onThread_withObject_(
                self, aSelector, thread, arg):
            result = []
            self.performSelector_onThread_withObject_waitUntilDone_(
                b'_pyobjc_performOnThreadWithResult:', thread,
                (aSelector, arg, result), True)
            isOK, result = result[0]

            if isOK:
                return result
            else:
                exc_type, exc_value, exc_trace = result
                _raise(exc_type, exc_value, exc_trace)


del NSObject
