from typing import TYPE_CHECKING, List, Optional, Tuple, Union

import axipy
from axipy.cpp_core_geometry import (
    ShadowArc,
    ShadowEllipse,
    ShadowRectangle,
    ShadowRoundRectangle,
    ShadowText,
)
from axipy.cs import CoordSystem
from axipy.da import Geometry, Polygon, Style
from axipy.gui import MapView, ReportView
from axipy.utl import Pnt, Rect
from PySide2.QtCore import QPointF, QRectF

if TYPE_CHECKING:
    from axipy.da.style import TextStyle

__all__: List[str] = [
    "Rectangle",
    "RoundRectangle",
    "Ellipse",
    "Arc",
    "Text",
]


class Rectangle(Geometry):
    """
    Геометрический объект типа прямоугольник.

    .. literalinclude:: /../../tests/doc_examples/da/test_example_geometry.py
        :caption: Пример.
        :pyobject: test_run_example_geometry_rectangle
        :lines: 2-
        :dedent: 4
    """

    _shadow: ShadowRectangle

    # TODO: Refactor
    # noinspection PyTypeChecker
    # noinspection PyUnresolvedReferences
    def __init__(self, *par: Union[Rect, float], cs: Optional[CoordSystem] = None) -> None:
        """
        Конструктор класса.

        Args:
            par: Прямоугольник класса :class:`Rect` или перечень координат через запятую
                 (`xmin`, `ymin`, `xmax`, `ymax`) или списком :class:`list`.
            cs: Система Координат, в которой создается геометрия.
        """
        if len(par) == 1 and type(par[0]) is Rect:
            v = par[0]
        elif len(par) == 1 and type(par[0]) is list and len(par[0]) == 4:
            v = Rect(par[0][0], par[0][1], par[0][2], par[0][3])
        elif len(par) == 4:
            # TODO: Refactor
            v = Rect(par[0], par[1], par[2], par[3])  # type: ignore[arg-type]
        else:
            raise TypeError("Unsupported data type")

        self._set_shadow(ShadowRectangle(v.to_qt(), None if cs is None else cs._shadow))

    @property
    def xmin(self) -> float:
        """Минимальное значение X."""
        return self._shadow.get_rect().left()

    @xmin.setter
    def xmin(self, v: float) -> None:
        r = self._shadow.get_rect()
        r.setLeft(v)
        self._shadow.set_rect(r)

    @property
    def ymin(self) -> float:
        """Минимальное значение Y."""
        return self._shadow.get_rect().top()

    @ymin.setter
    def ymin(self, v: float) -> None:
        r = self._shadow.get_rect()
        r.setTop(v)
        self._shadow.set_rect(r)

    @property
    def xmax(self) -> float:
        """Максимальное значение X."""
        return self._shadow.get_rect().right()

    @xmax.setter
    def xmax(self, v: float) -> None:
        r = self._shadow.get_rect()
        r.setRight(v)
        self._shadow.set_rect(r)

    @property
    def ymax(self) -> float:
        """Максимальное значение Y."""
        return self._shadow.get_rect().bottom()

    @ymax.setter
    def ymax(self, v: float) -> None:
        r = self._shadow.get_rect()
        r.setBottom(v)
        self._shadow.set_rect(r)

    def __repr__(self) -> str:
        return (
            "{} xmin={} ymin={} xmax={} ymax={}".format(self._class_name, self.xmin, self.ymin, self.xmax, self.ymax)
            + self._cs_str
        )


# noinspection PyPep8Naming
class RoundRectangle(Rectangle):
    """
    Геометрический объект типа скругленный прямоугольник.

    .. literalinclude:: /../../tests/doc_examples/da/test_example_geometry.py
        :caption: Пример.
        :pyobject: test_run_example_geometry_rrectangle
        :lines: 2-
        :dedent: 4
    """

    _shadow: ShadowRoundRectangle

    def __init__(
        self,
        rect: Union[Rect, list],
        xRad: float,
        yRad: float,
        cs: Optional[CoordSystem] = None,
    ) -> None:
        """
        Конструктор класса.

        Args:
            rect: Прямоугольник класса :class:`Rect` или как :class:`list`.
            xRad: Скругление по X.
            yRad: Скругление по Y.
            cs: Система Координат, в которой создается геометрия.
        """
        if type(rect) is Rect:
            v = rect
        elif type(rect) is list and len(rect) == 4:
            v = Rect(rect[0], rect[1], rect[2], rect[3])
        else:
            raise TypeError("Unsupported data type")
        self._set_shadow(ShadowRoundRectangle(v.to_qt(), xRad, yRad, None if cs is None else cs._shadow))

    @property
    def xRadius(self) -> float:
        """Скругление углов по координате X."""
        return self._shadow.xRadius()

    @xRadius.setter
    def xRadius(self, v: float) -> None:
        self._shadow.setXRadius(v)

    @property
    def yRadius(self) -> float:
        """Скругление углов по координате Y."""
        return self._shadow.yRadius()

    @yRadius.setter
    def yRadius(self, v: float) -> None:
        self._shadow.setYRadius(v)

    def __repr__(self) -> str:
        return (
            "{} xmin={} ymin={} xmax={} ymax={} xraduis={} yraduis={}".format(
                self._class_name,
                self.xmin,
                self.ymin,
                self.xmax,
                self.ymax,
                self.xRadius,
                self.yRadius,
            )
            + self._cs_str
        )


# noinspection PyPep8Naming
class Ellipse(Geometry):
    """
    Геометрический объект типа эллипс.

    .. literalinclude:: /../../tests/doc_examples/da/test_example_geometry.py
        :caption: Пример.
        :pyobject: test_run_example_geometry_ellipse
        :lines: 2-
        :dedent: 4
    """

    _shadow: ShadowEllipse

    def __init__(self, rect: Union[Rect, list], cs: Optional[CoordSystem] = None) -> None:
        """
        Конструктор класса.

        Args:
            rect: Прямоугольник класса :class:`Rect` или как :class:`list`.
            cs: Система Координат, в которой создается геометрия.
        """
        if type(rect) is Rect:
            v = rect
        elif type(rect) is list and len(rect) == 4:
            v = Rect(rect[0], rect[1], rect[2], rect[3])
        else:
            raise TypeError("Unsupported data type")
        self._set_shadow(ShadowEllipse(v.to_qt(), None if cs is None else cs._shadow))

    @property
    def center(self) -> Pnt:
        """Центр эллипса."""
        return Pnt.from_qt(self._shadow.center())

    @center.setter
    def center(self, v: Union[Pnt, tuple]) -> None:
        self._shadow.setCenter(Pnt._fix_pnt(v).to_qt())

    @property
    def majorSemiAxis(self) -> float:
        """Радиус большой полуоси эллипса."""
        return self._shadow.majorSemiAxis()

    @majorSemiAxis.setter
    def majorSemiAxis(self, v: float) -> None:
        self._shadow.setMajorSemiAxis(v)

    @property
    def minorSemiAxis(self) -> float:
        """Радиус малой полуоси эллипса."""
        return self._shadow.minorSemiAxis()

    @minorSemiAxis.setter
    def minorSemiAxis(self, v: float) -> None:
        self._shadow.setMinorSemiAxis(v)

    def __repr__(self) -> str:
        return (
            "{} center={} major={} minor={}".format(
                self._class_name, self.center, self.majorSemiAxis, self.minorSemiAxis
            )
            + self._cs_str
        )


# noinspection PyPep8Naming
class Arc(Geometry):
    """
    Геометрический объект типа дуга.

    .. literalinclude:: /../../tests/doc_examples/da/test_example_geometry.py
        :caption: Пример.
        :pyobject: test_run_example_geometry_arc
        :lines: 2-
        :dedent: 4
    """

    _shadow: ShadowArc

    def __init__(
        self,
        rect: Union[Rect, list],
        startAngle: float,
        endAngle: float,
        cs: Optional[CoordSystem] = None,
    ) -> None:
        """
        Конструктор класса.

        Args:
            rect: Прямоугольник класса :class:`Rect` или как :class:`list`.
            startAngle: Начальный угол дуги.
            endAngle: Конечный угол дуги.
            cs: Система Координат, в которой создается геометрия.
        """
        if type(rect) is Rect:
            v = rect
        elif type(rect) is list and len(rect) == 4:
            v = Rect(rect[0], rect[1], rect[2], rect[3])
        else:
            raise TypeError("Unsupported data type")
        self._set_shadow(ShadowArc(v.to_qt(), startAngle, endAngle, None if cs is None else cs._shadow))

    @property
    def center(self) -> Pnt:
        """Центр дуги."""
        return Pnt.from_qt(self._shadow.center())

    @center.setter
    def center(self, v: Union[Pnt, tuple]) -> None:
        self._shadow.setCenter(Pnt._fix_pnt(v).to_qt())

    @property
    def xRadius(self) -> float:
        """Радиус большой полуоси прямоугольника, в который вписана дуга."""
        return self._shadow.xRadius()

    @xRadius.setter
    def xRadius(self, v: float) -> None:
        self._shadow.setXRadius(v)

    @property
    def yRadius(self) -> float:
        """Радиус малой полуоси прямоугольника, в который вписана дуга."""
        return self._shadow.yRadius()

    @yRadius.setter
    def yRadius(self, v: float) -> None:
        self._shadow.setYRadius(v)

    @property
    def startAngle(self) -> float:
        """Начальный угол дуги."""
        return self._shadow.startAngle()

    @startAngle.setter
    def startAngle(self, v: float) -> None:
        self._shadow.setStartAngle(v)

    @property
    def endAngle(self) -> float:
        """Конечный угол дуги."""
        return self._shadow.endAngle()

    @endAngle.setter
    def endAngle(self, v: float) -> None:
        self._shadow.setEndAngle(v)

    def __repr__(self) -> str:
        return (
            "{} center={} xradius={} yradius={} start={} end={}".format(
                self._class_name,
                self.center,
                self.xRadius,
                self.yRadius,
                self.startAngle,
                self.endAngle,
            )
            + self._cs_str
        )


# noinspection PyPep8Naming
class Text(Geometry):
    """
    Геометрический объект типа текст.

    Warning:
        Геометрия текста и, в отличие от остальных типов объектов, определяется кроме геометрического представления
        так же и стилем его оформления :class:`axipy.TextStyle`.
        Так же стоит заметить, что параметры геометрии текста зависит от того, куда (с карту или отчет)
        будет добавлен созданный текст.
        Поэтому созданный объект для карты должен быть добавлен в карту, а для отчета - в отчет.
        Подробнее см. :meth:`create_by_style`.

    Note:
        Для создания текстового объекта с указанием его размера в пунктах
        можно использовать метод :meth:`create_by_style`.

    .. literalinclude:: /../../tests/doc_examples/da/test_example_geometry.py
        :caption: Пример создания текста и вставки его в отчет.
        :pyobject: test_run_example_geometry_text
        :lines: 2-
        :dedent: 4
    """

    _shadow: ShadowText

    def __init__(
        self,
        text: str,
        rect: Union[Rect, QRectF],
        view: Optional[Union[MapView, ReportView]] = None,
        angle: float = 0,
        cs: Optional[CoordSystem] = None,
    ) -> None:
        """
        Конструктор класса.

        Args:
            text: Строка с текстом. Многострочный текст можно задать, вставив символ "\\\\n" в место переноса.
            rect: Прямоугольник, в который будет вписан текст. Прямоугольник задается в координатах отчета.
                Верхней левой точкой является :attr:`startPoint`.
                В этот прямоугольник будет произведена попытка размещения текста.
            angle: Угол поворота текстового объекта. Задается значением в градусах против ЧС по отношению к горизонтали.
            view: Окно карты или отчета.
            cs: Система Координат, в которой создается геометрия.
        """
        r = Rect._rect_value_to_qt(rect)
        r, angle = Text.__adaptive_for_view(r, angle, view)
        self.__view = view
        self._set_shadow(ShadowText(text, r, angle, None if cs is None else cs._shadow))

    __view = None

    @classmethod
    def __adaptive_for_view(
        cls,
        rect: QRectF,
        angle: float,
        view: Optional[Union[MapView, ReportView]] = None,
    ) -> Tuple[QRectF, float]:
        h = rect.height()
        if isinstance(view, MapView):
            rect.setHeight(-h)
            rect = rect.translated(0, h * 2)
        elif isinstance(view, ReportView):
            rect = rect.translated(0, -h)
            angle = -angle
        return rect, angle

    @classmethod
    def create_by_style(
        cls,
        text: str,
        point: Union[Pnt, QPointF],
        style: Style,
        view: Union[MapView, ReportView],
        angle: float = 0,
        cs: Optional[CoordSystem] = None,
    ) -> "Text":
        """
        Создает текстовый объект по точке привязки и стиля для карты или отчета.
        Результирующий объект будет корректен только для текущего состояния окна карты
        или отчета, куда он должен быть добавлен. Это связано с тем, что расчет размера
        производится на основании текущего установленного масштаба. Поэтому при
        изменении масштаба перед добавлением необходимо заново создать текстовый объект.

        Args:
            text: Текст.
            point: Точка привязки :attr:`startPoint`. Этой точкой служит левая верхняя точка.
            style: Стиль оформления текста.
            view: Окно карты или отчета.
            angle: Угол поворота текстового объекта. Задается значением в градусах против ЧС по отношению к горизонтали.
            cs: Система Координат, в которой создается геометрия.

        .. literalinclude:: /../../tests/doc_examples/da/test_example_geometry.py
            :caption: Пример изменения параметров текстовых объектов для окна карты.
            :pyobject: test_run_example_geometry_mv_text_style
            :lines: 2-
            :dedent: 4

        .. literalinclude:: /../../tests/doc_examples/da/test_example_geometry.py
            :caption: Пример создания текста для отчета.
            :pyobject: test_run_example_geometry_text_style
            :lines: 2-
            :dedent: 4
        """
        if not isinstance(view, (MapView, ReportView)):
            raise ValueError(f"view parameter has bad type: {type(view)}")
        obj = cls.__new__(cls)
        p = Pnt._point_value_to_qt(point)
        rect = view._shadow.getTextRect(p, text, style._shadow)
        rect, angle = Text.__adaptive_for_view(rect, angle, view)
        obj.__view = view
        obj._set_shadow(ShadowText(text, rect, angle, None if cs is None else cs._shadow))
        return obj

    @property
    def startPoint(self) -> Pnt:
        """Координаты точки привязки."""
        return Pnt.from_qt(self._shadow.startPoint())

    @startPoint.setter
    def startPoint(self, v: Union[Pnt, tuple]) -> None:
        self._shadow.setStartPoint(Pnt._fix_pnt(v).to_qt())

    @property
    def endPoint(self) -> Pnt:
        """Координаты точки выноски (указки)."""
        return Pnt.from_qt(self._shadow.endPoint())

    @endPoint.setter
    def endPoint(self, v: Union[Pnt, tuple]) -> None:
        self._shadow.setEndPoint(Pnt._fix_pnt(v).to_qt())

    @property
    def text(self) -> str:
        """Текст."""
        return self._shadow.text()

    @text.setter
    def text(self, v: str) -> None:
        self._shadow.setText(v)

    @property
    def angle(self) -> float:
        """Угол поворота текста, отсчитываемый от горизонтали против часовой стрелки."""
        a = self._shadow.angle()
        if isinstance(self.__view, ReportView):
            a = 360 - a
        return a

    @angle.setter
    def angle(self, a: float) -> None:
        if isinstance(self.__view, ReportView):
            a = 360 - a
        self._shadow.setAngle(a)

    @property
    def rect_as_polygon(self) -> Polygon:
        """Представление ограничивающего прямоугольника в виде полигона."""
        return Polygon._from_iterable_q_point(self._shadow.as_polygon(), cs=self.coordsystem)

    @property
    def width(self) -> float:
        """Ширина прямоугольника с текстом."""
        return self._shadow.width()

    @property
    def height(self) -> float:
        """Высота прямоугольника с текстом."""
        return self._shadow.height()

    def size_for_view(self, style: "TextStyle", view: Union[MapView, ReportView]) -> float:
        """
        Размер шрифта в пунктах для конкретного окна. Значение меняется в зависимости от
        текущего масштаба.

        Args:
            style: Стиль оформления текста.
            view: Окно карты или отчета.
        """
        if not isinstance(view, (MapView, ReportView)):
            raise ValueError(f"view parameter has bad type: {type(view)}")
        if not isinstance(style, axipy.TextStyle):
            raise ValueError(f"style parameter has bad type: {type(style)}")
        return view._shadow.textPoints(self._shadow, style._shadow)

    def __repr__(self) -> str:
        return (
            "{} text={} startPoint={} angle={}".format(self._class_name, self.text, self.startPoint, self.angle)
            + self._cs_str
        )
