from functools import cached_property
from typing import Optional

import axipy.cpp_app
import axipy.cpp_core_core
import axipy.cpp_cs
import axipy.cpp_gui
import axipy.cpp_render


class _ShadowManager:
    _error_msg_axipy_init = "axipy is not initialized"

    def __init__(self) -> None:
        self._cs_factory: Optional[axipy.cpp_cs.ShadowCoordSysFactory] = None
        self._core: Optional[axipy.cpp_core_core.Core] = None
        self._render: Optional[axipy.cpp_render.Render] = None
        self._gui: Optional[axipy.cpp_gui.ShadowGui] = None
        self._state_observer: Optional[axipy.cpp_core_core.StateObserver] = None

    @cached_property
    def menu_bar(self) -> axipy.cpp_gui.ShadowMenuBar:
        return axipy.cpp_gui.ShadowMenuBar(self.gui)

    @property
    def cs_factory(self) -> axipy.cpp_cs.ShadowCoordSysFactory:
        if self._cs_factory is None:
            raise RuntimeError(self._error_msg_axipy_init)
        return self._cs_factory

    @cs_factory.setter
    def cs_factory(self, value: axipy.cpp_cs.ShadowCoordSysFactory) -> None:
        self._cs_factory = value

    @property
    def core(self) -> axipy.cpp_core_core.Core:
        if self._core is None:
            raise RuntimeError(self._error_msg_axipy_init)
        return self._core

    @core.setter
    def core(self, value: axipy.cpp_core_core.Core) -> None:
        self._core = value

    @property
    def render(self) -> axipy.cpp_render.Render:
        if self._render is None:
            raise RuntimeError(self._error_msg_axipy_init)
        return self._render

    @render.setter
    def render(self, value: axipy.cpp_render.Render) -> None:
        self._render = value

    @property
    def gui(self) -> axipy.cpp_gui.ShadowGui:
        if self._gui is None:
            raise RuntimeError(self._error_msg_axipy_init)
        return self._gui

    @gui.setter
    def gui(self, value: axipy.cpp_gui.ShadowGui) -> None:
        self._gui = value

    @property
    def state_observer(self) -> axipy.cpp_core_core.StateObserver:
        if self._state_observer is None:
            raise RuntimeError(self._error_msg_axipy_init)
        return self._state_observer

    @state_observer.setter
    def state_observer(self, value: axipy.cpp_core_core.StateObserver) -> None:
        self._state_observer = value

    @cached_property
    def shadow_io(self) -> axipy.cpp_core_core.ShadowIO:
        shadow_io = axipy.cpp_core_core.ShadowIO(self.core)
        shadow_io.setDefaultCatalog(self.gui.catalog())
        return shadow_io

    @cached_property
    def selection_manager(self) -> axipy.cpp_gui.ShadowSelectionManager:
        return axipy.cpp_gui.ShadowSelectionManager(self.gui)

    @cached_property
    def widget_manager(self) -> axipy.cpp_gui.ShadowWidgetManager:
        return axipy.cpp_gui.ShadowWidgetManager(self.gui)

    @cached_property
    def active_tool_panel_mediator(self) -> axipy.cpp_gui.ShadowActiveToolPanelMediator:
        return axipy.cpp_gui.ShadowActiveToolPanelMediator(axipy.gui_instance._shadow)

    # noinspection PyStatementEffect
    def ensure_axioma_is_initialized(self) -> None:
        self.cs_factory
        self.core
        self.render
        self.gui
        self.state_observer


_shadow_manager: _ShadowManager = _ShadowManager()
