# Author: Enric Tejedor CERN  04/2019

################################################################################
# Copyright (C) 1995-2019, Rene Brun and Fons Rademakers.                      #
# All rights reserved.                                                         #
#                                                                              #
# For the licensing terms see $ROOTSYS/LICENSE.                                #
# For the list of contributors see $ROOTSYS/README/CREDITS.                    #
################################################################################

import sys
import time

from cppyy.gbl import gSystem, gInterpreter, gEnv

from ROOT.libROOTPythonizations import InitApplication, InstallGUIEventInputHook


class PyROOTApplication(object):
    """
    Application class for PyROOT.
    Configures the interactive usage of ROOT from Python.
    """

    def __init__(self, config, is_ipython):
        # Construct a TApplication for PyROOT
        InitApplication(config.IgnoreCommandLineOptions)

        self._is_ipython = is_ipython

    @staticmethod
    def _ipython_config():
        # Integrate IPython >= 5 with ROOT's event loop
        # Check for new GUI events until there is some user input to process

        from IPython import get_ipython
        from IPython.terminal import pt_inputhooks
        from IPython.terminal.interactiveshell import TerminalInteractiveShell

        def inputhook(context):
            while not context.input_is_ready():
                gSystem.ProcessEvents()
                time.sleep(0.01)

        pt_inputhooks.register('ROOT', inputhook)

        ipy = get_ipython()

        # Only the TerminalInteractiveShell will use the input hooks that are
        # registered via terminal.pt_inputhooks.
        if ipy and isinstance(ipy, TerminalInteractiveShell):
            get_ipython().run_line_magic('gui', 'ROOT')

    @staticmethod
    def _inputhook_config():
        # PyOS_InputHook-based mechanism
        # Point to a function which will be called when Python's interpreter prompt
        # is about to become idle and wait for user input from the terminal
        InstallGUIEventInputHook()

    @staticmethod
    def _set_display_hook():
        # Set the display hook

        orig_dhook = sys.displayhook

        def displayhook(v):
            # sys.displayhook is called on the result of evaluating an expression entered
            # in an interactive Python session.
            # Therefore, this function will call EndOfLineAction after each interactive
            # command (to update display etc.)
            gInterpreter.EndOfLineAction()
            return orig_dhook(v)

        sys.displayhook = displayhook

    def init_graphics(self):
        """Configure ROOT graphics to be used interactively"""

        # Note that we only end up in this function if gROOT.IsBatch() is false
        import __main__

        if self._is_ipython and "IPython" in sys.modules and sys.modules["IPython"].version_info[0] >= 5:
            # ipython and notebooks, register our event processing with their hooks
            self._ipython_config()
        elif sys.flags.interactive == 1 or not hasattr(__main__, "__file__") or gSystem.InheritsFrom("TMacOSXSystem"):
            # Python in interactive mode, use the PyOS_InputHook to call our event processing
            # - sys.flags.interactive checks for the -i flags passed to python
            # - __main__ does not have the attribute __file__ if the Python prompt is started directly
            # - MacOS does not allow to run a second thread to process events, fall back to the input hook
            self._inputhook_config()
        else:
            # Python in script mode, start a separate thread for the event processing

            # indicate that ProcessEvents called in different thread, let ignore thread id checks in RWebWindow
            gEnv.SetValue("WebGui.ExternalProcessEvents", "yes")

            def _process_root_events(self):
                while self.keep_polling:
                    gSystem.ProcessEvents()
                    time.sleep(0.01)

            import threading

            self.keep_polling = True  # Used to shut down the thread safely at teardown time
            update_thread = threading.Thread(None, _process_root_events, None, (self,))
            self.process_root_events = update_thread  # The thread is joined at teardown time
            update_thread.daemon = True
            update_thread.start()

        self._set_display_hook()
