import sys
import os
from IPython.external.qt_for_kernel import QtCore, QtGui, enum_helper
from IPython import get_ipython

# If we create a QApplication, QEventLoop, or a QTimer, keep a reference to them
# so that they don't get garbage collected or leak memory when created multiple times.
_appref = None
_eventloop = None
_timer = None
_already_warned = False


def _exec(obj):
    # exec on PyQt6, exec_ elsewhere.
    obj.exec() if hasattr(obj, "exec") else obj.exec_()


def _reclaim_excepthook():
    shell = get_ipython()
    if shell is not None:
        sys.excepthook = shell.excepthook


def inputhook(context):
    global _appref, _eventloop, _timer
    app = QtCore.QCoreApplication.instance()
    if not app:
        if sys.platform == 'linux':
            if not os.environ.get('DISPLAY') \
                    and not os.environ.get('WAYLAND_DISPLAY'):
                import warnings
                global _already_warned
                if not _already_warned:
                    _already_warned = True
                    warnings.warn(
                        'The DISPLAY or WAYLAND_DISPLAY environment variable is '
                        'not set or empty and Qt5 requires this environment '
                        'variable. Deactivate Qt5 code.'
                    )
                return
        try:
            QtCore.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
        except AttributeError:  # Only for Qt>=5.6, <6.
            pass
        try:
            QtCore.QApplication.setHighDpiScaleFactorRoundingPolicy(
                QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
            )
        except AttributeError:  # Only for Qt>=5.14.
            pass
        _appref = app = QtGui.QApplication([" "])

        # "reclaim" IPython sys.excepthook after event loop starts
        # without this, it defaults back to BaseIPythonApplication.excepthook
        # and exceptions in the Qt event loop are rendered without traceback
        # formatting and look like "bug in IPython".
        QtCore.QTimer.singleShot(0, _reclaim_excepthook)

    if _eventloop is None:
        _eventloop = QtCore.QEventLoop(app)

    if sys.platform == 'win32':
        # The QSocketNotifier method doesn't appear to work on Windows.
        # Use polling instead.
        if _timer is None:
            _timer = QtCore.QTimer()
            _timer.timeout.connect(_eventloop.quit)
        while not context.input_is_ready():
            # NOTE: run the event loop, and after 10 ms, call `quit` to exit it.
            _timer.start(10)  # 10 ms
            _exec(_eventloop)
            _timer.stop()
    else:
        # On POSIX platforms, we can use a file descriptor to quit the event
        # loop when there is input ready to read.
        notifier = QtCore.QSocketNotifier(
            context.fileno(), enum_helper("QtCore.QSocketNotifier.Type").Read
        )
        try:
            # connect the callback we care about before we turn it on
            # lambda is necessary as PyQT inspect the function signature to know
            # what arguments to pass to. See https://github.com/ipython/ipython/pull/12355
            notifier.activated.connect(lambda: _eventloop.exit())
            notifier.setEnabled(True)
            # only start the event loop we are not already flipped
            if not context.input_is_ready():
                _exec(_eventloop)
        finally:
            notifier.setEnabled(False)
