from functools import cached_property
from typing import Union

import axipy
from axipy.cpp_core_core import DefaultKeys as _ShadowDefaultKeys
from axipy.cpp_gui import (
    AcceptableToolPanelHandler,
    ActiveToolPanelHandlerBase,
    CustomToolPanelHandler,
    ShadowActiveToolPanelMediator,
    ToolPanelHandlerType,
)
from axipy.da import Observer
from PySide2.QtCore import QObject, Signal
from PySide2.QtWidgets import QWidget

from .gui_class import gui_instance

__all__ = [
    "AxipyActiveToolPanelHandlerBase",
    "AxipyAcceptableActiveToolHandler",
    "AxipyCustomActiveToolPanelHandler",
    "ActiveToolPanel",
]


class AxipyActiveToolPanelHandlerBase(QObject):
    """
    Базовый класс обработчика панели активного инструмента.
    """

    def __init__(self, shadow_handler) -> None:
        super().__init__()
        self._shadow = shadow_handler
        self._shadow_widget = None

    def __del__(self) -> None:
        self.deactivate()

    def activate(self) -> None:
        """
        Показывает пользовательский графический элемент в панели активного инструмента.
        """
        self._shadow.setWidget(self._shadow_widget)
        self._shadow.activate()

    def deactivate(self) -> None:
        """
        Скрывает пользовательский графический элемент из панели активного инструмента.
        """
        self._shadow.deactivate()

    def set_panel_title(self, title: str):
        """
        Устанавливает заголовок панели активного инструмента.

        Args:
            title: Новый заголовок.
        """
        self._shadow.setPanelTitle(title)

    @property
    def widget(self) -> QWidget:
        """
        Возвращает пользовательский графический элемент.

        Return:
            Переданный ранее пользовательский графический элемент.
        """
        if self._shadow_widget is not None:
            return self._shadow_widget
        else:
            return self._shadow.widget()

    def set_widget(self, widget: QWidget):
        """
        Пользовательский графический элемент будет помещен в панель активного
        инструмента при активации обработчика. Владение графическим элементом
        передаётся обработчику. Это значит, что не следует использовать и сохранять
        где-либо ссылку на этот объект. Для получения графического элемента обратно
        используйте :meth:`axipy.AxipyActiveToolPanelHandlerBase.widget`.
        """
        self._shadow_widget = widget

    def set_observer(self, observer_id: Observer):
        """
        Метод устанавливает наблюдателя. Если наблюдатель сигнализирует, что
        условия доступности кнопки нарушены, то панель активного инструмента
        сразу же закроется.

        Args:
            observer_id: Идентификатор наблюдателя для управления видимостью и доступностью

        .. seealso:: Наблюдатели за состоянием инструмента :class:`axipy.ObserverManager`
        """
        is_supported = isinstance(observer_id, (str, _ShadowDefaultKeys.Key, Observer))
        if not is_supported:
            raise TypeError(axipy.tr(f"Неподдерживаемый тип наблюдателя {type(observer_id)}"))
        if isinstance(observer_id, _ShadowDefaultKeys.Key):
            observer_id = observer_id.name.decode()
        elif isinstance(observer_id, Observer):
            observer_id = observer_id._name
        self._shadow.setObserver(observer_id)

    @property
    def activated(self) -> Signal:
        """
        Сигнал испускается когда обработчик панели активного инструмента становится активным.

        :rtype: Signal[]
        """
        return self._shadow.activated

    @property
    def deactivated(self) -> Signal:
        """
        Сигнал испускается перед тем как обработчик панели активного
        инструмента перестает быть активным.

        :rtype: Signal[]
        """
        return self._shadow.deactivated

    @property
    def panel_was_closed(self) -> Signal:
        """
        Сигнал испускается после закрытия панели активного инструмента.

        :rtype: Signal[]
        """
        return self._shadow.panelClosed


class AxipyAcceptableActiveToolHandler(AxipyActiveToolPanelHandlerBase):
    """
    Обработчик панели активного инструмента, который предоставляет по умолчанию кнопку Применить.
    При нажатии на эту кнопку испускается сигнал :meth:`axipy.AxipyAcceptableActiveToolHandler.accepted`.
    """

    def __init__(self, shadow_handler: AcceptableToolPanelHandler) -> None:
        super().__init__(shadow_handler)

    @property
    def accepted(self) -> Signal:
        """
        Отсылается после того как пользователь нажал кнопку "Применить"
        в панели активного инструмента.

        :rtype: Signal[]
        """
        return self._shadow.accepted

    def try_enable(self) -> None:
        """
        Включает доступность блока с кнопкой 'Применить' если наблюдатель,
        связанный с панелью активного инструмента, подтверждает доступность.
        """
        self._shadow.enable()

    def disable(self) -> None:
        """
        Отключает доступность блока с кнопкой Применить. Если инструмент
        запускает фоновые задачи с использованием :class:`axipy.concurrent.TaskManager`,
        то следует вызвать эту функцию перед началом выполнения задачи. Иначе у пользователя
        может быть возможность добавить множество одинаковых задач, несколько раз
        нажав на кнопку.
        """
        self._shadow.disable()


class AxipyCustomActiveToolPanelHandler(AxipyActiveToolPanelHandlerBase):
    """
    Обработчик панели активного инструмента который не предоставляет никаких встроенных
    по умолчанию элементов управления.
    """

    def __init__(self, shadow_handler: CustomToolPanelHandler) -> None:
        super().__init__(shadow_handler)


class ActiveToolPanel(QObject):
    """
    Сервис предоставляющий доступ к панели активного инструмента.

    .. literalinclude:: /../../tests/doc_examples/gui/test_example_active_tool_panel.py
        :caption: Пример использования.
        :pyobject: test_make_handler
        :lines: 2-
        :dedent: 4

    Чтобы отобразить переданный ранее графический элемент нужно вызвать
    :meth:`axipy.AxipyActiveToolPanelHandlerBase.activate`. Например при
    нажатии на пользовательскую кнопку.

    Панель активного инструмента созданная через :meth:`axipy.ActiveToolPanel.make_acceptable`
    по умолчанию содержит кнопку "Применить".  По нажатию на эту кнопку
    отсылается сигнал :meth:`axipy.AxipyAcceptableActiveToolHandler.accepted`
    который можно обработать в пользовательском инструменте.

    Панель активного инструмента созданная через :meth:`axipy.ActiveToolPanel.make_custom`
    представляет из себя пустой контейнер в который можно поместить пользовательский
    графический элемент. Это дает больше свободы для реализации управления панелью
    активного инструмента.

    Переданный идентификатор наблюдателя используется для управления видимостью и
    доступностью панели. Панель активного инструмента сразу закроется как только
    наблюдатель вернет False.
    """

    def __init__(self) -> None:
        super().__init__(None)

    def make_acceptable(
        self, title: str, observer_id: Observer, widget: QWidget = None
    ) -> AxipyAcceptableActiveToolHandler:
        """
        Создает экземпляр обработчика, через который можно взаимодействовать с
        панелью активного инструмента. По умолчанию добавляются кнопки "Применить/Отменить".

        Args:
            title: Заголовок панели активного инструмента. Обычно это название инструмента.
            observer_id: Идентификатор наблюдателя для управления видимостью и доступностью.
            widget: Пользовательский виджет который будет отображаться в панели активного инструмента.
        """
        return self.__make_handler(ToolPanelHandlerType.WithAcceptButtonBox, title, observer_id, widget)

    def make_custom(
        self, title: str, observer_id: Observer, widget: QWidget = None
    ) -> AxipyCustomActiveToolPanelHandler:
        """
        Создает экземпляр обработчика панели активного инструмента. В который можно
        установить любой пользовательский графический элемент. Используется когда пользователю
        не нужны предустановленные элементы управления.

        Args:
            title: Заголовок панели активного инструмента. Обычно это название инструмента.
            observer_id: Идентификатор наблюдателя для управления видимостью и доступностью.
            widget: Пользовательский виджет который будет отображаться в панели активного инструмента.
        """
        return self.__make_handler(ToolPanelHandlerType.Custom, title, observer_id, widget)

    def __make_handler(
        self, handler_type: ToolPanelHandlerType, title: str, observer_id: Union[str, Observer], widget: QWidget = None
    ):
        panel_handler = self.__wrap(self.__service.make_handler(handler_type))
        panel_handler.set_observer(observer_id)
        panel_handler.set_panel_title(title)
        panel_handler.set_widget(widget)
        return panel_handler

    @cached_property
    def __service(self) -> ShadowActiveToolPanelMediator:
        return ShadowActiveToolPanelMediator(gui_instance._shadow)

    def __wrap(self, shadow_handler: ActiveToolPanelHandlerBase):
        if isinstance(shadow_handler, AcceptableToolPanelHandler):
            return AxipyAcceptableActiveToolHandler(shadow_handler)
        elif isinstance(shadow_handler, CustomToolPanelHandler):
            return AxipyCustomActiveToolPanelHandler(shadow_handler)
        else:
            raise NotImplementedError()
