from functools import cached_property
from typing import TYPE_CHECKING, Iterator, List, Optional, cast

from axipy.cpp_render import (
    ShadowLegend,
    ShadowLegendStyleItem,
    ShadowLegendStyleItemList,
)
from axipy.da import Style
from axipy.utl import Pnt
from PySide2.QtGui import QImage, QPainter

from .context import Context
from .layer import Layer

__all__: List[str] = [
    "LegendItem",
    "ListLegendItems",
    "Legend",
]


class LegendItem:
    """Элемент легенды."""

    if TYPE_CHECKING:
        _shadow: ShadowLegendStyleItem

    def __init__(self) -> None:
        raise NotImplementedError

    @classmethod
    def _wrap(cls, _shadow: ShadowLegendStyleItem) -> "LegendItem":
        result = cls.__new__(cls)
        result._shadow = _shadow
        return result

    @property
    def visible(self) -> bool:
        """Видимость элемента легенды."""
        return self._shadow.isVisible()

    @visible.setter
    def visible(self, v: bool) -> None:
        self._shadow.setVisible(v)

    @property
    def title(self) -> str:
        """Описание элемента легенды."""
        return self._shadow.description()

    @title.setter
    def title(self, v: str) -> None:
        self._shadow.setDescription(v)

    @property
    def style(self) -> Style:
        """Стиль оформления элемента легенды."""
        return cast(Style, Style._wrap(self._shadow.get_style()))


class ListLegendItems:
    """Элементы легенды."""

    _shadow: ShadowLegendStyleItemList

    def __init__(self) -> None:
        raise NotImplementedError

    @classmethod
    def _wrap(cls, shadow: ShadowLegendStyleItemList) -> "ListLegendItems":
        result = cls.__new__(cls)
        result._shadow = shadow
        return result

    # noinspection PyPep8Naming
    def move(self, fromIdx: int, toIdx: int) -> None:
        self._shadow.move(fromIdx, toIdx)

    def __getitem__(self, item: int) -> Optional[LegendItem]:
        return LegendItem._wrap(ShadowLegendStyleItemList.get_item(self._shadow, item))

    def __setitem__(self, idx: int, v: LegendItem) -> None:
        self._shadow.set_item(idx, v._shadow)

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

    def __iter__(self) -> Iterator[LegendItem]:
        return cast(Iterator[LegendItem], (self.__getitem__(idx) for idx in range(self.__len__())))


class Legend:
    """
    Легенда слоя. Позволяет получить информацию об условных обозначениях на слое.
    Созданная легенда в дальнейшем может быть помещена на лист отчета
    :class:`axipy.Report` как :class:`axipy.LegendReportItem` или же расположена в
    отдельном окне легенд слоев для карты :class:`axipy.Map`.

    Args:
        lay: Слой, для которого создается легенда.

    .. literalinclude:: /../../tests/doc_examples/render/test_example_legend.py
        :caption: Пример создания легенды.
        :pyobject: test_run_example_legend
        :lines: 3-
        :dedent: 4
    """

    _shadow: ShadowLegend

    def __init__(self, lay: Layer):
        self._shadow = ShadowLegend(lay._shadow)

    @classmethod
    def _wrap(cls, shadow: ShadowLegend) -> "Legend":
        result = cls.__new__(cls)
        result._shadow = shadow
        return result

    def draw(self, context: Context) -> None:
        """
        Рисует легенду в контексте.

        Легенду также можно отрисовать совместно с картой в одном контексте (см. :meth:`Map.draw`).

        Args:
            context: Контекст рисования.
        """
        self._shadow.draw(context._shadow)

    @property
    def position(self) -> Pnt:
        """Положение легенды в контексте рисования."""
        return Pnt.from_qt(self._shadow.get_position())

    @position.setter
    def position(self, pos: Pnt) -> None:
        self._shadow.set_position(Pnt._point_value_to_qt(pos))

    @property
    def caption(self) -> str:
        """
        Заголовок легенды.

        Стиль заголовка задается свойством :attr:`style_caption`
        """
        return self._shadow.get_caption()

    @caption.setter
    def caption(self, s: str) -> None:
        self._shadow.set_caption(s)

    @property
    def style_caption(self) -> Style:
        """Стиль заголовка легенды."""
        return cast(Style, Style._wrap(ShadowLegend.get_styleCaption(self._shadow)))

    @style_caption.setter
    def style_caption(self, style: Style) -> None:
        self._shadow.set_styleCaption(style._shadow)

    @property
    def subcaption(self) -> str:
        """
        Подзаголовок легенды.

        Стиль заголовка задается свойством :attr:`style_subcaption`
        """
        return self._shadow.get_subCaption()

    @subcaption.setter
    def subcaption(self, s: str) -> None:
        self._shadow.set_subCaption(s)

    @property
    def style_subcaption(self) -> Style:
        """Стиль подзаголовка легенды."""
        return cast(Style, Style._wrap(ShadowLegend.get_styleSubCaption(self._shadow)))

    @style_subcaption.setter
    def style_subcaption(self, style: Style) -> None:
        self._shadow.set_styleSubCaption(style._shadow)

    @property
    def style_text(self) -> Style:
        """Стиль текстовых подписей."""
        return cast(Style, Style._wrap(ShadowLegend.get_styleText(self._shadow)))

    @style_text.setter
    def style_text(self, style: Style) -> None:
        self._shadow.set_styleText(style._shadow)

    @property
    def border_style(self) -> Style:
        """
        Стиль используемой окантовки.

        Отображается если `has_border` установлено в True.
        """
        return cast(Style, Style._wrap(self._shadow.get_borderStyle()))

    @border_style.setter
    def border_style(self, s: Style) -> None:
        self._shadow.set_borderStyle(s._shadow)

    @property
    def fill_style(self) -> Style:
        """Стиль заливки заднего фона."""
        return cast(Style, Style._wrap(self._shadow.get_fillStyle()))

    @fill_style.setter
    def fill_style(self, s: Style) -> None:
        self._shadow.set_fillStyle(s._shadow)

    @property
    def columns(self) -> int:
        """
        Количество колонок в легенде.

        По умолчанию 1.
        """
        return self._shadow.numberColumns()

    @columns.setter
    def columns(self, v: int) -> None:
        self._shadow.setNumberColumns(v)

    def refresh(self) -> None:
        """Обновляет стили из источника."""
        self._shadow.refreshStyleItems()

    @cached_property
    def _items(self) -> ListLegendItems:
        return ListLegendItems._wrap(self._shadow.get_items())

    @property
    def items(self) -> ListLegendItems:
        """
        Перечень стилей легенды.

        Реализован в виде списка. Для изменения какого-либо параметра необходимо сначала
        получить элемент, затем поменять требуемое свойство, а затем измененный элемент
        переназначить.
        """
        return self._items

    def to_image(self, width: int, height: int) -> QImage:
        """
        Возвращает легенду в виде растра.

        Args:
            width: Ширина выходного растра.
            height: Высота выходного растра.
        """
        image = QImage(width, height, QImage.Format_ARGB32_Premultiplied)
        image.fill(0)
        painter = QPainter(image)
        context = Context(painter)
        self.draw(context)
        return image
