from abc import abstractmethod
from enum import Enum
from typing import (
    TYPE_CHECKING,
    Final,
    List,
    Literal,
    NamedTuple,
    Optional,
    Protocol,
    Type,
    Union,
    cast,
    overload,
)

from axipy._internal._decorator import _deprecated_by, _experimental
from axipy._internal._utils import _AxiReprMeta
from axipy.cpp_common import FileFormatType as ShadowFileFormatType
from axipy.cpp_core_geometry import (
    ShadowCollectionStyle,
    ShadowFillStyle,
    ShadowLineStyle,
    ShadowPointCompatStyle,
    ShadowPointFontStyle,
    ShadowPointPictureStyle,
    ShadowPointStyle,
    ShadowPolygonStyle,
    ShadowStyle,
    ShadowTextStyle,
)
from PySide2.QtCore import Qt
from PySide2.QtGui import QColor, QPainter

from .file_format_type import FileFormatType
from .geometry import (
    Geometry,
    GeometryCollection,
    Line,
    LineString,
    MultiLineString,
    MultiPoint,
    MultiPolygon,
    Point,
    Polygon,
)

if TYPE_CHECKING:
    from axipy.mi.mi_geometry import Arc, Ellipse, Rectangle, RoundRectangle, Text

__all__: List[str] = [
    "Style",
    "PointStyle",
    "PointCompatStyle",
    "PointFontStyle",
    "PointPictureStyle",
    "LineStyle",
    "FillStyle",
    "PolygonStyle",
    "TextBackgroundType",
    "TextStyle",
    "CollectionStyle",
    "TextCallout",
    "TextAlignment",
    "StyleGeometryType",
]


class StyleGeometryType(int, Enum):
    """
    Тип геометрического объекта для стиля.

    Используется как категория геометрии при выборе стиля представления этой геометрии.
    """

    Point = 1
    """Точечные."""
    Linear = 2
    """Линейные."""
    Polygonal = 3
    """Полигональные."""
    MultiGeometry = 4
    """Разнородная коллекция."""
    Text = 5
    """Текстовые."""


class Style(metaclass=_AxiReprMeta):
    """
    Абстрактный класс стиля оформления геометрического объекта. Определяет как будет
    отображен геометрический объект.

    Note:
        Для получения текстового представления стиля можно воспользоваться функцией :class:`str`.
    """

    _shadow: ShadowStyle

    def __init__(self) -> None:
        raise NotImplementedError

    @classmethod
    def from_mapinfo(cls, mapbasic_string: str) -> Optional["Style"]:
        """
        Получает стиль из строки формата MapBasic.

        Args:
            mapbasic_string: Строка в формате MapBasic.

        .. literalinclude:: /../../tests/doc_examples/da/test_example_style.py
            :pyobject: test_run_example_style_from_mi
            :lines: 2
            :dedent: 4
        """
        result = ShadowStyle.from_mapinfo(mapbasic_string)
        if result is None or result.isEmpty():
            return None
        return Style._wrap(result)

    def to_mapinfo(self) -> str:
        """
        Возвращает строковое представление в формате MapBasic.

        .. literalinclude:: /../../tests/doc_examples/da/test_example_style.py
            :pyobject: test_run_example_style_from_mi
            :lines: 3-
            :dedent: 4
        """
        return self._shadow.to_mapinfo().strip()

    @overload
    @classmethod
    def for_geometry(cls, geom: Union["Point", "MultiPoint"]) -> "PointStyle": ...  # type: ignore[overload-overlap]

    @overload
    @classmethod
    def for_geometry(  # type: ignore[overload-overlap]
        cls,
        geom: Union[
            "Line",
            "LineString",
            "Arc",
            "MultiLineString",
        ],
    ) -> "LineStyle": ...

    @overload
    @classmethod
    def for_geometry(  # type: ignore[overload-overlap]
        cls,
        geom: Union[
            "Polygon",
            "Rectangle",
            "RoundRectangle",
            "Ellipse",
            "MultiPolygon",
        ],
    ) -> "PolygonStyle": ...

    @overload
    @classmethod
    def for_geometry(cls, geom: "GeometryCollection") -> "CollectionStyle": ...

    @overload
    @classmethod
    def for_geometry(cls, geom: "Text") -> "TextStyle": ...

    @overload
    @classmethod
    def for_geometry(cls, geom: "Geometry") -> "Style": ...

    @classmethod
    def for_geometry(cls, geom: "Geometry") -> "Style":
        """
        Возвращает стиль по умолчанию из настроек для переданного объекта.

        Args:
            geom: Геометрический объект, для которого необходимо получить соответствующий ему стиль.
        """
        return cast(Style, Style._wrap(ShadowStyle.for_geometry(geom._shadow)))
    
    @classmethod
    @_experimental()
    def _for_geometry_current(cls, geom: "Geometry") -> "Style":
        """
        Возвращает текущий стиль для переданного объекта.

        Args:
            geom: Геометрический объект, для которого необходимо получить соответствующий ему стиль.
        """
        return cast(Style, Style._wrap(ShadowStyle.for_geometry_current(geom._shadow)))
    
    @classmethod
    @_experimental()
    def _update_current_style(cls, style: "Style") -> None:
        """
        Обновляет текущий стиль для новых объектов.

        Args:
            style: Стиль.
            

        Пример::

            from PySide2.QtCore import Qt
            # Получение текущего стиля для точки
            curr=axipy.Style._for_geometry_current(axipy.Point(0,0))
            # Обновление параметров (установка цвета)
            curr.color=Qt.green
            # Замена
            axipy.Style._update_current_style(curr)
        """
        if style is None:
            return
        ShadowStyle.update_current_style(style._shadow)

    @overload
    @classmethod
    def for_geometry_type(cls, geometry_type: Literal[StyleGeometryType.Point]) -> "PointStyle": ...

    @overload
    @classmethod
    def for_geometry_type(cls, geometry_type: Literal[StyleGeometryType.Linear]) -> "LineStyle": ...

    @overload
    @classmethod
    def for_geometry_type(cls, geometry_type: Literal[StyleGeometryType.Polygonal]) -> "PolygonStyle": ...

    @overload
    @classmethod
    def for_geometry_type(cls, geometry_type: Literal[StyleGeometryType.MultiGeometry]) -> "CollectionStyle": ...

    @overload
    @classmethod
    def for_geometry_type(cls, geometry_type: Literal[StyleGeometryType.Text]) -> "TextStyle": ...

    @overload
    @classmethod
    def for_geometry_type(cls, geometry_type: StyleGeometryType) -> "Style": ...

    @classmethod
    def for_geometry_type(cls, geometry_type: StyleGeometryType) -> "Style":
        """
        Возвращает стиль по умолчанию для переданного типа геометрии.

        Args:
            geometry_type: Тип геометрического объекта, для которого необходимо получить стиль.
        """
        if geometry_type == StyleGeometryType.Point:
            shadow_style = ShadowStyle.get_for_point()
        elif geometry_type == StyleGeometryType.Linear:
            shadow_style = ShadowStyle.get_for_line()
        elif geometry_type == StyleGeometryType.Polygonal:
            shadow_style = ShadowStyle.get_for_polygon()
        elif geometry_type == StyleGeometryType.Text:
            shadow_style = ShadowStyle.get_for_text()
        elif geometry_type == StyleGeometryType.MultiGeometry:
            shadow_style = ShadowStyle.get_for_collection()
        else:
            raise ValueError("Unsupported geometry type.")
        return cast(Style, Style._wrap(shadow_style))

    def draw(self, geometry: Geometry, painter: QPainter) -> None:
        """
        Рисует геометрический объект с текущим стилем в произвольном контексте вывода.
        Это может быть востребовано при желании отрисовать геометрию со стилем на форме
        или диалоге.

        Args:
            geometry: Геометрия. Должна соответствовать стилю. Т.е. если объект полигон, а стиль для рисования точечных
                объектов, то ничего нарисовано не будет.
            painter: Контекст вывода.

        .. literalinclude:: /../../tests/doc_examples/da/test_example_style.py
            :caption: Пример отрисовки в растре и сохранение результата в файле.
            :pyobject: test_run_example_style_draw
            :lines: 7-
            :dedent: 4
        """
        self._shadow.draw(geometry._shadow, painter)

    def clone(self) -> "Style":
        """Создаёт копию объекта стиля."""
        return cast(Style, Style._wrap(self._shadow.clone()))

    def __eq__(self, other: object) -> bool:
        if isinstance(other, Style):
            return self._shadow.isEqual(other._shadow)
        return NotImplemented

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

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

    @classmethod
    def __wrap_typed(cls, shadow: ShadowStyle) -> "Style":
        result = cls.__new__(cls)
        result._shadow = shadow
        return result

    @classmethod
    @overload
    def _wrap(cls, shadow: ShadowPointCompatStyle) -> Optional["PointCompatStyle"]: ...

    @classmethod
    @overload
    def _wrap(cls, shadow: ShadowPointFontStyle) -> Optional["PointFontStyle"]: ...

    @classmethod
    @overload
    def _wrap(cls, shadow: ShadowPointPictureStyle) -> Optional["PointPictureStyle"]: ...

    @classmethod
    @overload
    def _wrap(cls, shadow: ShadowPointStyle) -> Optional["PointStyle"]: ...

    @classmethod
    @overload
    def _wrap(cls, shadow: ShadowLineStyle) -> Optional["LineStyle"]: ...

    @classmethod
    @overload
    def _wrap(cls, shadow: ShadowPolygonStyle) -> Optional["PolygonStyle"]: ...

    @classmethod
    @overload
    def _wrap(cls, shadow: ShadowFillStyle) -> Optional["FillStyle"]: ...

    @classmethod
    @overload
    def _wrap(cls, shadow: ShadowTextStyle) -> Optional["TextStyle"]: ...

    @classmethod
    @overload
    def _wrap(cls, shadow: ShadowCollectionStyle) -> Optional["CollectionStyle"]: ...

    @classmethod
    @overload
    def _wrap(cls, shadow: ShadowStyle) -> Union[
        "PointCompatStyle",
        "PointFontStyle",
        "PointPictureStyle",
        "PointStyle",
        "LineStyle",
        "PolygonStyle",
        "FillStyle",
        "TextStyle",
        "CollectionStyle",
        "Style",
        None,
    ]: ...

    @classmethod
    def _wrap(cls, shadow: ShadowStyle) -> Union[
        "PointCompatStyle",
        "PointFontStyle",
        "PointPictureStyle",
        "PointStyle",
        "LineStyle",
        "PolygonStyle",
        "FillStyle",
        "TextStyle",
        "CollectionStyle",
        "Style",
        None,
    ]:
        if shadow is None:
            return None

        obj_type: Type[Style]
        if isinstance(shadow, ShadowPointCompatStyle):
            obj_type = PointCompatStyle
        elif isinstance(shadow, ShadowPointFontStyle):
            obj_type = PointFontStyle
        elif isinstance(shadow, ShadowPointPictureStyle):
            obj_type = PointPictureStyle
        elif isinstance(shadow, ShadowPointStyle):
            obj_type = PointStyle
        elif isinstance(shadow, ShadowLineStyle):
            obj_type = LineStyle
        elif isinstance(shadow, ShadowPolygonStyle):
            obj_type = PolygonStyle
        elif isinstance(shadow, ShadowFillStyle):
            obj_type = FillStyle
        elif isinstance(shadow, ShadowTextStyle):
            obj_type = TextStyle
        elif isinstance(shadow, ShadowCollectionStyle):
            obj_type = CollectionStyle
        elif isinstance(shadow, ShadowStyle):
            obj_type = Style
        else:
            return None
        return obj_type.__wrap_typed(shadow)


class PointStyle(Style):
    """
    Стиль оформления точечных объектов.

    По умолчанию создается стиль на базе шрифта True Type, а параметры аналогичны значениям по умолчанию
    в методе :meth:`create_mi_font`.

    Поддерживается 3 вида оформления:

    * Совместимое с MapInfo версии 3. Для создания такого стиля необходимо использовать :meth:`create_mi_compat`.
    * На базе шрифтов True Type. Задано по умолчанию. Стиль создается посредством :meth:`create_mi_font`.
    * На базе растрового файла. Стиль можно создать с помощью :meth:`create_mi_picture`.

    .. literalinclude:: /../../tests/doc_examples/da/test_example_style.py
        :caption: Пример.
        :pyobject: test_run_example_style_point
        :lines: 2-
        :dedent: 4
    """

    _shadow: ShadowPointStyle

    class _ShadowProtocol(Protocol):
        def get_color(self) -> QColor: ...

        def set_color(self, color: QColor) -> None: ...

    def __init__(self) -> None:
        self._shadow = ShadowPointStyle()

    # noinspection PyPep8Naming
    @staticmethod
    def create_mi_compat(
        symbol: int = 35, color: Union[QColor, Qt.GlobalColor] = Qt.red, pointSize: int = 8
    ) -> "PointCompatStyle":
        """
        Создание стиля в виде совместимого с MapInfo 3 :class:`PointCompatStyle`.

        Args:
            symbol: Номер символа, который будет отображен.
                Для создания невидимого символа используйте значение 31.
                Стандартный набор условных знаков включает символы от 31 до 67.
            color: Цвет символа.
            pointSize: Целое число, размер символа в пунктах от 1 до 48.
        """
        return cast(PointCompatStyle, Style._wrap(ShadowPointStyle.create_mi_compat(symbol, color, pointSize)))

    @staticmethod
    def create_mi_font(
        symbol: int = 36,
        color: Union[QColor, Qt.GlobalColor] = Qt.red,
        size: int = 8,
        fontname: str = "Axioma MI MapSymbols",
        fontstyle: int = 0,
        rotation: float = 0.0,
    ) -> "PointFontStyle":
        """
        Создание стиля на базе шрифта True Type :class:`PointFontStyle`.

        Args:
            symbol: Целое, имеющее значение 31 или больше, определяющее, какой используется символ из шрифтов TrueType.
                Для создания невидимого символа используйте значение 31.
            color: Цвет символа
            size: Целое число, размер символа в пунктах от 1 до 48;
            fontname: Строка с именем шрифта TrueType (например, значение по умолчание 'Axioma MI MapSymbols')
            fontstyle: Стиль дополнительного оформления, например, курсивный текст.
                Возможные параметры см. в таблице ниже. Для указания нескольких параметров их суммируют между собой.
            rotation: Угол поворота символа в градусах.
        """

        return cast(
            PointFontStyle,
            Style._wrap(ShadowPointStyle.create_mi_font(symbol, color, size, fontname, fontstyle, rotation)),
        )

    @staticmethod
    def create_mi_picture(
        filename: str,
        color: Union[QColor, Qt.GlobalColor] = Qt.black,
        size: int = 12,
        customstyle: int = 0,
    ) -> "PointPictureStyle":
        """
        Создание стиля со ссылкой на растровый файл :class:`PointPictureStyle`.

        Args:
            filename: Наименование растрового файла. Строка до 31 символа длиной.
                Данный файл должен находится в каталоге CustSymb с ресурсами. Например, `“Arrow.BMP”`.
            color: Цвет символа.
            size: Размер символа в пунктах от 1 до 48.
            customstyle: Задание дополнительных параметров стиля оформления.
        """
        return cast(
            PointPictureStyle, Style._wrap(ShadowPointStyle.create_mi_picture(filename, color, size, customstyle))
        )

    @property
    @abstractmethod
    def _point_style_shadow_protocol(self) -> "PointStyle._ShadowProtocol":
        raise NotImplementedError

    @property
    def color(self) -> QColor:
        """Устанавливает или возвращает цвет символа."""
        return self._point_style_shadow_protocol.get_color()

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


class PointCompatStyle(PointStyle):
    """
    Класс стиля, совместимого с MapInfo 3.

    В системе доступны следующие стили:

    .. image:: /./images/style/style_point_mi3.png

    .. literalinclude:: /../../tests/doc_examples/da/test_example_style.py
        :caption: Пример.
        :pyobject: test_run_example_style_point_compat
        :lines: 2-
        :dedent: 4
    """

    _shadow: ShadowPointCompatStyle

    # noinspection PyPep8Naming
    def __init__(self, symbol: int = 35, color: Union[QColor, Qt.GlobalColor] = Qt.red, pointSize: int = 8) -> None:
        """
        Конструктор класса.

        Args:
            symbol: Номер символа, который будет отображен.
                Для создания невидимого символа используйте значение 31.
                Стандартный набор условных знаков включает символы от 31 до 67.
            color: Цвет символа.
            pointSize: Целое число, размер символа в пунктах от 1 до 48.
        """
        # cast is enforced by code logic and tests
        self._shadow = cast(ShadowPointCompatStyle, ShadowPointStyle.create_mi_compat(symbol, color, pointSize))

    @property
    def _point_style_shadow_protocol(self) -> PointStyle._ShadowProtocol:
        return self._shadow

    @property
    def size(self) -> int:
        """Устанавливает или возвращает размер символа в пунктах."""
        return self._shadow.get_point_size()

    @size.setter
    def size(self, s: int) -> None:
        self._shadow.set_point_size(s)

    @property
    def symbol(self) -> int:
        """Устанавливает или возвращает номер символа."""
        return self._shadow.get_symbol()

    @symbol.setter
    def symbol(self, s: int) -> None:
        self._shadow.set_symbol(s)


class PointFontStyle(PointStyle):
    """
    Стиль на базе шрифта True Type.

    В системе доступны следующие стили:

    .. image:: /./images/style/style_point_mi_tt.png

    .. literalinclude:: /../../tests/doc_examples/da/test_example_style.py
        :caption: Пример.
        :pyobject: test_run_example_style_point_font
        :lines: 2-
        :dedent: 4
    """

    _shadow: ShadowPointFontStyle
    _BlackOutline: Final[int] = 16
    _WhiteOutline: Final[int] = 256

    class FontStyleTypes(int, Enum):
        """Тип шрифта."""

        Plain = 0
        """Обычный текст."""
        Bold = 1
        """Жирный текст."""
        Outline = 16
        """Черная кайма вокруг символа."""
        Shadow = 32
        """Тень."""
        Halo = 256
        """Белая кайма вокруг символа."""

    def __init__(
        self,
        symbol: int = 36,
        color: Union[QColor, Qt.GlobalColor] = Qt.red,
        size: int = 8,
        fontname: str = "Axioma MI MapSymbols",
        fontstyle: FontStyleTypes = FontStyleTypes.Plain,
        rotation: float = 0.0,
    ) -> None:
        """
        Конструктор класса.

        Args:
            symbol: Целое, имеющее значение 31 или больше, определяющее, какой используется символ из шрифтов TrueType.
                Для создания невидимого символа используйте значение 31.
            color: Цвет символа.
            size: Целое число, размер символа в пунктах от 1 до 48.
            fontname: Строка с именем шрифта TrueType (например, значение по умолчание 'Axioma MI MapSymbols')
            fontstyle: Стиль дополнительного оформления, например, курсивный текст.
                Возможные параметры см. в таблице ниже.
                Для указания нескольких параметров их суммируют между собой.
            rotation: Угол поворота символа в градусах.
        """
        # cast is enforced by code logic and tests
        self._shadow = cast(
            ShadowPointFontStyle, ShadowPointStyle.create_mi_font(symbol, color, size, fontname, fontstyle, rotation)
        )

    @property
    def _point_style_shadow_protocol(self) -> PointStyle._ShadowProtocol:
        return self._shadow

    @property
    def symbol(self) -> int:
        """Устанавливает или возвращает номер символа."""
        return self._shadow.get_symbol()

    @symbol.setter
    def symbol(self, s: int) -> None:
        self._shadow.set_symbol(s)

    @property
    def size(self) -> int:
        """Устанавливает или возвращает размер символа в пунктах."""
        return self._shadow.get_point_size()

    @size.setter
    def size(self, s: int) -> None:
        self._shadow.set_point_size(s)

    @property
    def font_name(self) -> str:
        """
        Устанавливает или возвращает наименование шрифта.

        Например, `Adobe Helvetica`.
        """
        return self._shadow.get_font_name()

    @font_name.setter
    def font_name(self, s: str) -> None:
        self._shadow.set_font_name(s)

    @property
    def rotation(self) -> float:
        """Устанавливает или возвращает угол поворота."""
        return self._shadow.rotation()

    @rotation.setter
    def rotation(self, s: float) -> None:
        self._shadow.set_rotation(s)

    @property
    def bold(self) -> bool:
        """Устанавливает или возвращает признак 'Жирности шрифта'."""
        return self._shadow.get_bold()

    @bold.setter
    def bold(self, v: bool) -> None:
        self._shadow.set_bold(v)

    @property
    def has_shadow(self) -> bool:
        """Устанавливает или возвращает признак 'Тень'."""
        return self._shadow.get_shadow()

    @has_shadow.setter
    def has_shadow(self, v: bool) -> None:
        self._shadow.set_shadow(v)

    @property
    def black_border(self) -> bool:
        """Устанавливает или возвращает признак 'Темная окантовка'."""
        return self._shadow.get_outline() == self._BlackOutline

    @black_border.setter
    def black_border(self, v: bool) -> None:
        self._shadow.set_outline(self._BlackOutline if v else 0)

    @property
    def white_border(self) -> bool:
        """Устанавливает или возвращает признак 'Светлая окантовка'."""
        return self._shadow.get_outline() == self._WhiteOutline

    @white_border.setter
    def white_border(self, v: bool) -> None:
        self._shadow.set_outline(self._WhiteOutline if v else 0)


class PointPictureStyle(PointStyle):
    """
    Стиль со ссылкой на растровый файл.

    .. csv-table:: Возможные значения параметра `customstyle`:
        :header: Значение, Наименование
        :widths: 10, 70

        0, "Флажки 'Фон' и 'Покрасить одним цветом' не установлены. Символ показывается стандартно.
        Все белые точки изображения становятся прозрачными и под ними видны объекты Карты."
        1, "Установлен флажок Фон; все белые точки изображения становятся непрозрачными."
        2, "Установлен флажок 'Покрасить одним цветом' все не белые точки изображения красятся в цвет символа."
        3, "Установлены флажки 'Фон' и 'Покрасить одним цветом'."


    .. literalinclude:: /../../tests/doc_examples/da/test_example_style.py
        :caption: Пример.
        :pyobject: test_run_example_style_point_picture
        :lines: 2-
        :dedent: 4
    """

    _shadow: ShadowPointPictureStyle

    def __init__(
        self,
        filename: str,
        color: Union[QColor, Qt.GlobalColor] = Qt.black,
        size: int = 12,
        customstyle: int = 0,
    ) -> None:
        """
        Конструктор класса.

        Args:
            filename: Наименование растрового файла. Строка до 31 символа длиной.
                Данный файл должен находится в каталоге CustSymb с ресурсами. Например, `“Arrow.BMP”`.
            color: Цвет символа.
            size: Размер символа в пунктах от 1 до 48.
            customstyle: Задание дополнительных параметров стиля оформления.
        """
        # cast is enforced by code logic and tests
        self._shadow = cast(
            ShadowPointPictureStyle, ShadowPointStyle.create_mi_picture(filename, color, size, customstyle)
        )

    @property
    def _point_style_shadow_protocol(self) -> PointStyle._ShadowProtocol:
        return self._shadow

    @property
    def size(self) -> int:
        """Устанавливает или возвращает размер символа в пунктах."""
        return self._shadow.get_point_size()

    @size.setter
    def size(self, s: int) -> None:
        self._shadow.set_point_size(s)

    @property
    def filename(self) -> str:
        """Устанавливает или возвращает наименование файла растра."""
        return self._shadow.get_filename()

    @filename.setter
    def filename(self, s: str) -> None:
        self._shadow.set_filename(s)

    @property
    def show_background(self) -> bool:
        """Устанавливает или возвращает признак 'Непрозрачный фон'."""
        return self._shadow.get_show_background()

    @show_background.setter
    def show_background(self, b: bool) -> None:
        self._shadow.set_show_background(b)

    @property
    def apply_color(self) -> bool:
        """Устанавливает или возвращает признак 'Применить цвет'."""
        return self._shadow.get_apply_color()

    @apply_color.setter
    def apply_color(self, b: bool) -> None:
        self._shadow.set_apply_color(b)

    @property
    def actual_size(self) -> bool:
        """Устанавливает или возвращает признак 'Реальный размер'."""
        return self._shadow.get_actual_size()

    @actual_size.setter
    def actual_size(self, b: bool) -> None:
        self._shadow.set_actual_size(b)


class LineStyle(Style):
    """
    Стиль линейного объекта, совместимый с MapInfo.

    В системе доступны следующие стили линии:

    .. image:: /./images/style/style_line.png

    .. literalinclude:: /../../tests/doc_examples/da/test_example_style.py
        :caption: Пример.
        :pyobject: test_run_example_style_line
        :lines: 2-
        :dedent: 4
    """

    _shadow: ShadowLineStyle

    def __init__(self, pattern: int = 2, color: Union[QColor, Qt.GlobalColor] = Qt.black, width: int = 1) -> None:
        """
        Конструктор класса.

        Args:
            pattern: Тип линии. Типы линий обозначаются кодами от 1 до 118. Тип 1 представляет собой невидимую линию.
            color: Цвет линии
            width: Толщина линии. Задается числом от 0 до 7, при этом линия нулевой ширины невидима на экране.
                   11-2047 - это значения, которые могут быть преобразованы в пункты:
                   ширина линии = (число пунктов * 10) + 10
                   Значение 0 допустимо только для типа линии 1 или невидимых линий.
        """
        self._shadow = ShadowLineStyle(pattern, color, width)

    @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 pattern(self) -> int:
        """Устанавливает или возвращает номер стиля линии."""
        return self._shadow.get_pattern()

    @pattern.setter
    def pattern(self, value: int) -> None:
        self._shadow.set_pattern(value)

    @property
    def width(self) -> int:
        """Устанавливает или возвращает толщину линии."""
        return self._shadow.get_width()

    @width.setter
    def width(self, w: int) -> None:
        self._shadow.set_width(w)

    # noinspection PyShadowingBuiltins
    def to_file(self, file_name: str, format: FileFormatType) -> None:
        self._shadow.to_file(file_name, ShadowFileFormatType(format))

    # noinspection PyShadowingBuiltins
    def to_string(self, format: FileFormatType) -> str:
        return self._shadow.to_string(ShadowFileFormatType(format))

    @staticmethod
    def _convert_pattern_file(
        in_file_name: str,
        in_format: FileFormatType,
        out_file_name: str,
        out_format: FileFormatType,
    ) -> None:
        with open(in_file_name) as f:
            f.close()
        ShadowLineStyle.convert_pattern_file(
            in_file_name,
            ShadowFileFormatType(in_format),
            out_file_name,
            ShadowFileFormatType(out_format),
        )


class FillStyle(Style):
    """
    Стиль заливки полигонов :class:`PolygonStyle`.

    В системе доступны следующие стили заливки:

    .. image:: /./images/style/style_fill.png
    """

    _shadow: ShadowFillStyle

    def __init__(self, pattern: int = 1, color: Union[QColor, Qt.GlobalColor] = Qt.transparent) -> None:
        """
        Конструктор класса.

        Args:
            pattern: Номер стиля заливки.
            color: Цвет основной заливки.
        """
        self._shadow = ShadowFillStyle(pattern, color)

    @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 bg_color(self) -> QColor:
        """Устанавливает или возвращает цвет фона."""
        return self._shadow.get_bg_color()

    @bg_color.setter
    def bg_color(self, c: QColor) -> None:
        self._shadow.set_bg_color(c)

    @property
    def pattern(self) -> int:
        """Устанавливает или возвращает номер стиля заливки."""
        return self._shadow.get_pattern()

    @pattern.setter
    def pattern(self, value: int) -> None:
        self._shadow.set_pattern(value)


class PolygonStyle(Style):
    """
    Стиль площадного объекта. По умолчанию создается прозрачный стиль :class:`FillStyle`
    с черной окантовкой :class:`LineStyle`.

    .. literalinclude:: /../../tests/doc_examples/da/test_example_style.py
        :caption: Пример.
        :pyobject: test_run_example_style_poly
        :lines: 2-
        :dedent: 4
    """

    _shadow: ShadowPolygonStyle

    def __init__(self, pattern: int = 1, color: Union[QColor, Qt.GlobalColor] = Qt.white, pattern_pen: int = 1) -> None:
        """
        Конструктор класса.

        Args:
            pattern: Номер стиля заливки.
            color: Цвет основной заливки.
            pattern_pen: Цвет обводки. По умолчанию обводка отсутствует.
        """
        self._shadow = ShadowPolygonStyle(pattern, color, pattern_pen)

    def set_pen(self, pattern: int = 2, color: Union[QColor, Qt.GlobalColor] = Qt.black, width: int = 1) -> None:
        """
        Задание стиля обводки. Параметры аналогичны при задании стиля линии
        :meth:`LineStyle`

        Args:
            pattern: Номер стиля линии.
            color: Цвет линии
            width: Толщина линии.
        """
        self._shadow.set_pen(pattern, color, width)

    # noinspection PyPep8Naming
    def set_brush(
        self,
        pattern: int = 1,
        color: Union[QColor, Qt.GlobalColor] = Qt.white,
        bgColor: Union[QColor, Qt.GlobalColor] = Qt.transparent,
    ) -> None:
        """
        Задание стиля заливки площадного объекта.

        Args:
            pattern: Номер стиля заливки. Шаблон задается числом от 1 до 71,
                при этом в шаблоне с номером 1 оба цвета отсутствуют,
                а в шаблоне 2 отсутствует цвет фона. Шаблоны с кодами 9-11 зарезервированы для внутренних целей.
            color: Цвет основной заливки.
            bgColor: Цвет заднего фона, если заливка неполная.

        Доступные стили заливки см. :class:`FillStyle`:
        """
        self._shadow.set_brush(pattern, color, bgColor)

    @property
    def border(self) -> Optional[LineStyle]:
        """
        Стиль окантовки.

        Может отсутствовать. Для переопределения можно также использовать :meth:`set_pen`.
        """
        return Style._wrap(cast(ShadowLineStyle, self._shadow.get_border()))

    @property
    def fill(self) -> FillStyle:
        """Стиль заливки."""
        return cast(FillStyle, Style._wrap(self._shadow.get_fill()))


class TextBackgroundType(int, Enum):
    """Тип отрисовки фона текстового объекта."""

    NoBackground = 0
    """Фон отсутствует."""
    Outline = 1
    """Обводка по контору букв текста."""
    Frame = 2
    """Рамка вокруг текста."""


class TextCallout(int, Enum):
    """Тип выноски."""

    NoCallout = 0
    """Не отображать."""
    Line = 1
    """Линия."""
    Arrow = 2
    """Стрелка."""


class TextAlignment(int, Enum):
    """Выравнивание текста."""

    Left = 0
    """По левому краю."""
    Center = 1
    """По центру."""
    Right = 2
    """По правому краю."""


class TextStyle(Style):
    """
    Стиль текстового объекта.

     .. csv-table:: Возможные значения параметра style
        :header: Значение, Наименование
        :widths: 10, 70

            0, Обычный
            1, Жирный
            2, Курсив
            4, Подчеркнутый
            16, Контур (только для `Macintosh`)
            32, Тень
            256, Кайма
            512, Капитель
            1024, Разрядка


    .. literalinclude:: /../../tests/doc_examples/da/test_example_style.py
        :caption: Пример.
        :pyobject: test_run_example_style_text
        :lines: 2-
        :dedent: 4

    Note:
        При назначении стиля для текста необходимо помнить, что его параметры
        и параметры геометрии :class:`axipy.Text` взаимозависимы.
    """

    _shadow: ShadowTextStyle

    def __init__(
        self,
        fontname: str,
        size: int,
        style: int = 0,
        forecolor: Union[QColor, Qt.GlobalColor] = Qt.black,
        backcolor: Union[QColor, Qt.GlobalColor] = Qt.transparent,
    ) -> None:
        """
        Конструктор класса.

        Args:
            fontname: Наименование шрифта.
            size: Размер шрифта в пунктах. Может принимать значение 0 для подписей в окне карты,
                так как они являются атрибутами карты, и их размер определяется динамически.
            style: Дополнительные параметры стиля. Подробнее см. в таблице ниже. Стоит заметить,
                что если оставить значение, равным 0,
                то необходимые свойства можно установить позже через соответствующие свойства.
            forecolor: Цвет шрифта.
            backcolor: Цвет заднего фона, если он задан.
        """
        self._shadow = ShadowTextStyle(fontname, size, style, forecolor, backcolor)

    @property
    def size(self) -> float:
        """
        Устанавливает или возвращает базовый размер шрифта в пунктах.

        Точный размер шрифта высчитывается исходя из контекста рисования (карта или
        отчет).
        """
        return self._shadow.font_size()

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

    @property
    def color(self) -> QColor:
        """Устанавливает или возвращает цвет текста."""
        return self._shadow.color()

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

    @property
    def bg_color(self) -> QColor:
        """Устанавливает или возвращает цвет фона текста."""
        return self._shadow.bg_color()

    @bg_color.setter
    def bg_color(self, c: QColor) -> None:
        self._shadow.set_bg_color(c)

    @property
    def fontname(self) -> str:
        """Устанавливает или возвращает наименование шрифта."""
        return self._shadow.font_family()

    @fontname.setter
    def fontname(self, v: str) -> None:
        self._shadow.set_font_family(v)

    @property
    def bold(self) -> bool:
        """Устанавливает или возвращает признак 'Жирный текст'."""
        return self._shadow.bold()

    @bold.setter
    def bold(self, v: bool) -> None:
        self._shadow.set_bold(v)

    @property
    def italic(self) -> bool:
        """Устанавливает или возвращает признак 'Курсив текста'."""
        return self._shadow.italic()

    @italic.setter
    def italic(self, v: bool) -> None:
        self._shadow.set_italic(v)

    @property
    def shadow(self) -> bool:
        """Устанавливает или возвращает признак 'Тень'."""
        return self._shadow.shadow()

    @shadow.setter
    def shadow(self, v: bool) -> None:
        self._shadow.set_shadow(v)

    @property
    def spacing(self) -> bool:
        """Устанавливает или возвращает признак 'Разрядка'."""
        return self._shadow.spacing()

    @spacing.setter
    def spacing(self, v: bool) -> None:
        self._shadow.set_spacing(v)

    @property
    def underline(self) -> bool:
        """Устанавливает или возвращает признак 'Подчеркивание'."""
        return self._shadow.underline()

    @underline.setter
    def underline(self, v: bool) -> None:
        self._shadow.set_underline(v)

    @property
    def capital(self) -> bool:
        """Устанавливает или возвращает признак 'Все заглавные'."""
        return self._shadow.capital()

    @capital.setter
    def capital(self, v: bool) -> None:
        self._shadow.set_capital(v)

    @property
    def bg_type(self) -> TextBackgroundType:
        """Устанавливает или возвращает тип отрисовки фона текста."""
        return TextBackgroundType(self._shadow.bg_type())

    @bg_type.setter
    def bg_type(self, v: TextBackgroundType) -> None:
        self._shadow.set_bg_type(v)

    @property
    def callout(self) -> TextCallout:
        """Устанавливает или возвращает тип выноски."""
        return TextCallout(self._shadow.get_callout())

    @callout.setter
    def callout(self, v: TextCallout) -> None:
        self._shadow.set_callout(v)

    @property
    def callout_style(self) -> LineStyle:
        """Устанавливает или возвращает стиль выноски."""
        return cast(LineStyle, Style._wrap(ShadowTextStyle.get_lineStyle(self._shadow)))

    @callout_style.setter
    def callout_style(self, v: LineStyle) -> None:
        self._shadow.set_lineStyle(v._shadow)

    @property
    def alignment(self) -> TextAlignment:
        """Устанавливает или возвращает выравнивание текста."""
        a = self._shadow.alignment()
        if a == Qt.AlignCenter:
            return TextAlignment.Center
        elif a == Qt.AlignRight:
            return TextAlignment.Right
        return TextAlignment.Left

    @alignment.setter
    def alignment(self, v: TextAlignment) -> None:
        if v == TextAlignment.Center:
            self._shadow.set_alignment(Qt.AlignCenter)
        elif v == TextAlignment.Right:
            self._shadow.set_alignment(Qt.AlignRight)
        elif v == TextAlignment.Left:
            self._shadow.set_alignment(Qt.AlignLeft)


class CollectionStyle(Style):
    """
    Смешанный стиль для разнородного типа объектов.

    Контейнер стилей. может применяться вкупе с геометрическим объектом
    типа разнородная коллекция :class:`axipy.GeometryCollection`.
    Для задания или переопределения стилей простейших объектов,
    необходимо вызывать соответствующие методы для необходимых типов объектов.

    Note:
        Объекты стилей, полученные через методы :meth:`line`, :meth:`polygon` и т.д.
        будут удалены сразу после удаления объекта стиля коллекции. Если их нужно
        сохранить, воспользуйтесь операцией :meth:`axipy.Style.clone`.
    """

    _shadow: ShadowCollectionStyle

    def __init__(self) -> None:
        self._shadow = ShadowCollectionStyle()

    def for_point(self, style: PointStyle) -> None:
        """Задание стиля для точечных объектов :class:`PointStyle`."""
        self._shadow.for_point(style._shadow)

    def for_line(self, style: LineStyle) -> None:
        """Задание стиля для линейных объектов :class:`LineStyle`."""
        self._shadow.for_line(style._shadow)

    def for_polygon(self, style: PolygonStyle) -> None:
        """Задание стиля для полигональных объектов :class:`PolygonStyle`."""
        self._shadow.for_polygon(style._shadow)

    def for_text(self, style: TextStyle) -> None:
        """Задание стиля для текстовых объектов :class:`TextStyle`."""
        self._shadow.for_text(style._shadow)

    @property
    def text(self) -> Optional[TextStyle]:
        """Стиль для текстовых объектов :class:`TextStyle`."""
        return Style._wrap(cast(ShadowTextStyle, self._shadow.text()))

    @property
    def line(self) -> Optional[LineStyle]:
        """Стиль для линейных объектов :class:`LineStyle`."""
        return Style._wrap(cast(ShadowLineStyle, self._shadow.line()))

    @property
    def polygon(self) -> Optional[PolygonStyle]:
        """Стиль для полигональных объектов :class:`PolygonStyle`."""
        return Style._wrap(cast(ShadowPolygonStyle, self._shadow.polygon()))

    @property
    def point(self) -> Optional[PointStyle]:
        """Стиль для точечных объектов :class:`PointStyle`."""
        return Style._wrap(cast(ShadowPointStyle, self._shadow.point()))

    def find_style(self, geom: Geometry) -> Style:
        """Возвращает стиль, подходящий для переданной геометрии."""
        return cast(Style, Style._wrap(self._shadow.style(geom._shadow)))


def _apply_deprecated() -> None:
    # Using getattr to hide deprecated objects from axipy namespace on IDE inspections
    getattr(__all__, "extend")(("TextStyleEffects",))

    class TextStyleEffects(NamedTuple):
        bold: bool = False
        italic: bool = False
        shadow: bool = False
        spacing: bool = False
        underline: bool = False
        capital: bool = False

    @property  # type: ignore[misc]
    @_deprecated_by("similar properties of TextStyle class")
    def effects(self: TextStyle) -> TextStyleEffects:
        eff = self._shadow.effects()
        return TextStyleEffects(
            eff.bold, eff.italic, eff.shadow, eff.spacing, eff.underline, eff.capital  # type: ignore[attr-defined]
        )

    setattr(TextStyle, "effects", effects)

    globals().update(
        TextStyleEffects=TextStyleEffects,
    )


_apply_deprecated()
