from __future__ import nested_scopes

from _pydev_imps._pydev_saved_modules import threading
from _pydevd_bundle.pydevd_constants import IS_PY38
import os

def set_trace_in_qt():
    from _pydevd_bundle.pydevd_comm import get_global_debugger
    debugger = get_global_debugger()
    if debugger is not None:
        threading.current_thread()  # Create the dummy thread for qt.
        debugger.enable_tracing()


_patched_qt = False
def patch_qt(qt_support_mode):
    '''
    This method patches qt (PySide, PyQt4, PyQt5) so that we have hooks to set the tracing for QThread.
    '''
    if not qt_support_mode:
        return

    if qt_support_mode is True or qt_support_mode == 'True':
        # do not break backward compatibility
        qt_support_mode = 'auto'

    if qt_support_mode == 'auto':
        qt_support_mode = os.getenv('PYDEVD_PYQT_MODE', 'auto')

    # Avoid patching more than once
    global _patched_qt
    if _patched_qt:
        return

    _patched_qt = True

    if qt_support_mode == 'auto':

        patch_qt_on_import = None
        try:
            if IS_PY38:
                raise ImportError
            import PySide6
            qt_support_mode = 'pyside6'
        except:
            try:
                # PY-50959
                # Problem:
                # 1. We have Python 3.8;
                # 2. PyQt compatible = Auto or PySide2;
                # 3. We try to import numpy, we get "AttributeError: module 'numpy.core' has no attribute 'numerictypes'"
                #
                # Solution:
                # We decided to turn off patching for PySide2 if we have Python 3.8
                # Here we skip 'import PySide2' and keep trying to import another qt libraries
                if IS_PY38:
                    raise ImportError
                import PySide2  # @UnresolvedImport @UnusedImport
                qt_support_mode = 'pyside2'
            except:
                try:
                    import Pyside  # @UnresolvedImport @UnusedImport
                    qt_support_mode = 'pyside'
                except:
                    try:
                        import PyQt6  # @UnresolvedImport @UnusedImport
                        qt_support_mode = 'pyqt6'
                    except:
                        try:
                            import PyQt5  # @UnresolvedImport @UnusedImport
                            qt_support_mode = 'pyqt5'
                        except:
                            try:
                                import PyQt4  # @UnresolvedImport @UnusedImport
                                qt_support_mode = 'pyqt4'
                            except:
                                return

    if qt_support_mode == 'pyside6':
        if IS_PY38:
            return
        try:
            import PySide6.QtCore  # @UnresolvedImport
            _internal_patch_qt(PySide6.QtCore, qt_support_mode)
        except:
            return
    elif qt_support_mode == 'pyside2':
        # PY-50959
        # We can get here only if PyQt compatible = PySide2, in this case we should return
        # See comment above about PY-50959
        if IS_PY38:
            return
        try:
            import PySide2.QtCore  # @UnresolvedImport
            _internal_patch_qt(PySide2.QtCore, qt_support_mode)
        except:
            return

    elif qt_support_mode == 'pyside':
        try:
            import PySide.QtCore  # @UnresolvedImport
            _internal_patch_qt(PySide.QtCore, qt_support_mode)
        except:
            return

    elif qt_support_mode == 'pyqt6':
        try:
            import PyQt6.QtCore  # @UnresolvedImport
            _internal_patch_qt(PyQt6.QtCore)
        except:
            return

    elif qt_support_mode == 'pyqt5':
        try:
            import PyQt5.QtCore  # @UnresolvedImport
            _internal_patch_qt(PyQt5.QtCore)
        except:
            return

    elif qt_support_mode == 'pyqt4':
        # Ok, we have an issue here:
        # PyDev-452: Selecting PyQT API version using sip.setapi fails in debug mode
        # http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html
        # Mostly, if the user uses a different API version (i.e.: v2 instead of v1),
        # that has to be done before importing PyQt4 modules (PySide/PyQt5 don't have this issue
        # as they only implements v2).
        patch_qt_on_import = 'PyQt4'
        def get_qt_core_module():
            import PyQt4.QtCore  # @UnresolvedImport
            return PyQt4.QtCore
        _patch_import_to_patch_pyqt_on_import(patch_qt_on_import, get_qt_core_module)

    else:
        raise ValueError('Unexpected qt support mode: %s' % (qt_support_mode,))


def _patch_import_to_patch_pyqt_on_import(patch_qt_on_import, get_qt_core_module):
    # I don't like this approach very much as we have to patch __import__, but I like even less
    # asking the user to configure something in the client side...
    # So, our approach is to patch PyQt4 right before the user tries to import it (at which
    # point he should've set the sip api version properly already anyways).

    dotted = patch_qt_on_import + '.'
    original_import = __import__

    from _pydev_imps._pydev_sys_patch import patch_sys_module, patch_reload, cancel_patches_in_sys_module

    patch_sys_module()
    patch_reload()

    def patched_import(name, *args, **kwargs):
        if patch_qt_on_import == name or name.startswith(dotted):
            builtins.__import__ = original_import
            cancel_patches_in_sys_module()
            _internal_patch_qt(get_qt_core_module()) # Patch it only when the user would import the qt module
        return original_import(name, *args, **kwargs)

    import sys
    if sys.version_info[0] >= 3:
        import builtins # Py3
    else:
        import __builtin__ as builtins

    builtins.__import__ = patched_import


def _internal_patch_qt(QtCore, qt_support_mode='auto'):
    _original_thread_init = QtCore.QThread.__init__
    _original_runnable_init = QtCore.QRunnable.__init__
    _original_QThread = QtCore.QThread

    class FuncWrapper:
        def __init__(self, original):
            self._original = original

        def __call__(self, *args, **kwargs):
            set_trace_in_qt()
            return self._original(*args, **kwargs)

    class StartedSignalWrapper(QtCore.QObject):  # Wrapper for the QThread.started signal

        try:
            _signal = QtCore.Signal()  # @UndefinedVariable
        except:
            _signal = QtCore.pyqtSignal()  # @UndefinedVariable

        def __init__(self, thread, original_started):
            QtCore.QObject.__init__(self)
            self.thread = thread
            self.original_started = original_started
            if qt_support_mode in ('pyside', 'pyside2', 'pyside6'):
                self._signal = original_started
            else:
                self._signal.connect(self._on_call)
                self.original_started.connect(self._signal)

        def connect(self, func, *args, **kwargs):
            _is_signal_instance = False
            if hasattr(QtCore, 'SignalInstance'):
                _is_signal_instance = isinstance(func, QtCore.SignalInstance)
            if qt_support_mode in ('pyside', 'pyside2', 'pyside6') and not _is_signal_instance:
                return self._signal.connect(FuncWrapper(func), *args, **kwargs)
            else:
                return self._signal.connect(func, *args, **kwargs)

        def disconnect(self, *args, **kwargs):
            return self._signal.disconnect(*args, **kwargs)

        def emit(self, *args, **kwargs):
            return self._signal.emit(*args, **kwargs)

        def _on_call(self, *args, **kwargs):
            set_trace_in_qt()

    class ThreadWrapper(QtCore.QThread):  # Wrapper for QThread

        def __init__(self, *args, **kwargs):
            _original_thread_init(self, *args, **kwargs)

            # In PyQt5 the program hangs when we try to call original run method of QThread class.
            # So we need to distinguish instances of QThread class and instances of QThread inheritors.
            if self.__class__.run == _original_QThread.run:
                self.run = self._exec_run
            else:
                self._original_run = self.run
                self.run = self._new_run
            self._original_started = self.started
            self.started = StartedSignalWrapper(self, self.started)

        def _exec_run(self):
            set_trace_in_qt()
            self.exec_()
            return None

        def _new_run(self):
            set_trace_in_qt()
            return self._original_run()

    class RunnableWrapper(QtCore.QRunnable):  # Wrapper for QRunnable

        def __init__(self, *args):
            _original_runnable_init(self, *args)

            self._original_run = self.run
            self.run = self._new_run


        def _new_run(self):
            set_trace_in_qt()
            return self._original_run()

    QtCore.QThread = ThreadWrapper
    QtCore.QRunnable = RunnableWrapper
