from typing import List, Optional, Tuple, Union

from axipy.cpp_common import CommonUtils
from PySide2.QtCore import QLocale, QPointF, QRect, QRectF

from .float_formatter import FloatCoord
from .pnt import Pnt

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


class Rect:
    """
    Прямоугольник, который не обладает геопривязкой. Используется для различного вида
    запросов.

    .. literalinclude:: /../../tests/doc_examples/utl/test_example_rect.py
        :caption: Создание прямоугольника.
        :pyobject: test_run_example_rect
        :start-after: # start init
        :end-before: # finish init
        :dedent: 4
    """

    __slots__ = ("__rect",)

    def __init__(self, xmin: float, ymin: float, xmax: float, ymax: float) -> None:
        """
        Конструктор класса.

        Args:
            xmin: Минимальное значение X.
            ymin: Минимальное значение Y.
            xmax: Максимальное значение X.
            ymax: Максимальное значение Y.
        """
        self.__rect: QRectF = QRectF(xmin, ymin, xmax - xmin, ymax - ymin).normalized()

    def _set_rect(self, r: Union[QRect, QRectF]) -> None:
        self.__rect = r if isinstance(r, QRectF) else QRectF(r)

    @classmethod
    def from_qt(cls, r: Union[QRect, QRectF]) -> "Rect":
        """
        Преобразует из формата Qt.

        Args:
            r: Преобразуемый прямоугольник.
        """
        # backward compat
        if not isinstance(r, (QRectF, QRect)):
            # noinspection PyTypeChecker
            return None

        inst = cls.__new__(cls)
        inst._set_rect(r)
        return inst

    def to_qt(self) -> QRectF:
        """Преобразование в формат Qt."""
        return self.__rect

    @property
    def xmin(self) -> float:
        """Устанавливает или возвращает минимальное значение X."""
        return self.__rect.left()

    @xmin.setter
    def xmin(self, v: float) -> None:
        self.__rect.setLeft(v)

    @property
    def ymin(self) -> float:
        """Устанавливает или возвращает минимальное значение Y."""
        return self.__rect.top()

    @ymin.setter
    def ymin(self, v: float) -> None:
        self.__rect.setTop(v)

    @property
    def xmax(self) -> float:
        """Устанавливает или возвращает максимальное значение X."""
        return self.__rect.right()

    @xmax.setter
    def xmax(self, v: float) -> None:
        self.__rect.setRight(v)

    @property
    def ymax(self) -> float:
        """Устанавливает или возвращает максимальное значение Y."""
        return self.__rect.bottom()

    @ymax.setter
    def ymax(self, v: float) -> None:
        self.__rect.setBottom(v)

    @property
    def center(self) -> Pnt:
        """Устанавливает или возвращает центр прямоугольника."""
        return Pnt.from_qt(self.__rect.center())

    @center.setter
    def center(self, p: Pnt) -> None:
        self.__rect.moveCenter(Pnt._fix_pnt(p).to_qt())

    @classmethod
    def eq_approx(cls, rect1: "Rect", rect2: "Rect", precision: float = 1e-12) -> bool:
        """
        Сравнивает два прямоугольника с заданной точностью.

        Args:
            rect1: Первый прямоугольник сравнения.
            rect2: Второй прямоугольник сравнения.
            precision: Точность сравнения.

        Return:
            True если прямоугольники равны.
        """
        if isinstance(rect1, Rect) and isinstance(rect2, Rect):
            return CommonUtils.approx_compare_rects(rect1.__rect, rect2.__rect, precision)
        return False

    @property
    def is_empty(self) -> bool:
        """Возвращает признак, что один или оба размера равны нулю."""
        return self.__rect.isEmpty()

    @property
    def is_valid(self) -> bool:
        """Возвращает признак, является ли прямоугольник правильным."""
        return self.__rect.isValid()

    def normalize(self) -> None:
        """
        Исправляет прямоугольник, если его ширина или высота отрицательны.

        .. literalinclude:: /../../tests/doc_examples/utl/test_example_rect.py
            :caption: Пример.
            :pyobject: test_run_example_rect
            :start-after: # start normalize
            :end-before: # finish normalize
            :dedent: 4
        """
        self.__rect = self.__rect.normalized()

    @property
    def width(self) -> float:
        """Возвращает ширину прямоугольника."""
        return self.__rect.width()

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

    def merge(self, other: "Rect") -> "Rect":
        """
        Возвращает прямоугольник, занимаемый обоими прямоугольниками.

        Args:
            other: Прямоугольник, с которым производится операция.

        .. literalinclude:: /../../tests/doc_examples/utl/test_example_rect.py
            :caption: Пример.
            :pyobject: test_run_example_rect
            :start-after: # start merge
            :end-before: # finish merge
            :dedent: 4
        """
        r2 = Rect._rect_value_to_qt(other)
        return Rect.from_qt(self.__rect.united(r2))

    def intersected(self, other: "Rect") -> "Rect":
        """
        Возвращает общий для обоих прямоугольник.

        Args:
            other: Прямоугольник, с которым производится операция.

        .. literalinclude:: /../../tests/doc_examples/utl/test_example_rect.py
            :caption: Пример.
            :pyobject: test_run_example_rect
            :start-after: # start intersected
            :end-before: # finish intersected
            :dedent: 4
        """
        r2 = Rect._rect_value_to_qt(other)
        return Rect.from_qt(self.__rect.intersected(r2))

    def intersects(self, other: "Rect") -> bool:
        """
        Проверяет, имеются ли пересечения с другим прямоугольником.

        Args:
            other: Прямоугольник, с которым производится операция.
        """
        r2 = Rect._rect_value_to_qt(other)
        return self.__rect.intersects(r2)

    def translated(self, dx: float, dy: float) -> "Rect":
        """
        Возвращает прямоугольник, смещенный на заданную величину.

        Args:
            dx: смещение по X.
            dy: смещение по Y.

        .. literalinclude:: /../../tests/doc_examples/utl/test_example_rect.py
            :caption: Пример.
            :pyobject: test_run_example_rect
            :start-after: # start translated
            :end-before: # finish translated
            :dedent: 4
        """
        return Rect.from_qt(self.__rect.translated(dx, dy))

    def expanded(self, dx: float, dy: float) -> "Rect":
        """
        Возвращает прямоугольник, увеличенный на заданные величины. Увеличение размеров
        производится по отношению к центру, который не меняется в результате операции.

        Args:
            dx: расширение по X.
            dy: расширение по Y.

        .. literalinclude:: /../../tests/doc_examples/utl/test_example_rect.py
            :caption: Пример.
            :pyobject: test_run_example_rect
            :start-after: # start expanded
            :end-before: # finish expanded
            :dedent: 4
        """
        dx2 = dx / 2
        dy2 = dy / 2
        return Rect.from_qt(self.__rect.adjusted(-dx2, -dy2, dx2, dy2))

    def contains(
        self,
        other: Union[
            Pnt,
            Tuple[float, float],
            QPointF,
            "Rect",
            Tuple[float, float, float, float],
            QRectF,
            QRect,
        ],
    ) -> bool:
        """
        Содержит ли полностью в своих границах переданный объект.

        Args:
            other: Переданный объект - точка или прямоугольник.

        .. literalinclude:: /../../tests/doc_examples/utl/test_example_rect.py
            :caption: Пример.
            :pyobject: test_run_example_rect
            :start-after: # start contains
            :end-before: # finish contains
            :dedent: 4
        """
        try:
            # check if arg is Rect-like
            r = Rect._rect_value_to_qt(other)  # type: ignore[arg-type]
            return self.__rect.contains(r)
        except ValueError:
            try:
                # check if arg is Pnt-like
                p = Pnt._point_value_to_qt(other)  # type: ignore[arg-type]
                return self.__rect.contains(p)
            except ValueError:
                pass
        return False

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Rect):
            return NotImplemented
        return CommonUtils.approx_compare_rects(self.__rect, other.__rect)

    def __repr__(self) -> str:
        params = {"precision": 15, "locale": QLocale.English}
        xmin = FloatCoord(self.xmin).as_string(**params)  # type: ignore[arg-type]
        ymin = FloatCoord(self.ymin).as_string(**params)  # type: ignore[arg-type]
        xmax = FloatCoord(self.xmax).as_string(**params)  # type: ignore[arg-type]
        ymax = FloatCoord(self.ymax).as_string(**params)  # type: ignore[arg-type]
        return f"axipy.Rect({xmin}, {ymin}, {xmax}, {ymax})"

    def __str__(self) -> str:
        return f"({self.xmin} {self.ymin}) ({self.xmax} {self.ymax})"

    def __format__(self, format_spec: str) -> str:
        return (
            f"({format(self.xmin, format_spec)} {format(self.ymin, format_spec)}) "
            f"({format(self.xmax, format_spec)} {format(self.ymax, format_spec)})"
        )

    @classmethod
    def _rect_value_to_qt(cls, v: Union[Tuple[float, float, float, float], "Rect", QRect, QRectF]) -> QRectF:
        if isinstance(v, tuple) and len(v) == 4:
            return QRectF(QPointF(v[0], v[1]), QPointF(v[2], v[3]))
        elif isinstance(v, Rect):
            return v.to_qt()
        elif isinstance(v, QRect):
            return QRectF(v)
        elif isinstance(v, QRectF):
            return v
        # Should be TypeError, back compat
        raise ValueError(
            f"Incorrect value type '{type(v)}'. "
            f"Supported types: Union[Tuple[float, float, float, float], 'Rect', QRect, QRectF]"
        )

    @classmethod
    def _rect_value_from_qt(cls, v: Union[QRectF, QRect, "Rect"]) -> "Rect":
        if isinstance(v, QRectF):
            return Rect.from_qt(v)
        elif isinstance(v, QRect):
            return Rect.from_qt(QRectF(v))
        elif isinstance(v, Rect):
            return v
        # Should be TypeError, back compat
        raise ValueError(f"Incorrect value type '{type(v)}'. " f"Supported types: Union[QRectF, QRect, 'Rect']")

    def clone(self) -> "Rect":
        """Возвращает копию."""
        return Rect.from_qt(QRectF(self.__rect))

    def __copy__(self) -> "Rect":
        return self.clone()

    def __deepcopy__(self, memo_dict: Optional[dict] = None) -> "Rect":
        return self.clone()
