"""
Define a category on NSObject with some useful methods.
"""

import sys

import objc


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
            m = getattr(self, _str(sel))
            m(arg)
        except:  # noqa: E722, B001
            import traceback

            traceback.print_exc(file=sys.stderr)

    @objc.namedSelector(b"_pyobjc_performOnThreadWithResult:")
    def _pyobjc_performOnThreadWithResult_(self, callinfo):
        try:
            sel, arg, result = callinfo
            m = getattr(self, _str(sel))
            r = m(arg)
            result.append((True, r))
        except:  # noqa: E722, B001
            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
    ):
        """
        Similar 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
