import re
from enum import Enum
from functools import cached_property
from typing import List, Optional, Tuple, cast

import axipy
from axipy._internal._decorator import _deprecated_by
from axipy._internal._utils import _AxiRepr, _AxiReprMetaQObject
from axipy.cpp_app import ShadowMainWindow
from axipy.da import DataManager, data_manager
from axipy.gui import MapView, View, gui_instance
from axipy.render import Layer
from PySide2.QtCore import QObject, QRect, Qt, QUrl
from PySide2.QtGui import QIcon
from PySide2.QtWidgets import QDockWidget, QMainWindow, QMdiSubWindow, QToolBar, QWidget

__all__: List[str] = [
    "MainWindow",
    "mainwindow",
    "DockWidgetArea",
]


class DockWidgetArea(Enum):
    """Расположение панели."""

    Left = Qt.LeftDockWidgetArea
    """Слева."""
    Right = Qt.RightDockWidgetArea
    """Справа."""
    Top = Qt.TopDockWidgetArea
    """Сверху."""
    Bottom = Qt.BottomDockWidgetArea
    """Снизу."""


class MainWindow(_AxiRepr, QObject, metaclass=_AxiReprMetaQObject):
    """
    Главное окно ГИС Аксиома.

    Note:
        Используйте готовый объект :attr:`axipy.mainwindow`.
    """

    def __init__(self) -> None:
        QObject.__init__(self)
        # Will be initialized later, from cpp code
        self._inner_shadow: Optional[ShadowMainWindow] = None

    @property
    def _shadow(self) -> ShadowMainWindow:
        self.__ensure_valid()
        # cast, because ensure_valid() checks for None and raises
        return cast(ShadowMainWindow, self._inner_shadow)

    @staticmethod
    def show() -> "MainWindow":
        """Создает и показывает главное окно программы."""
        if axipy.mainwindow._inner_shadow is None:
            ShadowMainWindow.create(gui_instance._shadow)
        mainwindow._widget_ensured.show()
        return mainwindow

    @property
    def is_valid(self) -> bool:
        """Возвращает признак корректности состояния главного окна."""
        return self._inner_shadow is not None and self._inner_shadow.isValid()

    @property
    def geometry(self) -> QRect:
        """Устанавливает или возвращает положение главного окна."""
        return self._widget_ensured.geometry()

    @geometry.setter
    def geometry(self, r: QRect) -> None:
        if r.isValid():
            self._widget_ensured.setGeometry(r)

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

        Return:
            Qt5 виджет содержимого.
        """
        return self.__cached_widget if self.is_valid else None

    @cached_property
    def __cached_widget(self) -> Optional[QMainWindow]:
        qt_object = self._shadow.qt_object()
        # prepare widgets, bugfix #6904
        # qt_object.findChildren(QWidget)
        return qt_object

    def __ensure_valid(self) -> None:
        if not self.is_valid:
            raise RuntimeError(
                "axipy.mainwindow is not yet created. Create the window by calling axipy.mainwindow.show()."
            )

    @property
    def _widget_ensured(self) -> QMainWindow:
        self.__ensure_valid()
        return cast(QMainWindow, self.widget)

    def add(self, view: View) -> QMdiSubWindow:
        """
        Добавляет окно просмотра данных.

        Args:
            view: окно просмотра данных.

        Note:
            При создании окон просмотра данных :meth:`axipy.ViewManager.create_mapview`
            или :meth:`axipy.ViewManager.create_tableview`
            они автоматически добавляются в главное окно программы.
        """
        return self._shadow.add(view._shadow)

    def add_layer_interactive(self, layer: Layer) -> MapView:
        """Добавляет слой с запросом на помещение на текущую карту или в новую."""
        return cast(MapView, View._wrap(self._shadow.addLayerInteractive(layer._shadow)))

    def add_layer_current_map(self, layer: Layer) -> MapView:
        """Добавляет слой в текущей карте."""
        return cast(MapView, View._wrap(self._shadow.addLayerCurrentMap(layer._shadow)))

    def add_layer_new_map(self, layer: Layer) -> MapView:
        """Открывает слой в новой карте."""
        return cast(MapView, View._wrap(self._shadow.addLayerNewMap(layer._shadow)))

    def add_dock_widget(
        self,
        dock_widget: QDockWidget,
        area: DockWidgetArea,
        icon: Optional[QIcon] = None,
    ) -> bool:
        """
        Добавляет панель в главное окно приложения. При успешном добавлении возвращает
        True. Если же данная панель уже присутствует, то команда игнорируется и
        возвращается False. Элементы управления, которые требуется разместить на панели,
        создаются в дополнительном окне, а уже это окно, в свою очередь, устанавливается
        для панели (см. пример ниже).

        Args:
            dock_widget: Пользовательская созданная панель.
            area: Расположение.
            icon: Иконка для отображения в списке всех доступных панелей.

        Пример::

            from PySide2.QtWidgets import QDockWidget, QWidget, QPushButton
            from PySide2.QtCore import Qt

            dock = QDockWidget('Заголовок')
            widget = QWidget()
            layout = QVBoxLayout()
            button = QPushButton("Кнопка")
            button.clicked.connect(lambda: print('Реакция на кнопку'))
            layout.addWidget(button)
            layout.addStretch()
            widget.setLayout(layout)
            dock.setWidget(widget)
            app.mainwindow.add_dock_widget(dock, Qt.RightDockWidgetArea, QIcon('filename.png'))
        """

        qt_dock_widget_area: Qt.DockWidgetArea
        if isinstance(area, DockWidgetArea):
            qt_dock_widget_area = area.value
        else:
            qt_dock_widget_area = area

        return self._shadow.addDockWidget(dock_widget, qt_dock_widget_area, icon if icon is not None else QIcon())

    def remove_dock_widget(self, dock: QDockWidget) -> None:
        """Удаляет существующую панель у главного окна приложения."""
        self._shadow.removeDockWidget(dock)

    @property
    def dock_widgets(self) -> Tuple[QDockWidget, ...]:
        """Возвращает список панелей."""
        return tuple(self._widget_ensured.findChildren(QDockWidget))

    def dock_widget_by_name(self, name: str) -> Tuple[QDockWidget, ...]:
        """
        Производит поиск панелей по заголовку.

        Args:
            name: Заголовок панели или же шаблон в формате :py:mod:`re`

        Пример выдачи всех окон, у которых заголовок начинается c "Заголовок"  ::

            for dock in app.mainwindow.dock_widget_by_name('Заголовок.*$'):
                print(dock.windowTitle())

            >>> Заголовок2
            >>> Заголовок
            >>> Заголовок1
        """
        regex = re.compile(name)
        return tuple(filter(lambda dock: re.match(regex, dock.windowTitle()), self.dock_widgets))

    def show_html_url(self, url: QUrl, caption: str) -> None:
        """
        Показывает окно для локального файла html или если это web страница, запускает
        браузер по ассоциации.

        Args:
            url: Ссылка на файл html или адрес страницы.
            caption: Заголовок окна.
        """
        self._shadow.showHtmlUrl(caption, url)

    @property
    def quick_toolbar(self) -> Optional[QToolBar]:
        """
        Возвращает панель быстрого запуска. Доступна только для ленточного интерфейса.

        Пример добавления инструмента выбора::

            tool_bar = mainwindow.quick_toolbar
            if tool_bar:
                tool_bar.addAction(action_manager['Select'])
        """
        return self._shadow.quickBar()


def _apply_deprecated() -> None:
    # noinspection PyPep8Naming
    @_deprecated_by("axipy.Workspace.load_file")
    def load_workspace(self: MainWindow, fileName: str) -> None:
        """
        Warning:
            .. deprecated:: 5.1.0
        """
        self._shadow.open_workspace(fileName)

    setattr(MainWindow, "load_workspace", load_workspace)

    # noinspection PyPep8Naming
    @_deprecated_by("axipy.Workspace.save_file")
    def save_workspace(self: MainWindow, fileName: str) -> None:
        """
        Warning:
            .. deprecated:: 5.1.0
        """
        self._shadow.save_workspace(fileName)

    setattr(MainWindow, "save_workspace", save_workspace)

    @_deprecated_by("axipy.MainWindow.widget")
    def qt_object(self: MainWindow) -> Optional[QMainWindow]:
        return self.widget

    setattr(MainWindow, "qt_object", qt_object)

    def catalog(_self: MainWindow) -> DataManager:
        """
        Warning:
            .. deprecated:: 6.3.0
        """
        return data_manager

    setattr(MainWindow, "catalog", property(_deprecated_by("axipy.data_manager")(catalog)))


_apply_deprecated()

mainwindow = MainWindow()
