from abc import abstractmethod
from typing import TYPE_CHECKING, Any, Final, List, Tuple, Union, cast

from axipy.cpp_render import (
    ShadowThematicBar,
    ShadowThematicDensity,
    ShadowThematicIndividual,
    ShadowThematicPie,
    ShadowThematicRange,
    ShadowThematicSymbol,
)
from axipy.da import Style, StyleGeometryType
from PySide2.QtCore import Qt
from PySide2.QtGui import QColor

from .layer import ThematicLayer

from axipy._internal._decorator import _experimental

if TYPE_CHECKING:
    from axipy.cpp_core_geometry import ShadowStyle

__all__: List[str] = [
    "StyledByIndexThematic",
    "ReallocateThematicColor",
    "RangeThematicLayer",
    "create_range",
    "AllocationThematic",
    "OrientationThematic",
    "PieThematicLayer",
    "create_pie",
    "BarThematicLayer",
    "SymbolThematicLayer",
    "IndividualThematicLayer",
    "DensityThematicLayer",
]


class StyledByIndexThematic:
    """Поддержка набора индексированных стилей."""

    @abstractmethod
    def _get_style_impl(self, idx: int) -> "ShadowStyle":
        raise NotImplementedError

    @property
    @abstractmethod
    def _shadow_can_set_style(self) -> Union[
        ShadowThematicBar,
        ShadowThematicIndividual,
        ShadowThematicPie,
        ShadowThematicRange,
    ]:
        raise NotImplementedError

    def get_style(self, idx: int) -> Style:
        """
        Стиль для указанного выражения.

        Args:
            idx: Порядковый номер выражения.
        """
        return cast(Style, Style._wrap(self._get_style_impl(idx)))

    def set_style(self, idx: int, style: Style) -> None:
        """
        Установка стиля оформления для выражения по его индексу в списке выражений.

        Args:
            idx: Индекс.
            style: Назначаемый стиль.

        .. literalinclude:: /../../tests/doc_examples/render/test_example_layer.py
            :caption: Пример установки стиля для значения с индексом 2 первого тематического слоя.
            :pyobject: test_run_example_layer_thematic
            :lines: 13-14
            :dedent: 4
        """
        self._shadow_can_set_style.set_style(idx, style._shadow)


# noinspection PyPep8Naming
class ReallocateThematicColor:
    """Поддержка различного рода алгоритмов распределения оформления."""

    @property
    @abstractmethod
    def _shadow_can_assign(self) -> Union[
        ShadowThematicIndividual,
        ShadowThematicRange,
    ]:
        raise NotImplementedError

    def assign_two_colors(
        self, colorMin: Union[QColor, Qt.GlobalColor], colorMax: Union[QColor, Qt.GlobalColor], useHSV: bool = False
    ) -> None:
        """
        Равномерно распределяет оформление по заданным крайним цветам.

        Args:
            colorMin: Цвет нижнего диапазона.
            colorMax: Цвет верхнего диапазона.
            useHSV: Если True, то будет использоваться схема HSV. В противном случае - RGB.
        """
        self._shadow_can_assign.assign_two_colors(colorMin, colorMax, useHSV)

    def assign_three_colors(
        self,
        colorMin: QColor,
        colorMax: QColor,
        colorBreak: QColor,
        br: int,
        useHSV: bool = True,
    ) -> None:
        """
        Цвет, распределенный между тремя заданными цветами (с разрывом).

        Args:
            colorMin: Цвет нижнего диапазона.
            colorMax: Цвет верхнего диапазона.
            colorBreak: Цвет на уровне разрыва.
            br: Индекс интервала, на котором используется цвет разрыва.
            useHSV: Если True, то будет использоваться схема HSV. В противном случае - RGB.
        """
        self._shadow_can_assign.assign_three_colors(colorMin, colorMax, colorBreak, br, useHSV)

    def assign_rainbow(self, sequential: bool = True, saturation: float = 90, value: float = 90) -> None:
        """
        Распределение цветов по спектру. Цветовая схема HSV.

        Args:
            sequential: Если True, то последовательное распределение цветов. В противном случае распределение случайно.
            saturation: Яркость. Задается в интервале (0..100)
            value: Насыщенность. Задается в интервале (0..100)
        """
        self._shadow_can_assign.assign_rainbow(sequential, saturation / 100, value / 100)

    def assign_gray(self, minV: int = 20, maxV: int = 80) -> None:
        """
        Распределение в виде градации серого. Значение задается в интервале (0..100) от
        черного до белого.

        Args:
            minV: Минимальное значение.
            maxV: Максимальное значение.
        """
        self._shadow_can_assign.assign_gray(minV, maxV)

    def assign_monotone(self, color: QColor, minv: int = 20, maxv: int = 80) -> None:
        """
        Монотонная заливка разной яркости (оттенки красного, синего и т.п.). Цветовая
        схема HSL. Максимальное и минимальное значения задаются в интервале (0..100).

        Args:
            color: Базовый цвет.
            minv: Минимальное значение.
            maxv: Максимальное значение.
        """
        self._shadow_can_assign.assign_monotone(color, minv, maxv)

    @property
    @_experimental()
    def _default_style(self) -> Style:
        """Стиль по умолчанию для игнорируемых или отсутствующих значений."""
        return cast(Style, Style._wrap(self._shadow_can_assign.get_default_style()))

    @_default_style.setter
    @_experimental()
    def _default_style(self, style: Style) -> None:
        self._shadow_can_assign.set_default_style(style._shadow)


class RangeThematicLayer(ThematicLayer, StyledByIndexThematic, ReallocateThematicColor):
    """
    Тематическое оформление слоя с распределением значений по интервалам. Для
    распределения цветов по заданным интервалам могут быть использованы функции
    `assign_*` класса :class:`ReallocateThematicColor` в зависимости от требуемых целей.

    Args:
        expression: Наименование атрибута таблицы или выражение.


    .. literalinclude:: /../../tests/doc_examples/render/test_example_layer.py
        :caption: Пример создания тематики по интервалам.
        :pyobject: test_run_example_layer_thematic_range
        :lines: 3-
        :dedent: 4
    """

    _shadow: ShadowThematicRange

    EQUAL_INTERVAL: Final[int] = 1
    """Распределение исходя из равномерности интервалов (по умолчанию)."""
    EQUAL_COUNT: Final[int] = 2
    """Распределение исходя их равного количества объектов в каждом интервале."""
    MANUAL: Final[int] = 3
    """Ручное распределение значений путем задания пределов вручную."""

    # noinspection PyMissingConstructor
    def __init__(self, expression: str) -> None:
        self._shadow = ShadowThematicRange(expression)

    @property
    def ranges(self) -> int:
        """Количество интервалов."""
        return self._shadow.get_ranges()

    @ranges.setter
    def ranges(self, n: int) -> None:
        self._shadow.set_ranges(n)

    @property
    def _shadow_can_set_style(self) -> Union[
        ShadowThematicBar,
        ShadowThematicIndividual,
        ShadowThematicPie,
        ShadowThematicRange,
    ]:
        return self._shadow

    @property
    def _shadow_can_assign(self) -> Union[
        ShadowThematicIndividual,
        ShadowThematicRange,
    ]:
        return self._shadow

    # noinspection PyPep8Naming
    @property
    def splitType(self) -> int:
        """
        Тип распределения значений по интервалам.

        .. csv-table:: Допустимые значения:
            :header: "Константа", "Значение", "Описание"
            :align: left
            :widths: 10, 5, 40

            EQUAL_INTERVAL, 1, "Распределение исходя из равномерности интервалов (по умолчанию)."
            EQUAL_COUNT, 2, "Распределение исходя их равного количества объектов в каждом интервале."
            MANUAL, 3, "Ручное распределение значений путем задания пределов вручную."
        """
        return self._shadow.get_split_type()

    # noinspection PyPep8Naming
    @splitType.setter
    def splitType(self, n: int) -> None:
        self._shadow.set_split_type(n)

    def get_interval_value(self, idx: int) -> Tuple[float, float]:
        """
        Возвращает предельные значения для указанного интервала в виде пары значений.

        Args:
            idx: Индекс диапазона.
        """
        return self._shadow.get_interval_values(idx)

    def set_interval_value(self, idx: int, v: Tuple[float, float]) -> None:
        """
        Заменяет предельные значения интервала.

        Args:
            idx: Индекс диапазона.
            v: Значение в виде пары.
        """
        self._shadow.set_interval_values(idx, v)

    def _get_style_impl(self, idx: int) -> "ShadowStyle":
        return ShadowThematicRange.get_style(self._shadow, idx)

    @property
    def style_geometry_type(self) -> StyleGeometryType:
        """
        Тип геометрического объекта для построения легенды.

        Т.е. какой объект для отображения стиля будет представлен в легенде.
        """
        return StyleGeometryType(self._shadow.get_geometry_class())

    @style_geometry_type.setter
    def style_geometry_type(self, v: StyleGeometryType) -> None:
        self._shadow.set_geometry_class(v)

    @property
    @_experimental()
    def _expression(self) -> str:
        """Выражение, заданное при формировании тематики."""
        return self._shadow.expression()



# noinspection PyPep8Naming
def create_range(
    expression: str,
    ranges: int = 6,
    colorMin: Union[QColor, Qt.GlobalColor] = Qt.yellow,
    colorMax: Union[QColor, Qt.GlobalColor] = Qt.red,
    splitType: int = RangeThematicLayer.EQUAL_INTERVAL,
) -> RangeThematicLayer:
    # noinspection PyShadowingBuiltins
    range = RangeThematicLayer(expression)
    range.ranges = ranges
    range.assign_two_colors(colorMin, colorMax)
    range.splitType = splitType
    return range


class AllocationThematic:
    """Метод распределения значений для диаграмм."""

    LINEAR = 1
    """Линейное (по умолчанию)"""
    SQRT = 2
    """Квадратичное."""
    LOG10 = 3
    """Логарифмическое."""

    @property
    @abstractmethod
    def _shadow_with_allocation_type(self) -> Union[
        ShadowThematicBar,
        ShadowThematicPie,
    ]:
        raise NotImplementedError

    # noinspection PyPep8Naming
    @property
    def allocationType(self) -> int:
        """
        Тип распределения значений.

        .. csv-table:: Допустимые значения.
            :header: "Константа", "Значение", "Описание"
            :align: left
            :widths: 10, 10, 30

            LINEAR, 1, "Линейное (по умолчанию)"
            SQRT, 2, "Квадратичное"
            LOG10, 3, "Логарифмическое"
        """
        return self._shadow_with_allocation_type.get_allocation_type()

    # noinspection PyPep8Naming
    @allocationType.setter
    def allocationType(self, n: int) -> None:
        self._shadow_with_allocation_type.set_allocation_type(n)


class OrientationThematic:
    """Ориентация тематического представления относительно центроида объекта."""

    CENTER = 0
    """Диаграмма рисуется по центру (по умолчанию)"""
    LEFT_UP = 1
    """Диаграмма выравнивается по левому верхнему краю."""
    UP = 2
    """Диаграмма выравнивается по верхнему краю."""
    RIGHT_UP = 3
    """Диаграмма выравнивается по верхнему правому краю."""
    RIGHT = 4
    """Диаграмма выравнивается по правому краю."""
    RIGHT_DOWN = 5
    """Диаграмма выравнивается по нижнему правому краю."""
    DOWN = 6
    """Диаграмма выравнивается по нижнему краю."""
    LEFT_DOWN = 7
    """Диаграмма выравнивается по нижнему левому краю."""
    LEFT = 8
    """Диаграмма выравнивается по левому краю."""

    @property
    @abstractmethod
    def _shadow_with_orientation_type(self) -> Union[
        ShadowThematicBar,
        ShadowThematicPie,
    ]:
        raise NotImplementedError

    # noinspection PyPep8Naming
    @property
    def orientationType(self) -> int:
        """
        Ориентация относительно центроида.

        .. csv-table:: Допустимые значения.
            :header: "Константа", "Значение", "Описание"
            :align: left
            :widths: 10, 10, 30

            CENTER, 0, "Диаграмма рисуется по центру (по умолчанию)"
            LEFT_UP, 1, "Диаграмма выравнивается по левому верхнему краю"
            UP, 2, "Диаграмма выравнивается по верхнему краю"
            RIGHT_UP, 3, "Диаграмма выравнивается по верхнему правому краю"
            RIGHT, 4, "Диаграмма выравнивается по правому краю"
            RIGHT_DOWN, 5, "Диаграмма выравнивается по нижнему правому краю"
            DOWN, 6, "Диаграмма выравнивается по нижнему краю"
            LEFT_DOWN, 7, "Диаграмма выравнивается по нижнему левому краю"
            LEFT, 8, "Диаграмма выравнивается по левому краю"
        """
        return self._shadow_with_orientation_type.get_orientation_type()

    # noinspection PyPep8Naming
    @orientationType.setter
    def orientationType(self, n: int) -> None:
        self._shadow_with_orientation_type.set_orientation_type(n)


class PieThematicLayer(ThematicLayer, AllocationThematic, OrientationThematic, StyledByIndexThematic):
    """
    Тематика в виде круговых диаграмм.

    Args:
        expressions: Наименования атрибутов или выражений в виде списка :class:`list`.

    .. literalinclude:: /../../tests/doc_examples/render/test_example_layer.py
        :caption: Создание тематики с последующим добавлением ее к базовому слою.
        :pyobject: test_run_example_layer_thematic_pie
        :lines: 3-
        :dedent: 4
    """

    _shadow: ShadowThematicPie

    # noinspection PyMissingConstructor
    def __init__(self, expressions: List) -> None:
        self._shadow = ShadowThematicPie(expressions)

    @property
    def _shadow_can_set_style(self) -> Union[
        ShadowThematicBar,
        ShadowThematicIndividual,
        ShadowThematicPie,
        ShadowThematicRange,
    ]:
        return self._shadow

    @property
    def _shadow_with_orientation_type(self) -> Union[
        ShadowThematicBar,
        ShadowThematicPie,
    ]:
        return self._shadow

    @property
    def _shadow_with_allocation_type(self) -> Union[
        ShadowThematicBar,
        ShadowThematicPie,
    ]:
        return self._shadow

    # noinspection PyPep8Naming
    @property
    def startAngle(self) -> int:
        """Начальный угол отсчета диаграммы."""
        return self._shadow.get_start_angle()

    # noinspection PyPep8Naming
    @startAngle.setter
    def startAngle(self, v: int) -> None:
        self._shadow.set_start_angle(v)

    def _get_style_impl(self, idx: int) -> "ShadowStyle":
        return ShadowThematicPie.get_style(self._shadow, idx)
    
    @property
    @_experimental()
    def _expressions(self) -> List[str]:
        """Выражения, заданные при формировании тематики."""
        return self._shadow.expressions()



# noinspection PyPep8Naming
def create_pie(expressions: List, allocationType: int = AllocationThematic.LINEAR) -> PieThematicLayer:
    pie = PieThematicLayer(expressions)
    pie.allocationType = allocationType
    return pie


class BarThematicLayer(ThematicLayer, AllocationThematic, OrientationThematic, StyledByIndexThematic):
    """
    Тематика в виде столбчатых диаграмм.

    Args:
        expressions: Наименования атрибутов или выражений в виде списка :class:`list`.

    .. literalinclude:: /../../tests/doc_examples/render/test_example_layer.py
        :caption: Создание тематики с последующим добавлением ее к базовому слою.
        :pyobject: test_run_example_layer_thematic_bar
        :lines: 3-
        :dedent: 4
    """

    _shadow: ShadowThematicBar

    # noinspection PyMissingConstructor
    def __init__(self, expressions: List) -> None:
        self._shadow = ShadowThematicBar(expressions)

    @property
    def _shadow_with_allocation_type(self) -> Union[
        ShadowThematicBar,
        ShadowThematicPie,
    ]:
        return self._shadow

    @property
    def _shadow_with_orientation_type(self) -> Union[
        ShadowThematicBar,
        ShadowThematicPie,
    ]:
        return self._shadow

    @property
    def _shadow_can_set_style(self) -> Union[
        ShadowThematicBar,
        ShadowThematicIndividual,
        ShadowThematicPie,
        ShadowThematicRange,
    ]:
        return self._shadow

    # noinspection PyPep8Naming
    @property
    def isStacked(self) -> bool:
        """Расположение столбчатой диаграммы в виде стопки, если True."""
        return self._shadow.get_is_stacked()

    # noinspection PyPep8Naming
    @isStacked.setter
    def isStacked(self, v: bool) -> None:
        self._shadow.set_is_stacked(v)

    def _get_style_impl(self, idx: int) -> "ShadowStyle":
        return ShadowThematicBar.get_style(self._shadow, idx)
    
    @property
    @_experimental()
    def _expressions(self) -> List[str]:
        """Выражения, заданные при формировании тематики."""
        return self._shadow.expressions()


# noinspection PyPep8Naming
class SymbolThematicLayer(ThematicLayer):
    """
    Тематический слой с распределением по интервалам и с градуировкой символа по
    размеру.

    Args:
        expression: Наименование атрибута или выражение.

    .. literalinclude:: /../../tests/doc_examples/render/test_example_layer.py
        :caption: Создание тематики с последующим добавлением ее к базовому слою.
        :pyobject: test_run_example_layer_thematic_symbol
        :lines: 3-
        :dedent: 4
    """

    _shadow: ShadowThematicSymbol

    # noinspection PyMissingConstructor
    def __init__(self, expression: str) -> None:
        self._shadow = ShadowThematicSymbol(expression)

    @property
    def minHeight(self) -> int:
        """Минимальная высота символа."""
        return self._shadow.get_min_height()

    @minHeight.setter
    def minHeight(self, v: int) -> None:
        self._shadow.set_min_height(v)

    @property
    def maxHeight(self) -> int:
        """Максимальная высота символа."""
        return self._shadow.get_max_height()

    @maxHeight.setter
    def maxHeight(self, v: int) -> None:
        self._shadow.set_max_height(v)

    @property
    def defaultStyle(self) -> Style:
        """Стиль по умолчанию для оформления знаков."""
        return cast(Style, Style._wrap(self._shadow.get_default_style()))

    @defaultStyle.setter
    def defaultStyle(self, style: Style) -> None:
        self._shadow.set_default_style(style._shadow)

    @property
    @_experimental()
    def _expression(self) -> str:
        """Выражение, заданное при формировании тематики."""
        return self._shadow.expression()


class IndividualThematicLayer(ThematicLayer, StyledByIndexThematic, ReallocateThematicColor):
    """
    Тематический слой с распределением стилей по индивидуальным значением.

    Args:
        expression: Наименование атрибута или выражение.

    .. literalinclude:: /../../tests/doc_examples/render/test_example_layer.py
        :caption: Создание тематики с последующим добавлением ее к базовому слою.
        :pyobject: test_run_example_layer_thematic_individual
        :lines: 3-
        :dedent: 4
    """

    _shadow: ShadowThematicIndividual

    # noinspection PyMissingConstructor
    def __init__(self, expression: str) -> None:
        self._shadow = ShadowThematicIndividual(expression)

    @property
    def _shadow_can_set_style(self) -> Union[
        ShadowThematicBar,
        ShadowThematicIndividual,
        ShadowThematicPie,
        ShadowThematicRange,
    ]:
        return self._shadow

    @property
    def _shadow_can_assign(self) -> Union[
        ShadowThematicIndividual,
        ShadowThematicRange,
    ]:
        return self._shadow

    @property
    def count(self) -> int:
        """Количество значений в тематике."""
        return self._shadow.get_count()

    def get_value(self, idx: int) -> Any:
        """
        Выражение по указанному индексу.

        Args:
            idx: Индекс.
        """
        return self._shadow.get_value(idx)

    def _get_style_impl(self, idx: int) -> "ShadowStyle":
        return ShadowThematicIndividual.get_style(self._shadow, idx)

    def __len__(self) -> int:
        return self.count

    @property
    def style_geometry_type(self) -> StyleGeometryType:
        """
        Тип геометрического объекта для построения легенды.

        Т.е. какой объект для отображения стиля будет представлен в легенде.
        """
        return StyleGeometryType(self._shadow.get_geometry_class())

    @style_geometry_type.setter
    def style_geometry_type(self, v: StyleGeometryType) -> None:
        self._shadow.set_geometry_class(v)

    @property
    @_experimental()
    def _expression(self) -> str:
        """Выражение, заданное при формировании тематики."""
        return self._shadow.expression()



class DensityThematicLayer(ThematicLayer):
    """
    Тематический слой с заполнением полигональных объектов точками, плотность которых
    зависит от вычисленного значения по выражению.

    Args:
        expression: Наименование атрибута или выражение.

    .. literalinclude:: /../../tests/doc_examples/render/test_example_layer.py
        :caption: Создание тематики с последующим добавлением ее к базовому слою.
        :pyobject: test_run_example_layer_thematic_density
        :lines: 3-
        :dedent: 4
    """

    _shadow: ShadowThematicDensity

    # noinspection PyMissingConstructor
    def __init__(self, expression: str) -> None:
        self._shadow = ShadowThematicDensity(expression)

    # noinspection PyPep8Naming
    @property
    def pointForMaximum(self) -> int:
        """Количество точек для максимального значения."""
        return self._shadow.get_point_for_maximum()

    # noinspection PyPep8Naming
    @pointForMaximum.setter
    def pointForMaximum(self, cnt: int) -> None:
        self._shadow.set_point_for_maximum(cnt)

    @property
    def color(self) -> QColor:
        """Цвет точек."""
        return self._shadow.get_color()

    @color.setter
    def color(self, c: QColor) -> None:
        self._shadow.set_color(c)

    @property
    def size(self) -> float:
        """Размер точек."""
        return self._shadow.get_size()

    @size.setter
    def size(self, v: float) -> None:
        self._shadow.set_size(v)

    @property
    @_experimental()
    def _expression(self) -> str:
        """Выражение, заданное при формировании тематики."""
        return self._shadow.expression()
