# -*- coding: utf-8 -*-
"""
Inputhook management for GUI event loop integration

Copyright (C) The IPython Development Team
Distributed under the terms of the modified BSD license
"""

# Stdlib imports
import ctypes
import os
import sys

QT_API = os.environ["QT_API"]

# Qt imports
if QT_API == 'pyqt5':
    from PyQt5 import QtCore
elif QT_API == 'pyqt':
    from PyQt4 import QtCore
elif QT_API == 'pyside':
    from PySide import QtCore


#-----------------------------------------------------------------------------
# Utilities
#-----------------------------------------------------------------------------
def _stdin_ready_posix():
    """Return True if there's something to read on stdin (posix version)."""
    infds, outfds, erfds = select.select([sys.stdin],[],[],0)
    return bool(infds)

def _stdin_ready_nt():
    """Return True if there's something to read on stdin (nt version)."""
    return msvcrt.kbhit()

def _stdin_ready_other():
    """Return True, assuming there's something to read on stdin."""
    return True


def _ignore_CTRL_C_posix():
    """Ignore CTRL+C (SIGINT)."""
    signal.signal(signal.SIGINT, signal.SIG_IGN)

def _allow_CTRL_C_posix():
    """Take CTRL+C into account (SIGINT)."""
    signal.signal(signal.SIGINT, signal.default_int_handler)

def _ignore_CTRL_C_other():
    """Ignore CTRL+C (not implemented)."""
    pass

def _allow_CTRL_C_other():
    """Take CTRL+C into account (not implemented)."""
    pass


if os.name == 'posix':
    import select
    import signal
    stdin_ready = _stdin_ready_posix
    ignore_CTRL_C = _ignore_CTRL_C_posix
    allow_CTRL_C = _allow_CTRL_C_posix
elif os.name == 'nt':
    import msvcrt
    stdin_ready = _stdin_ready_nt
    ignore_CTRL_C = _ignore_CTRL_C_other
    allow_CTRL_C = _allow_CTRL_C_other
else:
    stdin_ready = _stdin_ready_other
    ignore_CTRL_C = _ignore_CTRL_C_other
    allow_CTRL_C = _allow_CTRL_C_other


def clear_inputhook():
    """Set PyOS_InputHook to NULL and return the previous one"""
    pyos_inputhook_ptr = ctypes.c_void_p.in_dll(ctypes.pythonapi,
                                                "PyOS_InputHook")
    pyos_inputhook_ptr.value = ctypes.c_void_p(None).value
    allow_CTRL_C()

def get_pyos_inputhook():
    """Return the current PyOS_InputHook as a ctypes.c_void_p."""
    return ctypes.c_void_p.in_dll(ctypes.pythonapi, "PyOS_InputHook")

def set_pyft_callback(callback):
    callback = ctypes.PYFUNCTYPE(ctypes.c_int)(callback)
    return callback

def remove_pyqt_inputhook():
    if QT_API == 'pyqt' or QT_API == 'pyqt5':
        QtCore.pyqtRemoveInputHook()
    else:
        pass


#------------------------------------------------------------------------------
# Input hooks
#------------------------------------------------------------------------------
def qt4():
    """PyOS_InputHook python hook for Qt4.

    Process pending Qt events and if there's no pending keyboard
    input, spend a short slice of time (50ms) running the Qt event
    loop.

    As a Python ctypes callback can't raise an exception, we catch
    the KeyboardInterrupt and temporarily deactivate the hook,
    which will let a *second* CTRL+C be processed normally and go
    back to a clean prompt line.
    """
    try:
        allow_CTRL_C()
        app = QtCore.QCoreApplication.instance()
        if not app:
            return 0
        app.processEvents(QtCore.QEventLoop.AllEvents, 300)
        if not stdin_ready():
            # Generally a program would run QCoreApplication::exec()
            # from main() to enter and process the Qt event loop until
            # quit() or exit() is called and the program terminates.
            #
            # For our input hook integration, we need to repeatedly
            # enter and process the Qt event loop for only a short
            # amount of time (say 50ms) to ensure that Python stays
            # responsive to other user inputs.
            #
            # A naive approach would be to repeatedly call
            # QCoreApplication::exec(), using a timer to quit after a
            # short amount of time. Unfortunately, QCoreApplication
            # emits an aboutToQuit signal before stopping, which has
            # the undesirable effect of closing all modal windows.
            #
            # To work around this problem, we instead create a
            # QEventLoop and call QEventLoop::exec(). Other than
            # setting some state variables which do not seem to be
            # used anywhere, the only thing QCoreApplication adds is
            # the aboutToQuit signal which is precisely what we are
            # trying to avoid.
            timer = QtCore.QTimer()
            event_loop = QtCore.QEventLoop()
            timer.timeout.connect(event_loop.quit)
            while not stdin_ready():
                timer.start(50)
                event_loop.exec_()
                timer.stop()
    except KeyboardInterrupt:
        print("\nKeyboardInterrupt - Press Enter for new prompt")
    except: # NO exceptions are allowed to escape from a ctypes callback
        ignore_CTRL_C()
        from traceback import print_exc
        print_exc()
        print("Got exception from inputhook, unregistering.")
        clear_inputhook()
    finally:
        allow_CTRL_C()
    return 0
