from abc import ABC, abstractmethod
from typing import Callable, Iterable, List, Union

import axipy
from axipy import Observer
from axipy._internal._decorator import _experimental_class
from axipy._internal._utils import _AxiReprABCMeta
from PySide2.QtCore import QObject, Signal, Slot


@_experimental_class()
class _ObserverABC(Observer, ABC, metaclass=_AxiReprABCMeta):
    """
    Вспомогательный класс для создания наблюдателей.

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

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

        self.__connections: List[_ObserverABC.__Connection] = []
        self.__is_disconnected: bool = False
        self.__receiver = _ObserverABC.__ObserverABCSlotReceiver(self)

        self.__subscribe()

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

        Должно быть уникальным. Свойство должно быть переопределено.
        """
        pass

    @property
    @abstractmethod
    def signals(self) -> Union[Signal, Iterable[Signal]]:
        """
        Возвращает сигналы, на которые будет подписано обновление состояния наблюдателя
        (:meth:`test_value`).

        Свойство должно быть переопределено.
        """
        pass

    @abstractmethod
    def test_value(self) -> bool:
        """
        Проверяет состояние наблюдателя.

        Метод должен быть переопределен.
        """
        pass

    def __get_signals_ensure_iterable(self) -> Iterable[Signal]:
        if not isinstance(self.signals, Iterable):
            return (self.signals,)
        else:
            return self.signals

    def __subscribe(self) -> None:
        for signal in self.__get_signals_ensure_iterable():
            connection = _ObserverABC.__Connection(signal, self.__receiver._on_update)
            self.__connections.append(connection)

    def __unsubscribe(self) -> None:
        """
        Отписывает наблюдатель от всех сигналов.

        TODO: Выполняется автоматически, при удалении кнопки, к которой привязан наблюдатель.
        """
        if self.__is_disconnected:
            return None

        for connection in self.__connections:
            connection.disconnect()

        self.__is_disconnected = True
        return None

    def unload(self) -> None:
        """
        Отписывает наблюдатель от всех сигналов, и удаляет наблюдатель из менеджера
        наблюдателей :attr:`axipy.observer_manager`.
        """
        self.__unsubscribe()
        if self.name in axipy.observer_manager:
            axipy.observer_manager.remove(self.name)

        self.__receiver.deleteLater()
        del self.__receiver

    class __Connection:

        def __init__(self, signal: Signal, slot: Callable, auto_connect: bool = True) -> None:
            self._signal: Signal = signal
            self._slot: Callable = slot
            self._is_connected: bool = False

            if auto_connect:
                self.connect()

        def connect(self) -> None:
            result = self._signal.connect(self._slot)
            if result:
                self._is_connected = True
            else:
                raise RuntimeError("Failed to connect signal.")

        def disconnect(self) -> None:
            if self._is_connected:
                self._signal.disconnect(self._slot)

    class __ObserverABCSlotReceiver(QObject):

        def __init__(self, observer: "_ObserverABC") -> None:
            super().__init__()
            self.__observer = observer

        @Slot()
        def _on_update(self) -> None:
            self.__observer.value = self.__observer.test_value()
