"""Модуль операций с растрами."""

from enum import Enum
from typing import List, NamedTuple, Tuple, Union

from axipy.cpp_core_dp import PointBinding, RasterFacade
from axipy.cs import CoordSystem
from PySide2.QtCore import QPointF
from PySide2.QtGui import QTransform

__all__: List[str] = [
    "GCP",
    "Algorithm",
    "Compression",
    "Format",
    "Resample",
    "register",
    "transform",
]


class GCP(NamedTuple):
    """Точка привязки (Ground Control Point)."""

    device: Union[Tuple[float, float], QPointF]
    """Точка на изображении (пиксели)."""
    scene: Union[Tuple[float, float], QPointF]
    """Точка на карте."""
    label: str = ""
    """Идентификатор."""

    @staticmethod
    def _wrap(p: PointBinding) -> "GCP":
        # types will be checked in auto tests. Reason: attribute types aren't supported by stub files generator.
        return GCP(
            (p.rasterPoint.x(), p.rasterPoint.y()),  # type: ignore[attr-defined]
            (p.worldPoint.x(), p.worldPoint.y()),  # type: ignore[attr-defined]
            p.description,  # type: ignore[attr-defined]
        )

    def _to_qpointf(self, p: Union[Tuple[float, float], QPointF]) -> QPointF:
        if not isinstance(p, QPointF):
            p = QPointF(*p)
        return p

    def _to_cpp(self) -> PointBinding:
        return PointBinding(self._to_qpointf(self.device), self._to_qpointf(self.scene), self.label)


class Algorithm(Enum):
    """Алгоритм трансформации."""

    POLYNOM1 = 1
    """Аффинитет."""
    POLYNOM2 = 2
    """Полиномиальный второго порядка."""
    POLYNOM3 = 3
    """Полиномиальный третьего порядка."""
    SPLINE = 4
    """Сплайновый."""


class Compression(Enum):
    """
    Сжатие.

    Note:
        Сжатие можно использовать только для файлов формата GeoTIFF.
    """

    NONE = 1
    PACKBITS = 2
    LZW = 3
    DEFLATE = 4


class Format(Enum):
    """Формат изображения."""

    JPEG = 1
    PNG = 2
    BMP = 3
    GTiff = 4


class Resample(Enum):
    """Метод интерполяции."""

    NearestNeighbour = 0
    """Ближайший."""
    Bilinear = 1
    """Билинейный."""
    Cubic = 2
    """Кубический."""
    CubicSpline = 3
    """Кубический сплайн."""
    Lanczos = 4
    """Ланцоша."""
    Average = 5
    """Средний."""
    Mode = 6
    """Самый встречающийся."""
    Max = 8
    """Максимальный."""
    Min = 9
    """Минимальный."""
    Med = 10
    """Медианный."""
    Q1 = 11
    """Первый квартиль."""
    Q3 = 12
    """Третий квартиль."""
    Sum = 13
    """Сумма."""


def register(
    filepath: str,
    bindings: Union[List[GCP], QTransform],
    coordsystem: CoordSystem,
    override: bool = False,
) -> None:
    """
    Регистрирует растр.

    Добавляет изображению пространственную привязку.

    Args:
        filepath: Файл с изображением.
        bindings: Привязка в виде точек или матрицы преобразования.
        coordsystem: Координатная система.
        override: При регистрации формируется файл привязки TAB.
            Если он существует, то данный параметр управляет его перезаписью.

    .. literalinclude:: /../../tests/doc_examples/da/test_example_raster_register.py
        :caption: Пример использования
        :pyobject: example_raster_register_matrix
        :lines: 2-
        :dedent: 4
    """
    if isinstance(bindings, QTransform):
        RasterFacade.registerAffine(filepath, bindings, coordsystem._shadow, override)
    elif isinstance(bindings, list) and all([isinstance(binding, GCP) for binding in bindings]):
        RasterFacade.registerPoints(
            filepath,
            list(map(lambda p: p._to_cpp(), bindings)),
            coordsystem._shadow,
            override,
        )
    else:
        raise TypeError


def transform(
    inputfile: str,
    outputfile: str,
    points: List[GCP],
    coordsystem: CoordSystem,
    algorithm: Algorithm = Algorithm.SPLINE,
    resample: Resample = Resample.NearestNeighbour,
    output_format: Format = Format.GTiff,
    compression: Compression = Compression.NONE,
) -> None:
    """
    Трансформирует растр.

    Растру, имеющему пространственную привязку, задает новую привязку. На выходе
    получается новый растр.

    Args:
        inputfile: Входной файл с растром.
        outputfile: Выходной файл с растром.
        points: Точки привязки.
        coordsystem: Координатная система.
        algorithm: Алгоритм трансформации.
        resample: Метод интерполяции.
        output_format: Выходной формат.
        compression: Метод сжатия.

    .. literalinclude:: /../../tests/doc_examples/da/test_example_raster_register.py
        :caption: Пример использования
        :pyobject: example_raster_transform
        :lines: 2-
        :dedent: 4
    """
    RasterFacade.transform(
        inputfile,
        outputfile,
        list(map(lambda p: p._to_cpp(), points)),
        coordsystem._shadow,
        algorithm.value,
        resample.value,
        output_format.name,
        compression.name,
    )
