from typing import List, Optional, Union, cast

from axipy.cpp_gui import (
    ShadowGuiUtils,
    ShadowViewPointsHelper,
)
from axipy.da import Observer
from axipy.utl import Pnt, Rect
from PySide2.QtCore import (
    QObject,
    QPoint,
    QPointF,
    QRect,
    QRectF,
    Qt,
    QTimer,
    Slot,
)
from PySide2.QtGui import (
    QKeyEvent,
    QMouseEvent,
    QPainter,
    QPaintEvent,
    QPolygonF,
)

from .deactivation_reason import DeactivationReason
from .view import View
from .view_tool import ViewTool

__all__: List[str] = [
    "MapTool",
]


# noinspection PyPep8Naming
# noinspection PyUnusedLocal
class MapTool(ViewTool, QObject):

    PassEvent: bool = False
    BlockEvent: bool = True
    enable_on: Union[str, Observer] = ""

    def __init__(self) -> None:
        QObject.__init__(self)
        ViewTool.__init__(self)

    def unload(self) -> None:
        # backwards compatibility, old method name
        self.deactivate()

    def deactivate(self) -> None:
        pass

    def canUnload(self, reason: DeactivationReason) -> bool:
        return self.canDeactivate(reason)

    def canDeactivate(self, reason: DeactivationReason) -> bool:
        return True


class ViewPointsHelper:
    """Дополнительные методы для получения координат точек на карте/отчете."""

    def __init__(self, view: View) -> None:
        self.__view = view
        self.__shadow = ShadowViewPointsHelper(view._shadow)

    def normalize_point(self, point: Union[Pnt, QPoint, QPointF]) -> QPointF:
        """
        Нормализует координаты переданной точки в соответствии с установленным ItemView.
        Приводит точность координат точки в соответствие с текущим размером окна.
        Если координаты выходят за границы КС карты, они смещаются к ближайшей границе.
        """
        qt_point: QPointF = cast(QPointF, point.to_qt() if isinstance(point, Pnt) else point)
        poly = QPolygonF()
        return self.__shadow.normalize(qt_point, poly)


# noinspection PyPep8Naming
class SelectToolBehavior(QObject):
    """Класс позволяющий выделять объекты на карте аналогично инструменту Выбор."""

    def __init__(self, tool: MapTool) -> None:
        super().__init__()
        self.__tool: MapTool = tool
        self.__start_point: Optional[Pnt] = None
        self.__current_point: Optional[Pnt] = None
        self.__press_timer: QTimer = QTimer()
        self.__mouse_press_event: Optional[QMouseEvent] = None

        time_in_ms: int = 250
        self.__press_timer.setInterval(time_in_ms)
        self.__press_timer.timeout.connect(self.__mouseClickProcessing)

    def mousePressEvent(self, event: QMouseEvent) -> bool:
        if event.button() != Qt.LeftButton:
            return False
        self.__invalidate_selection()
        self.__mouse_press_event = QMouseEvent(
            event.type(),
            event.localPos(),
            event.screenPos(),
            event.button(),
            event.buttons(),
            event.modifiers(),
        )
        self.__press_timer.start()
        return True

    def mouseMoveEvent(self, event: QMouseEvent) -> bool:
        self.__current_point = self.__tool.to_scene(event.pos())
        self.__redraw()
        return True

    # noinspection PyUnusedLocal
    def paintEvent(self, event: QPaintEvent, painter: QPainter) -> None:
        if self.__start_point and self.__current_point:
            rect: QRect = cast(QRect, self.__to_rect())
            old_pen = painter.pen()
            pen = painter.pen()
            pen.setStyle(Qt.DotLine)
            painter.setPen(pen)
            painter.drawRect(rect)
            painter.setPen(old_pen)

    def mouseReleaseEvent(self, event: QMouseEvent) -> bool:
        if event.button() != Qt.LeftButton:
            return False

        if self.__start_point and self.__current_point:
            # Выделяем всё что в выбранной области
            qt_rect = QRectF(self.__start_point.to_qt(), self.__current_point.to_qt())
            qt_rect = qt_rect.normalized()
            rect = Rect.from_qt(qt_rect)
            SelectToolHelpers.select_by_rect(self.__tool.view, rect, event.modifiers())
        elif self.__mouse_press_event:
            # Выделяем по клику
            SelectToolHelpers.select_by_mouse(self.__tool.view, self.__mouse_press_event)

        self.__invalidate_selection()
        self.__redraw()
        return True

    def keyPressEvent(self, event: QKeyEvent) -> bool:
        if event.key() == Qt.Key_Escape and self.__is_rect_selection():
            self.__invalidate_selection()
            self.__redraw()
            return True
        return False

    def __to_rect(self) -> Optional[QRect]:
        if not (self.__start_point and self.__current_point):
            return None
        p1 = self.__tool.to_device(self.__start_point)
        p2 = self.__tool.to_device(self.__current_point)
        return QRect(p1, p2).normalized()

    def __invalidate_press_timer(self) -> None:
        self.__press_timer.stop()

    @Slot()
    def __mouseClickProcessing(self) -> None:
        event = self.__mouse_press_event
        if event is None:
            return None
        self.__start_point = self.__tool.to_scene(event.pos())

    def __redraw(self) -> None:
        self.__tool.redraw()

    def __is_rect_selection(self) -> bool:
        return self.__start_point is not None

    def __invalidate_selection(self) -> None:
        self.__start_point = None
        self.__current_point = None
        self.__mouse_press_event = None
        self.__invalidate_press_timer()


class SelectToolHelpers:
    """
    Набор вспомогательных функций из инструмента Выбор.

    Можно использовать при реализации своего инструмента :class:`axipy.MapTool`.
    """

    __shadow: Optional[ShadowGuiUtils] = None

    @classmethod
    def _shadow(cls) -> ShadowGuiUtils:
        if cls.__shadow is None:
            cls.__shadow = ShadowGuiUtils()
        return cls.__shadow

    @classmethod
    def select_by_mouse(cls, view: View, event: QMouseEvent) -> None:
        """
        Выделяет геометрию в месте нажатия мышкой.

        .. literalinclude:: /../../tests/doc_examples/test_example_tool_helpers.py
            :caption: Пример использования.
            :pyobject: test_select_by_mouse_tool
            :lines: 2-
            :dedent: 4
        """
        cls._shadow().selectByMouseClick(view._shadow, event)

    @classmethod
    def select_by_rect(
        cls, view: View, rect: Union[QRectF, Rect], modifiers: Union[Qt.KeyboardModifiers, Qt.KeyboardModifier]
    ) -> None:
        """Выделяет все геометрии с одного слоя попавшие в :attr:`rect`."""
        qt_rect: QRectF = rect.to_qt() if isinstance(rect, Rect) else rect
        cls._shadow().selectByRect(view._shadow, qt_rect, cast(Qt.KeyboardModifiers, modifiers))


# TODO: DEPR
DeactivationReason = DeactivationReason
