from typing import Any, Iterable, List, Optional, Tuple, Union, cast, overload

from axipy.cpp_common import CommonUtils
from PySide2.QtCore import QLocale, QPoint, QPointF

from .float_formatter import FloatCoord

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


class Pnt:
    """
    Точка без геопривязки. Может быть использована в качестве параметра геометрии (точки
    полигона) или при получении параметров, где результат представлен в виде точки
    (центр карты или элемента отчета).

    .. literalinclude:: /../../tests/doc_examples/utl/test_example_pnt.py
        :caption: Создание точки.
        :pyobject: test_run_example_pnt
        :start-after: # start init
        :end-before: # finish init
        :dedent: 4
    """

    __slots__ = ("__point",)

    def __init__(self, x: float, y: float) -> None:
        """
        Конструктор класса.

        Args:
            x: X координата.
            y: Y координата.
        """
        self.__point: QPointF = QPointF(x, y)

    def _set_point(self, p: Union[QPoint, QPointF]) -> None:
        self.__point = p if isinstance(p, QPointF) else QPointF(p)

    @classmethod
    def from_qt(cls, p: Union[QPoint, QPointF]) -> "Pnt":
        """
        Преобразует из формата Qt.

        Args:
            p: Преобразуемая точка.

        .. literalinclude:: /../../tests/doc_examples/utl/test_example_pnt.py
            :caption:  Пример.
            :pyobject: test_run_example_pnt
            :start-after: # start from_qt
            :end-before: # finish from_qt
            :dedent: 4
        """
        # back compat
        if not isinstance(p, (QPointF, QPoint)):
            # noinspection PyTypeChecker
            return None

        inst = cls.__new__(cls)
        inst._set_point(p)
        return inst

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

        .. literalinclude:: /../../tests/doc_examples/utl/test_example_pnt.py
            :caption:  Пример.
            :pyobject: test_run_example_pnt
            :start-after: # start to_qt
            :end-before: # finish to_qt
            :dedent: 4
        """
        return self.__point

    @classmethod
    def from_tuple(cls, value: Tuple[float, float]) -> "Pnt":
        """Создает точку из кортежа координат."""
        return Pnt(*value)

    def to_tuple(self) -> Tuple[float, float]:
        """Возвращает координаты точки как кортеж."""
        return self.x, self.y

    @property
    def x(self) -> float:
        """Устанавливает или возвращает координату X."""
        return self.__point.x()

    @x.setter
    def x(self, v: float) -> None:
        self.__point.setX(v)

    @property
    def y(self) -> float:
        """Устанавливает или возвращает координату Y."""
        return self.__point.y()

    @y.setter
    def y(self, v: float) -> None:
        self.__point.setY(v)

    @classmethod
    def eq_approx(cls, point1: "Pnt", point2: "Pnt", precision: float = 1e-12) -> bool:
        """
        Сравнивает две точки с заданной точностью.

        Args:
            point1: Первая точка сравнения.
            point2: Вторая точка сравнения.
            precision: Точность сравнения.

        Return:
            True если точки равны.
        """
        pnt1 = Pnt._point_value_to_qt(point1)
        pnt2 = Pnt._point_value_to_qt(point2)
        return CommonUtils.approx_compare_points(pnt1, pnt2, precision)

    def __eq__(self, other: Union["Pnt", Tuple[float, float], Any]) -> bool:
        if isinstance(other, Pnt):
            return CommonUtils.approx_compare_points(self.__point, other.__point)
        if isinstance(other, tuple) and len(other) == 2:
            p2 = Pnt._point_value_to_qt(other)
            return CommonUtils.approx_compare_points(self.__point, p2)
        return False

    def __repr__(self) -> str:
        x = FloatCoord(self.x).as_string(precision=15, locale=QLocale.English)
        y = FloatCoord(self.y).as_string(precision=15, locale=QLocale.English)
        return f"axipy.Pnt({x}, {y})"

    def __str__(self) -> str:
        return f"({self.x} {self.y})"

    def __format__(self, format_spec: str) -> str:
        return f"({format(self.x, format_spec)} {format(self.y, format_spec)})"

    @staticmethod
    def _fix_pnt(v: Union[Tuple[float, float], "Pnt"]) -> "Pnt":
        """Преобразование из tuple в Pnt, если это возможно."""
        if isinstance(v, tuple) and len(v) == 2:
            return Pnt(v[0], v[1])

        return v

    @classmethod
    @overload
    def _point_value_to_qt(cls, v: Union[Tuple[float, float], "Pnt", QPointF]) -> QPointF: ...

    @classmethod
    @overload
    def _point_value_to_qt(cls, v: Iterable[Union[Tuple[float, float], "Pnt", QPointF]]) -> List[QPointF]: ...

    @classmethod
    def _point_value_to_qt(
        cls, v: Union[Tuple[float, float], "Pnt", QPointF, Iterable[Union[Tuple[float, float], "Pnt", QPointF]]]
    ) -> Union[QPointF, List[QPointF]]:
        if isinstance(v, tuple) and len(v) == 2:
            v = cast(Tuple[float, float], v)
            return QPointF(v[0], v[1])
        elif isinstance(v, Pnt):
            return v.to_qt()
        elif isinstance(v, QPointF):
            return v
        elif isinstance(v, Iterable):
            v = cast(Iterable[Union[Tuple[float, float], Pnt, QPointF]], v)
            return [cls._point_value_to_qt(p) for p in v]
        # Should be TypeError, back compat
        raise ValueError(
            f"Incorrect value type '{type(v)}'. "
            f"Supported types: Union[Tuple[float, float], 'Pnt', QPointF] "
            f"List[Union[Tuple[float, float], 'Pnt', QPointF]]"
        )

    @overload
    @classmethod
    def _point_value_from_qt(cls, v: Union[Tuple[float, float], QPointF, "Pnt"]) -> "Pnt": ...

    @overload
    @classmethod
    def _point_value_from_qt(cls, v: List[Union[Tuple[float, float], QPointF, "Pnt"]]) -> List["Pnt"]: ...

    @classmethod
    def _point_value_from_qt(
        cls, v: Union[Tuple[float, float], QPointF, "Pnt", List[Union[Tuple[float, float], QPointF, "Pnt"]]]
    ) -> Union["Pnt", List["Pnt"]]:
        if isinstance(v, tuple) and len(v) == 2:
            return Pnt(v[0], v[1])
        elif isinstance(v, QPointF):
            return Pnt.from_qt(v)
        elif isinstance(v, list):
            return [cls._point_value_from_qt(p) for p in v]
        elif isinstance(v, Pnt):
            return v
        # Should be TypeError, back compat
        raise ValueError(
            f"Incorrect value type '{type(v)}'. "
            f"Supported types: Union[Tuple[float, float], QPointF, 'Pnt'] "
            f"List[Union[Tuple[float, float], QPointF, 'Pnt']"
        )

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

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

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