from typing import List, Optional

import axipy
from axipy import ActiveToolPanel, Plugin
from axipy.concurrent import (
    AxipyProgressHandler,
    ProgressGuiFlags,
    ProgressSpecification,
    task_manager,
)
from axipy.da import Feature, Table
from axipy.gui import MapTool
from axipy.gui import selection_manager as sm
from axipy.gui import view_manager
from axipy.gui.map_tool import SelectToolHelpers
from PySide2.QtCore import Qt
from PySide2.QtGui import QKeyEvent, QMouseEvent
from PySide2.QtWidgets import QLabel, QSizePolicy, QSpacerItem, QVBoxLayout, QWidget

from ...helper import Connection, ensure_sql_init_for_bg_thread, find_layer, has_table
from .data_types import SearchSettings
from .strategies.strategy_base import SelectStrategy
from .strategies.strategy_factory import SelectStrategyFactory
from .widgets.style_widget_interface import SelectByStyleWidgetInterface
from .widgets.table_selector import TableSelector


class SelectByStyle(MapTool):
    """
    Инструмент выделяет записи по выбранным настройкам стиля.
    """

    def __init__(self, iface: Plugin, title: str, observer_id) -> None:
        super().__init__()
        self.__title = title
        self.__iface = iface
        self.__observer_id = observer_id
        self.__widget = None  # type: Optional[SelectByStyleWidgetInterface]
        self.__table_selector = None  # type: Optional[TableSelector]
        self.__strategy = None  # type: Optional[SelectStrategy]
        self.__connections = []  # type: List[Connection]
        self.__min_selection_size = 1

        self.__vlayout = None
        self.__tool_panel = None
        self.__current_view_con = None

    def load(self):
        panel_service = ActiveToolPanel()
        widget = QWidget()
        vlayout = QVBoxLayout(widget)
        vlayout.setContentsMargins(0, 0, 0, 0)
        vlayout.setSpacing(0)
        widget.setLayout(vlayout)
        self.__vlayout = vlayout
        self.__tool_panel = panel_service.make_acceptable(self.__title, self.__observer_id, widget)
        self.__tool_panel.accepted.connect(self.__apply)
        self.__tool_panel.panel_was_closed.connect(self.reset)
        self.__connections.append(Connection(sm.changed, self.selection_changed))
        self.__current_view_con = Connection(view_manager.active_changed, self.active_view_changed)
        if (
            sm.count == self.__min_selection_size
            and self.view.widget == view_manager.active.widget
            and has_table(self.view, sm.table)
        ):
            self.on_triggered()

    def unload(self):
        self.finilize()

    def finilize(self):
        for con in self.__connections:
            con.disconnect()
        if self.__current_view_con:
            self.__current_view_con.disconnect()
        self.__tool_panel.deactivate()

    def selection_changed(self):
        is_current_map_view = self.view.widget == view_manager.active.widget
        if is_current_map_view and has_table(self.view, sm.table):
            self.on_triggered()
        elif is_current_map_view and sm.count != self.__min_selection_size:
            self.show_help_message_widget()

    def active_view_changed(self):
        # переподключаем соединения
        is_current = self.view.widget == view_manager.active.widget
        for con in self.__connections:
            if is_current:
                con.connect()
            else:
                con.disconnect()
        # выполняем действия только для инструмента текущей активной карты
        if is_current:
            self.on_triggered()

    def keyPressEvent(self, event: QKeyEvent) -> Optional[bool]:
        if event.key() == Qt.Key_Escape:
            self.reset()
            return
        return super().keyPressEvent(event)

    def on_triggered(self) -> None:
        if self.view.widget != view_manager.active.widget:
            return
        if sm.count == self.__min_selection_size and has_table(self.view, sm.table):
            self.__init_widget()
        else:
            self.show_help_message_widget()

    def clean_widget(self):
        if self.__table_selector is not None:
            self.__vlayout.removeWidget(self.__table_selector)
            self.__table_selector.deleteLater()
            self.__table_selector = None
        if self.__widget is not None:
            self.__vlayout.removeWidget(self.__widget)
            self.__widget.deleteLater()
            self.__widget = None

    def show_help_message_widget(self):
        # Не забываем активировать панель для текущего инструмента, т.к. в панели
        # активного инструмента может остаться виджет от другого инструмента или
        # от этого же но с другой карты.
        self.__tool_panel.activate()
        self.__tool_panel.disable()
        if self.__widget is not None:
            self.clean_widget()
        widget = QWidget()
        text = axipy.tr("Для работы c инструментом нужно выделить только одну геометрию.")
        label = QLabel(text)
        label.setWordWrap(True)
        layout = QVBoxLayout(widget)
        layout.addWidget(label)
        layout.addSpacerItem(QSpacerItem(10, 10, QSizePolicy.Minimum, QSizePolicy.Expanding))
        widget.setLayout(layout)
        self.__widget = widget
        self.__vlayout.addWidget(widget)

    def __init_widget(self):
        self.__tool_panel.activate()
        self.__tool_panel.try_enable()
        self.clean_widget()
        f = next(sm.get_as_cursor())  # type: Feature
        # В зависимости от выбранной геометрии нужно по разному обрабатывать настройки
        # для поиска стилей
        self.__strategy = SelectStrategyFactory.make(f, sm.table)
        # FIXME: Сейчас TableSelector не следит за переименованием слоёв
        self.__table_selector = TableSelector(self.view)
        # По умолчанию предпологаем, что хотят искать на том же слое
        # на котором выделен объект
        selection_layer = find_layer(self.view, sm.table)
        if selection_layer:
            self.__table_selector.select(selection_layer.title)
        self.__vlayout.addWidget(self.__table_selector)
        widget = self.__strategy.settings_widget(self.__iface)
        self.__widget = widget
        self.__vlayout.addWidget(widget)

    def __apply(self):
        if self.__widget is None or not issubclass(type(self.__widget), SelectByStyleWidgetInterface):
            return
        result = self.__widget.result()
        if not result.is_valid():
            return
        spec = ProgressSpecification(
            description=axipy.tr("Ищем записи с выбранным стилем..."), flags=ProgressGuiFlags.CANCELABLE
        )
        table_for_selection = self.__table_selector.selected_table
        self.__tool_panel.disable()
        try:
            task_manager.run_and_get(spec, self.select_by_style, result, table_for_selection, self.__strategy)
        finally:
            self.__tool_panel.try_enable()

    @staticmethod
    def select_by_style(
        ph: AxipyProgressHandler, user_settings: SearchSettings, table: Table, strategy: SelectStrategy
    ):
        ensure_sql_init_for_bg_thread()
        if table is None:
            return
        ids = strategy.query_ids(ph, table, user_settings)
        ph.prepare_to_write_changes(axipy.tr("Выделяем записи..."))
        if len(ids) != 0:
            sm.set(table, ids)

    def mousePressEvent(self, event: QMouseEvent):
        if self.__widget is None:
            self.show_help_message_widget()
        if event.button() != Qt.LeftButton:
            return self.PassEvent

        # С помощью модификатора Shift можно выделить несколько геометрий,
        # но для этого инструмента нужно чтобы была выделена только одна геометрия
        modifiers = event.modifiers() & ~Qt.ShiftModifier
        no_modifiers_event = QMouseEvent(
            event.type(),
            event.localPos(),
            event.button(),
            event.buttons(),
            modifiers,
        )
        SelectToolHelpers.select_by_mouse(self.view, no_modifiers_event)
