import logging
import os
import sys
from contextlib import contextmanager
from typing import ContextManager

import wx

from clarabot_nano.control.client import WxCoreInterface, WxCoreBuffer
from clarabot_nano.control.utility import find_last_children_of_type
from clarabot_nano.control.window import ControlWindow

NANO_INFO_LOGGER = logging.getLogger("nano/info")


class ControlApplication(wx.App):
    """
    This object form a small framework for the setup of the control application. It's primary reason for
    existing is to provide a customization interface for testing.
    """

    @classmethod
    @contextmanager
    def core_buffer(cls):
        """
        The core interface is special because it has to be created and started before most of the application.
        At shutdown it is also the last object that is alive.
        """
        core_buffer = WxCoreBuffer()
        NANO_INFO_LOGGER.info("Joined Core Instance, starting... (parent: %s)", os.getppid())
        try:
            yield core_buffer
        finally:
            core_buffer.close()

    @contextmanager
    def _core_interface(self, core_buffer: WxCoreBuffer):
        """
        The core interface is special because it has to be created and started before most of the application.
        At shutdown it is also the last object that is alive.
        """
        self.__core_interface = WxCoreInterface(core_buffer)
        try:
            yield self.__core_interface
        finally:
            self.__core_interface.close()
            self.__core_interface = None

    def _create_window(self, core_interface: WxCoreInterface, *args, **kwargs) -> ControlWindow:
        return ControlWindow(core_interface, *args, **kwargs)

    @contextmanager
    def _setup(self, core_interface: WxCoreInterface) -> ContextManager[ControlWindow]:
        """
        Setup the application's UI elements.
        """
        self.__main_window = self._create_window(core_interface)
        NANO_INFO_LOGGER.info("Entering main event loop...")
        try:
            yield self.__main_window
        finally:
            NANO_INFO_LOGGER.info("Shutting down, bye!")
            self.__main_window = None

    def __init__(self, core_buffer: WxCoreBuffer):
        wx.App.__init__(self, False)
        self.__core_buffer = core_buffer
        self.__core_interface = None
        self.__main_window = None
        self.SetAppName("clarabot-nano")
        self.SetAppDisplayName("Clarabot Nano")
        # NOTE: The extra timer is necessary sometimes (on darwin for example), because the chain-closed dialogs
        # sometimes are still in the parent's child references. Calling destroy on them a second time will not
        # trigger the destroyed callback. This would halt the unwinding and we can't detect if a child is being
        # destroyed already either.
        self._smart_exit_timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.smart_exit_main_loop, self._smart_exit_timer)
        # OS session event handling. This is only implemented for Windows and macOS in wxWidgets.
        if sys.platform.startswith("darwin"):
            pass  # NOTE: The default handler implemented in wxWidgets is bound to closing the window which is fine.
        elif sys.platform.startswith("win"):
            self.Bind(wx.EVT_QUERY_END_SESSION, self._on_query_session_end)
        self.Bind(wx.EVT_END_SESSION, self._on_session_end)

    def _on_query_session_end(self, event: wx.CloseEvent):  # NOTE: Windows only.
        w = self.__main_window
        if w and w.query_session_end_veto():
            event.Veto()

    def _on_session_end(self, event: wx.CloseEvent):
        event.Skip(False)
        # NOTE: The core may have been notified about the shutdown by the OS as well.
        core_interface = self.__core_interface
        if core_interface:
            try:
                core_interface.request_shutdown(explicit_user_request=False)
            except Exception:
                pass  # Assuming that the core is shutting down as well.
        # Exit even if the core has not requested that yet.
        self.smart_exit_main_loop()

    def OnRun(self):
        with self._core_interface(self.__core_buffer) as core_interface:
            with self._setup(core_interface) as w:
                self.SetTopWindow(w)
                # Run application/main event loop.
                return wx.App.OnRun(self)

    def smart_exit_main_loop(self, *args):
        """
        Handle modal dialogs before issuing destruction of main window.
        """
        w = self.GetTopWindow()
        exit_main = True
        if w:
            for d in find_last_children_of_type(w, wx.Dialog):
                self.Bind(wx.EVT_WINDOW_DESTROY, self.smart_exit_main_loop, d)
                d.EndModal(wx.ID_ABORT)  # abort instead of cancel so the dialog caller can differentiate
                exit_main = False
            if exit_main:  # the main window must be explicitly destroyed as well, if that is the only one remaining
                self.Bind(wx.EVT_WINDOW_DESTROY, self.smart_exit_main_loop, w)
                w.Destroy()
                exit_main = False
        if exit_main:
            self._smart_exit_timer.Stop()
            self.ExitMainLoop()
        else:
            self._smart_exit_timer.StartOnce(150)
