from typing import (
    Any,
    ItemsView,
    Iterator,
    KeysView,
    List,
    Mapping,
    Optional,
    SupportsIndex,
    Union,
    ValuesView,
    cast,
)

from axipy._internal._check_shadow import _shiboken2_is_valid
from axipy._internal._exceptions import _generate_type_error
from axipy._internal._metaclass2 import _decorate, _MappingMetaExtended
from axipy._internal._shadow_instance_factory import _shadow_manager
from axipy._internal._utils import _AxiRepr, _AxiReprMeta, _Singleton
from axipy.cpp_core_core import DefaultKeys as ShadowDefaultKeys
from axipy.cpp_core_core import StateObserver, ValueObserver
from PySide2.QtCore import Signal

from .state_manager_ import DefaultKeys

__all__: List[str] = [
    "Observer",
    "ObserverManager",
]

_RESERVED_NAMES = (
    "Selection",
    "Editable",
    "SelectionEditable",
    "SelectionEditableIsSame",
    "ActiveView",
    "ActiveMapView",
    "ActiveTableView",
    "HasTables",
)


class Observer(metaclass=_AxiReprMeta):
    """
    Наблюдатель.

    При создании, наблюдатель автоматически добавляется в менеджер наблюдателей :attr:`axipy.observer_manager`.
    Чтобы удалить наблюдатель, используйте метод :attr:`axipy.ObserverManager.remove`.
    """

    def __init__(self, name: str, init_value: Any) -> None:
        """
        Конструктор класса.

        Args:
            name: Имя наблюдателя.
            init_value: Начальное значение наблюдателя.
        """
        if name in _RESERVED_NAMES:
            raise TypeError(f"Can't create an observer with name '{name}'. Name is reserved.")
        self._name: str = name
        self.__inner_shadow: "ValueObserver" = _shadow_manager.state_observer.create(name, init_value)

    @classmethod
    def _wrap(cls, shadow: "ValueObserver", name: Optional[str] = None) -> "Observer":
        v = cls.__new__(cls)
        v.__inner_shadow = shadow
        v._name = name if name is not None else shadow.objectName()
        return v

    @property
    def _shadow(self) -> "ValueObserver":
        if not _shiboken2_is_valid(self.__inner_shadow):
            raise RuntimeError(f"Error in axipy.Observer, name={self.name}. Internal C++ object already deleted.")
        return self.__inner_shadow

    @property
    def value(self) -> Any:
        """
        Устанавливает или возвращает значение наблюдателя.

        При изменении значения испускается сигнал :attr:`changed`.
        """
        return self._shadow.value()

    @value.setter
    def value(self, value: Any) -> None:
        self._shadow.setValue(value)

    @property
    def name(self) -> str:
        """Возвращает имя наблюдателя."""
        return self._name

    @property
    def changed(self) -> Signal:
        """
        Сигнал об изменении значения.

        :rtype: Signal[Any]

        ::

            import axipy

            def print_func(value):
                print(value)

            axipy.observer_manager.Selection.changed.connect(print_func)
        """
        return self._shadow.changed

    def __repr__(self) -> str:
        return f"<axipy.{self.__class__.__name__} name={self.name} value={self.value}>"

    def __eq__(self, other: object) -> bool:
        if isinstance(other, Observer):
            return self.name == other.name
        return NotImplemented


def _iter() -> Iterator[str]:
    return iter(_shadow_manager.state_observer.keys())


def _len() -> int:
    return len(_shadow_manager.state_observer.keys())


def _get_item(key: str) -> "Observer":
    shadow = _shadow_manager.state_observer.find(key)
    if shadow is None:
        raise KeyError(key)
    else:
        return Observer._wrap(shadow, key)


class _CustomMappingMetaExtended(_MappingMetaExtended):

    def __iter__(cls) -> Iterator[str]:
        return _iter()

    def __len__(cls) -> int:
        return _len()

    def __getitem__(cls, key: str) -> "Observer":
        return _get_item(key)

    def _remove(cls, name: str) -> None:
        _shadow_manager.state_observer.remove(name)


class ObserverManager(Mapping, _Singleton, _AxiRepr, metaclass=_CustomMappingMetaExtended):
    """
    Наблюдатели за состоянием. Класс является словарем, доступным только для чтения
    (:class:`collections.abc.Mapping`), где ключи это имена наблюдателей, a значения это
    объекты класса :class:`axipy.Observer`. Поддерживает обращение по ключу.

    Note:
        Создание :class:`axipy.ObserverManager` не требуется,
        используйте объект :attr:`axipy.observer_manager`.
    """

    class _ClassProperty:

        def __init__(self, observer_id: str) -> None:
            self._observer_id: str = observer_id

        def __get__(self, _obj: Any, _obj_name: Any) -> Observer:
            value_observer = _shadow_manager.state_observer.find(self._observer_id)
            return Observer._wrap(value_observer, self._observer_id)

    Selection: Observer = _ClassProperty("Selection")  # type: ignore[assignment]
    """Есть выборка."""

    Editable: Observer = _ClassProperty("Editable")  # type: ignore[assignment]
    """Активная карта имеет редактируемый слой."""

    SelectionEditable: Observer = _ClassProperty("SelectionEditable")  # type: ignore[assignment]
    """Карта имеет редактируемый слой и есть выделенные объекты на одном из слоев
    карты."""

    SelectionEditableIsSame: Observer = _ClassProperty("SelectionEditableIsSame")  # type: ignore[assignment]
    """Карта имеет редактируемый слой и выборку на этом слое."""

    ActiveView: Observer = _ClassProperty("ActiveView")  # type: ignore[assignment]
    """Есть активное окно."""

    ActiveMapView: Observer = _ClassProperty("ActiveMapView")  # type: ignore[assignment]
    """Есть активное окно карты."""

    ActiveTableView: Observer = _ClassProperty("ActiveTableView")  # type: ignore[assignment]
    """Есть активное окно таблицы."""

    HasTables: Observer = _ClassProperty("HasTables")  # type: ignore[assignment]
    """Открыта хотя бы одна таблица."""

    def __len__(self) -> int:
        return _len()

    def __iter__(self) -> Iterator[str]:
        return _iter()

    def __getitem__(self, key: str) -> Observer:
        return _get_item(key)

    def keys(self) -> KeysView[str]:
        """Возвращает набор ключей, где ключи это имена наблюдателей."""
        return Mapping.keys(self)

    def values(self) -> ValuesView[Observer]:
        """Возвращает коллекцию значений, где значения это объекты класса
        :class:`axipy.Observer`."""
        return Mapping.values(self)

    def items(self) -> ItemsView[str, Observer]:
        """Возвращает набор кортежей ключ-значение, где ключи это имена наблюдателей, а
        значения это объекты класса :class:`axipy.Observer`."""
        return Mapping.items(self)

    def get(self, key: str, default_value: Optional[Any] = None) -> Optional[Observer]:
        """Возвращает значение по ключу."""
        return Mapping.get(self, key, default_value)

    def remove(self, name: str) -> None:
        """
        Удаляет наблюдатель по имени.

        :param name: Имя наблюдателя.
        """
        if name in _RESERVED_NAMES:
            raise TypeError(f"Can't remove built-in observer with name '{name}'.")

        self._shadow.remove(name)

    @property
    def _shadow(self) -> StateObserver:
        return _shadow_manager.state_observer

    @staticmethod
    def _to_name(key: Union[str, DefaultKeys, Observer, None]) -> Optional[str]:
        if key is None:  # Когда наблюдатель не нужен
            return None
        elif isinstance(key, Observer):
            return key.name
        elif isinstance(key, str):
            return key
        elif isinstance(key, (DefaultKeys, ShadowDefaultKeys.Key)):
            # Shiboken enum attributes have __index__ method.
            return ShadowDefaultKeys.names()[cast(SupportsIndex, key)]
        else:
            raise TypeError(f"Wrong observer_name type '{type(key), key}'")

    def __new__(cls, *args: Any, **kwargs: Any) -> "ObserverManager":
        # back compat
        setattr(cls, "keys", _decorate(cls, cls.keys))
        setattr(cls, "values", _decorate(cls, cls.values))
        setattr(cls, "items", _decorate(cls, cls.items))
        setattr(cls, "get", _decorate(cls, cls.get))
        setattr(cls, "remove", _decorate(cls, cls.remove))

        # noinspection PyMethodMayBeStatic
        def _create(name: str, init_value: Any) -> Observer:
            if not isinstance(name, str):
                raise _generate_type_error(type(name), str)
            return Observer._wrap(_shadow_manager.state_observer.create(name, init_value), name)

        setattr(cls, "create", _create)
        # noinspection PyTypeChecker
        inst = _Singleton.__new__(cls, *args, **kwargs)
        return cast("ObserverManager", inst)
