import math
from abc import ABC, abstractmethod
from typing import List, Optional, SupportsFloat, Tuple, Union, cast

from axipy._internal._decorator import _deprecated_by
from axipy.cpp_gui import ShadowCoordFormatter

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


class CoordFormatter:
    """
    Класс производит преобразование значений из строки в тип float и обратно. Под
    значениями в первую очередь следует понимать координаты объектов.

    Warning:

        .. deprecated:: 4.5
            Используйте :attr:`axipy.AngleCoord`.

    .. literalinclude:: /../../tests/doc_examples/utl/test_example_coord_formatter.py
        :caption: Пример использования.
        :pyobject: test_run_example_coord_formatter
        :start-after: # start
        :end-before: # finish
        :dedent: 4
    """

    def __init__(self) -> None:
        self._reader = ShadowCoordFormatter()

    @_deprecated_by("axipy.AngleCoord.value")
    def as_float(self, value: str) -> Tuple[float, bool]:
        """
        Преобразует строку в числовое значение.

        Допустимый формат входной строки:
        `<dd°mm'ss,zz">`, `<dd mm ss,zz>`, `<dd/mm/ss,zz>`, `<dd-mm-ss,zz>`, `<dd,mm,ss.zz>`, `<dd.zz>`, `<dd,zz>`
        или в румбах `ЮВ dd.zz`.

        Args:
            value: Значение в виде строки.

        Return:
            Пара значение/успешность операции.
        """
        return self._reader.as_float(value)

    @_deprecated_by("axipy.AngleCoord.as_string")
    def as_string(self, value: float, precision: int = -1, suppress_trailing_zeros: bool = False) -> str:
        """
        Преобразует число в строку в формате `<dd°mm'ss,zz">`.

        Args:
            value: Входное значение.
            precision: Количество знаков после запятой. Если `-1`, округление не производится.
            suppress_trailing_zeros: Признак удаления завершающих нулей.
        """
        return self._reader.as_string(value, precision, suppress_trailing_zeros)

    @_deprecated_by("axipy.AngleCoord.as_string")
    def as_string_with_delimeter(
        self,
        value: float,
        delimeter: str,
        precision: int = -1,
        suppress_trailing_zeros: bool = False,
    ) -> str:
        """
        Преобразует число в строку по формату с заданным разделителем.

        Args:
            value: Входное значение.
            delimeter: Разделитель. Например, '-' или '/'.
            precision: Количество знаков после запятой. Если `-1`, округление не производится.
            suppress_trailing_zeros: Признак удаления завершающих нулей.
        """
        return self._reader.as_string_with_delimeter(value, delimeter, precision, suppress_trailing_zeros)

    @_deprecated_by("axipy.AngleCoord.as_rumb")
    def double_to_rumb(self, value: float, precision: int = -1, suppress_trailing_zeros: bool = False) -> str:
        """
        Преобразует число в строку в формате румбов.

        Args:
            value: Значение в градусах.
            precision: Количество знаков после запятой. Если `-1`, округление не производится.
            suppress_trailing_zeros: Признак удаления завершающих нулей.

        Return:
            Строка в формате румбов.
        """
        return self._reader.double_to_rumb(value, precision, suppress_trailing_zeros)

    @_deprecated_by("axipy.AngleCoord properties")
    def split_degree(self, value: float) -> Tuple[int, int, float]:
        """
        Разбивает значение на три секции (градусы, минуты, секунды)

        Args:
            value: Значение в градусах.

        Return:
            (градусы, минуты, секунды)
        """
        res = self._reader.split_degree(value)
        return res[0][0], res[0][1], res[1]


class _Coord(ABC):
    _reader = ShadowCoordFormatter()

    @classmethod
    def _reader_as_float(cls, value: Union[float, int, str]) -> float:
        result, success = cls._reader.as_float(value)
        if success:
            return result
        else:
            raise ValueError(f"Can't transform {value=}.")

    def __init__(self, value: Union[float, int, str]) -> None:
        """
        Создает координату из значения в различных форматах.

        Args:
            value: Значение может быть:

                * целым числом;
                * числом с плавающей точкой;
                * строкой, представляющей целое число или число с плавающей точкой;
                * строкой, представляющей угловую координату с разделителями, или в формате румбов.

                (``<dd°mm'ss,zz">``, ``<dd mm ss,zz>``, ``<dd/mm/ss,zz>``, ``<dd-mm-ss,zz>``, ``<dd,mm,ss.zz>``,
                ``<dd.zz>``, ``<dd,zz>`` или в румбах ``ЮВ dd.zz``.)

        Raises:
            ValueError: если не удалось преобразовать значение в число с плавающей точкой.
        """
        self._value = _Coord._reader_as_float(value)

    @property
    def value(self) -> float:
        """Возвращает числовое значение в формате числа с плавающей точкой (:class:`float`)."""
        return self._value

    # Type conversion

    def __int__(self) -> int:
        return int(self._value)

    def __float__(self) -> float:
        return self._value

    @abstractmethod
    def as_string(self) -> str: ...

    def __str__(self) -> str:
        return self.as_string()

    @abstractmethod
    def _repr_arg(self) -> str: ...

    def __repr__(self) -> str:
        return f"axipy.{self.__class__.__name__}({self._repr_arg()})"

    def __bool__(self) -> bool:
        """
        True if self != 0.

        Called for bool(self).
        """
        return self != 0

    # Arithmetic operators

    def __neg__(self) -> "_Coord":
        """-self"""
        return self.__class__(-self._value)

    def __pos__(self) -> "_Coord":
        """+self."""
        return self.__class__(self._value)

    def __process_other(self, other: object) -> Optional[float]:
        other_value: Optional[SupportsFloat] = None
        if isinstance(other, _Coord):
            other_value = other.value
        elif isinstance(other, SupportsFloat):
            other_value = other
        # SupportsFloat and float are interchangeable in this case
        return cast(Optional[float], other_value)

    def __add__(self, other: object) -> "_Coord":
        """Self + other."""
        other_value = self.__process_other(other)
        if other_value is not None:
            return self.__class__(self.value + other_value)
        return NotImplemented

    def __radd__(self, other: object) -> "_Coord":
        """Other + self."""
        other_value = self.__process_other(other)
        if other_value is not None:
            return self.__class__(other_value + self.value)
        return NotImplemented

    def __sub__(self, other: object) -> "_Coord":
        """self - other"""
        if hasattr(other, "__neg__"):
            return self + -other
        other_value = self.__process_other(other)
        if other_value is not None:
            return self.__class__(self.value - other_value)
        return NotImplemented

    def __rsub__(self, other: object) -> "_Coord":
        """other - self"""
        return -self + other

    def __mul__(self, other: object) -> "_Coord":
        """Self * other."""
        other_value = self.__process_other(other)
        if other_value is not None:
            return self.__class__(self.value * other_value)
        return NotImplemented

    def __rmul__(self, other: object) -> "_Coord":
        """Other * self."""
        other_value = self.__process_other(other)
        if other_value is not None:
            return self.__class__(other_value * self.value)
        return NotImplemented

    def __truediv__(self, other: object) -> "_Coord":
        """Self / other."""
        other_value = self.__process_other(other)
        if other_value is not None:
            return self.__class__(self.value / other_value)
        return NotImplemented

    def __rtruediv__(self, other: object) -> "_Coord":
        """Other / self."""
        other_value = self.__process_other(other)
        if other_value is not None:
            return self.__class__(other_value / self.value)
        return NotImplemented

    def __floordiv__(self, other: object) -> "_Coord":
        """Self // other."""
        other_value = self.__process_other(other)
        if other_value is not None:
            return self.__class__(self.value // other_value)
        return NotImplemented

    def __mod__(self, other: object) -> "_Coord":
        """Self % other."""
        other_value = self.__process_other(other)
        if other_value is not None:
            return self.__class__(self.value % other_value)
        return NotImplemented

    # Comparison

    def __lt__(self, other: object) -> bool:
        """A < b."""
        other_value = self.__process_other(other)
        if other_value is not None:
            return self.value < other_value
        return NotImplemented

    def __le__(self, other: object) -> bool:
        """A <= b."""
        other_value = self.__process_other(other)
        if other_value is not None:
            return self.value <= other_value
        return NotImplemented

    def __eq__(self, other: object) -> bool:
        """Self == other."""
        other_value = self.__process_other(other)
        if other_value is not None:
            return math.isclose(self.value, other_value)
        return NotImplemented

    def __gt__(self, other: object) -> bool:
        """A > b."""
        other_value = self.__process_other(other)
        if other_value is not None:
            return self.value > other_value
        return NotImplemented

    def __ge__(self, other: object) -> bool:
        """A >= b."""
        other_value = self.__process_other(other)
        if other_value is not None:
            return self.value >= other_value
        return NotImplemented
