from functools import cached_property
from typing import List, Optional, cast

import axipy
from axipy.cpp_gui import (
    ShadowDataCatalogWidget,
    ShadowLayerControlWidget,
    ShadowListWindowWidget,
    ShadowNotificationWidget,
    ShadowPythonConsoleWidget,
    ShadowView,
)
from axipy.da import DataObject
from axipy.render import ListLayers
from PySide2.QtCore import QObject, Signal, Slot
from PySide2.QtWidgets import QListView, QWidget

from .gui_class import gui_instance
from .view import MapView, View

__all__: List[str] = [
    "DataManagerWidget",
    "ViewManagerWidget",
    "LayerControlWidget",
    "NotificationWidget",
    "PythonConsoleWidget",
]


class DataManagerWidget:
    """
    Виджет управления списком открытых данных. Список доступных виджетов можно получить
    посредством :attr:`axipy.ViewManager.data_managers`

    Пример добавления в главное окно приложения (полный пример см. :ref:`custom_app_widgets`)::

        from PySide2.QtWidgets import QDockWidget
        from PySide2.QtCore import Qt

        data_manager = DataManagerWidget()
        dock_catalog = QDockWidget("Открытые данные")
        dock_catalog.setWidget(data_manager.widget)
        app.mainwindow.widget.addDockWidget(Qt.LeftDockWidgetArea, dock_catalog)

    .. seealso::
        :ref:`custom_app_widgets`
    """

    _shadow: ShadowDataCatalogWidget

    def __init__(self) -> None:
        self._shadow = ShadowDataCatalogWidget.create(gui_instance._shadow)

    @classmethod
    def _wrap(cls, shadow: "ShadowView") -> Optional["DataManagerWidget"]:
        if isinstance(shadow, ShadowDataCatalogWidget):
            return cls.__wrap_typed(shadow)
        return None

    @classmethod
    def __wrap_typed(cls, shadow: ShadowDataCatalogWidget) -> "DataManagerWidget":
        result = cls.__new__(cls)
        result._shadow = shadow
        return result

    @property
    def widget(self) -> QWidget:
        """
        Виджет, соответствующий содержимому окна.

        Return:
            Qt5 виджет содержимого.
        """
        return self._shadow.widget()

    @property
    def list_widget(self) -> QListView:
        """
        Виджет, соответствующий содержимому списка таблиц.

        Return:
            Qt5 виджет списка.
        """
        return self._shadow.listWidget()

    @property
    def selection_changed(self) -> Signal:
        """
        Выделение в списке было изменено.

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

    @property
    def objects(self) -> List[DataObject]:
        """Список выделенных объектов."""
        return [cast(DataObject, DataObject._wrap(obj)) for obj in self._shadow.dataObjectsSelected()]


class ViewManagerWidget:
    """
    Виджет списка окон.

    .. seealso::
        :ref:`custom_app_widgets`
    """

    _shadow: ShadowListWindowWidget

    class _ListWindowSignals(QObject):
        view_changed = Signal(View)

    def __init__(self) -> None:
        self._signals = ViewManagerWidget._ListWindowSignals()
        self._signal = self._signals.view_changed

        self._shadow = ShadowListWindowWidget(gui_instance._shadow)
        self._shadow.selectionChanged.connect(self._slot_view_changed)

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

        :rtype: Signal[View]
        """
        return self._signal

    @property
    def widget(self) -> QWidget:
        """
        Виджет, соответствующий содержимому окна.

        Return:
            Qt5 виджет содержимого.
        """
        return self._shadow

    @Slot(QWidget)
    def _slot_view_changed(self, w: QWidget) -> None:
        for v in axipy.view_manager.views:
            if v.widget == w:
                self._signal.emit(v)


class LayerControlWidget:
    """
    Виджет управления слоями карты. Список доступных виджетов можно получить посредством
    :attr:`axipy.ViewManager.layer_controls`

    Пример добавления в главное окно приложения (полный пример см. :ref:`custom_app_widgets`)::

        from PySide2.QtWidgets import QDockWidget
        from PySide2.QtCore import Qt

        layer_control = LayerControlWidget()
        dock_layer_control = QDockWidget('Управление слоями', app.mainwindow.widget)
        dock_layer_control.setWidget(layer_control.widget)
        app.mainwindow.widget.addDockWidget(Qt.LeftDockWidgetArea, dock_layer_control)

    Пример запроса выделенных слоев::

        for lc in axipy.view_manager.layer_controls:
            print(f'Карта: {lc.active_map_view.title}')
            if lc.selected_layers:
                for l in lc.selected_layers:
                    print('Слой или группа:' , l.title)

    .. seealso::
        :ref:`custom_app_widgets`
    """

    # _shadow: ShadowLayerControlWidget

    class _LayerControlSignals(QObject):
        mapview_activated: Signal = Signal(MapView)

    def __init__(self) -> None:
        # custom signal and slot with wrapped objects
        self._signals = LayerControlWidget._LayerControlSignals()
        self._signal = self._signals.mapview_activated
        self._shadow.mapViewActivated.connect(self._slot_mapview_activated)

    @cached_property
    def _shadow(self) -> ShadowLayerControlWidget:
        return ShadowLayerControlWidget.create(gui_instance._shadow)

    @classmethod
    def _wrap(cls, shadow: "ShadowView") -> Optional["LayerControlWidget"]:
        if isinstance(shadow, ShadowLayerControlWidget):
            return cls.__wrap_typed(shadow)
        return None

    @classmethod
    def __wrap_typed(cls, shadow: ShadowLayerControlWidget) -> "LayerControlWidget":
        result = cls.__new__(cls)
        result._shadow = shadow
        cls.__init__(result)
        return result

    @property
    def mapview_activated(self) -> Signal:
        """
        Сигнал об изменении активной карты.

        :rtype: Signal[MapView]
        """
        return self._signal

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

        Пример::

            for lc in axipy.view_manager.layer_controls:
                lc.selection_changed.connect(lambda: print('Выбор изменен'))

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

    @property
    def widget(self) -> QWidget:
        """
        Виджет, соответствующий содержимому окна.r.

        Return:
            Qt5 виджет содержимого.
        """
        return self._shadow.widget()

    @property
    def active_map_view(self) -> Optional[MapView]:
        """Возвращает активную карту."""
        active_mapview = self._shadow.active_map_view()
        if active_mapview:
            return View._wrap(active_mapview)
        return None

    @property
    def selected_layers(self) -> Optional[ListLayers]:
        """Возвращает список выбранных слоев или групп."""
        selects = ShadowLayerControlWidget.selected_layers(self._shadow)
        if selects:
            return ListLayers._wrap(selects)
        return None

    @Slot(QWidget)
    def _slot_mapview_activated(self, w: QWidget) -> None:
        for map_view in axipy.view_manager.mapviews:
            if map_view.widget == w:
                self._signal.emit(map_view)


class NotificationWidget:
    """
    Виджет уведомлений.

    .. seealso::
        :ref:`custom_app_widgets`
    """

    _shadow: ShadowNotificationWidget

    def __init__(self) -> None:
        self._shadow = ShadowNotificationWidget()

    @property
    def widget(self) -> QWidget:
        """
        Виджет, соответствующий содержимому окна.

        Return:
            Qt5 виджет содержимого.
        """
        return self._shadow


class PythonConsoleWidget:
    """
    Виджет ввода команд python.

    .. seealso::
        :ref:`custom_app_widgets`
    """

    _shadow: ShadowPythonConsoleWidget

    def __init__(self) -> None:
        self._shadow = ShadowPythonConsoleWidget.create(gui_instance._shadow)

    @property
    def widget(self) -> QWidget:
        """
        Виджет, соответствующий содержимому окна.

        Return:
            Qt5 виджет содержимого.
        """
        return self._shadow.widget()
