import collections
import copy
import enum

import axipy
from axipy import Pnt
from axipy.cs import CoordSystem, CoordTransformer, Unit, UnitValue


class AngleType(enum.Enum):
    ClockWiseTop = 1
    CounterClockWiseRight = 2
    Relate = 3
    Math = 4

    @staticmethod
    def to_str(angle_type: "AngleType") -> str:
        if AngleType.Relate.value == angle_type.value:
            return axipy.tr("Относительно предыдущего сегмента")
        elif AngleType.ClockWiseTop.value == angle_type.value:
            return axipy.tr("Cверху по часовой стрелке")
        elif AngleType.CounterClockWiseRight.value == angle_type.value:
            return axipy.tr("Справа против часовой стрелки")
        raise RuntimeError("Не поддерживаемое значение списка")


class CSPoint:
    """Объект хранящий точку и её КС"""

    def __init__(self, point: Pnt, cs: CoordSystem) -> None:
        assert isinstance(point, Pnt)
        self.__point = point
        self.__cs = cs

    def to_pnt(self) -> Pnt:
        return self.__point

    @property
    def x(self) -> float:
        return self.__point.x

    @x.setter
    def x(self, value: float):
        self.__point.x = value

    @property
    def y(self) -> float:
        return self.__point.y

    @y.setter
    def y(self, value: float):
        self.__point.y = value

    @property
    def coordsystem(self) -> CoordSystem:
        return self.__cs

    @coordsystem.setter
    def coordsystem(self, value: CoordSystem):
        self.__cs = value

    def reproject(self, cs: CoordSystem) -> "CSPoint":
        if cs == self.coordsystem or cs is None:
            return copy.deepcopy(self)
        transformer = CoordTransformer(cs_from=self.__cs, cs_to=cs)
        return CSPoint(transformer.transform(self.__point), self.__cs)

    def __deepcopy__(self, memo) -> "CSPoint":
        return CSPoint(copy.deepcopy(self.__point, memo), self.__cs)


class CPAngle:
    """Класс хранящий угол и информацию о том откуда он отсчитывался"""

    def __init__(self, angle: float, angle_type: AngleType) -> None:
        self.value = angle
        self.type = angle_type


class CPNode:
    """Класс хранящий полную информацию о точке при построении с помощью угла и расстояния"""

    def __init__(self, point: CSPoint, angle: CPAngle, distance: UnitValue) -> None:
        assert isinstance(point, CSPoint)
        assert isinstance(angle, CPAngle)
        assert isinstance(distance, UnitValue)
        self.__cs_point = point
        self.__angle = angle
        self.__distance = distance

    def reproject(self, cs: CoordSystem) -> "CPNode":
        reprojected_node = copy.deepcopy(self)
        reprojected_node.cs_point = self.__cs_point.reproject(cs)
        return reprojected_node

    @property
    def coordsystem(self) -> CoordSystem:
        return self.__cs_point.coordsystem

    @coordsystem.setter
    def coordsystem(self, cs: CoordSystem):
        self.__cs_point.coordsystem = cs

    @property
    def distance(self) -> float:
        return self.__distance.value

    @distance.setter
    def distance(self, value):
        self.__distance.value = value

    @property
    def distance_type(self) -> Unit:
        return self.__distance.unit

    @distance_type.setter
    def distance_type(self, distance_type: Unit):
        self.__distance.unit = distance_type

    @property
    def angle(self) -> float:
        return self.__angle.value

    @angle.setter
    def angle(self, value: float):
        self.__angle.value = value

    @property
    def angle_type(self) -> AngleType:
        return self.__angle.type

    @angle_type.setter
    def angle_type(self, angle_type: AngleType):
        self.__angle.type = angle_type

    @property
    def cs_point(self) -> CSPoint:
        return self.__cs_point

    @cs_point.setter
    def cs_point(self, point: CSPoint):
        self.__cs_point = point

    def to_pnt(self) -> Pnt:
        return Pnt(self.__cs_point.x, self.__cs_point.y)

    @property
    def x(self) -> float:
        return self.__cs_point.x

    @x.setter
    def x(self, value: float):
        self.__cs_point.x = value

    @property
    def y(self) -> float:
        return self.__cs_point.y

    @y.setter
    def y(self, value: float):
        self.__cs_point.y = value

    def __deepcopy__(self, memo):
        copy_distance = UnitValue(copy.deepcopy(self.distance, memo), self.distance_type)
        return CPNode(copy.deepcopy(self.__cs_point, memo), copy.deepcopy(self.__angle, memo), copy_distance)


IndexRange = collections.namedtuple("IndexRange", ["top_left", "bottom_right"])


class CPModelMode(enum.Enum):
    """
    Варианты расчёта углов и расстояний
    """

    Sphere = 0
    Cartesian = 1


class RawNodeData:
    """
    Данные из которых модель создаёт узел для хранения. Например из данных
    полученных  по нажатию на карту или добавленных через таблицу
    """

    def __init__(self, point: CSPoint, evaluate_other=False) -> None:
        """
        Флаг evaluate_other говорит о том, что остальные параметры CadPolylinejode
        нужно будет вычислить, иначе в новом узле будут только координаты точки
        """
        self.point = point
        self.evaluate_other = evaluate_other
