from abc import ABCMeta
from typing import Any

from axipy.cpp_common import ShadowMemory
from PySide2.QtCore import QObject


class _Memory:
    """
    Запрос системной памяти в MB.

    Под linux: used + free < available
    """

    @staticmethod
    def __to_mb(v: float) -> float:
        return v / 1024

    @staticmethod
    def used() -> float:
        return _Memory.__to_mb(ShadowMemory().used())

    @staticmethod
    def free() -> float:
        return _Memory.__to_mb(ShadowMemory().free())

    @staticmethod
    def available() -> float:
        return _Memory.__to_mb(ShadowMemory().available())

    @staticmethod
    def percent_used() -> float:
        return _Memory.used() * 100.0 / _Memory.available()

    @staticmethod
    def percent_free() -> float:
        return _Memory.free() * 100.0 / _Memory.available()


# class _Desc:
#     """Шаблон дескриптора."""
#
#     def __init__(self) -> None:
#         self._inst = None
#         self._name: Optional[str] = None
#
#     def __set_name__(self, owner, name: str) -> None:
#         self._name = name
#
#     def __get__(self, obj, objtype=None):
#         if self._inst is not None:
#             return self._inst


_NoInitTypeError = TypeError("Class doesn't support init")


class _ReadOnlyMeta(type):

    def __setattr__(cls, key: str, value: Any) -> None:
        raise TypeError("Class is read-only")


class _NoInit:

    def __init__(self) -> None:
        raise _NoInitTypeError


class _NoInitReadOnlyMeta(_NoInit, metaclass=_ReadOnlyMeta): ...


class _AxiRepr:

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


class _AxiReprMeta(type):
    def __repr__(cls) -> str:
        return f"<class 'axipy.{cls.__name__}'>"


class _AxiReprABCMeta(_AxiReprMeta, ABCMeta):
    pass


class _AxiReprMetaQObject(_AxiReprMeta, type(QObject)):  # type: ignore[misc]
    pass


class _Singleton:
    __instance: "_Singleton"

    def __new__(cls, *args: Any, **kwargs: Any) -> "_Singleton":
        if not hasattr(cls, "_Singleton__instance"):
            cls.__instance = super().__new__(cls, *args, **kwargs)
        return cls.__instance


class _SignalBlocker:
    """
    A context manager for blocking and unblocking signals on a QObject.

    This class allows you to temporarily block signals for a given QObject
    while executing a block of code. When the block is exited, the original
    state of signal blocking is restored, even if an exception occurs within
    the block.

    Usage:
        with _SignalBlocker(some_q_object):
            # Code that may emit signals
            func_that_can_raise()
    """

    def __init__(self, obj: QObject) -> None:
        """
        Initializes the SignalBlocker with the specified QObject.

        Args:
            obj (QObject): The QObject whose signals will be blocked.
        """
        self.__obj: QObject = obj
        self.__was_blocked: bool = False

    def __enter__(self) -> None:
        """Blocks signals on the QObject when entering the context."""
        self.__was_blocked = self.__obj.signalsBlocked()
        self.__obj.blockSignals(True)

    def __exit__(self, exc_type: type, exc_val: Exception, exc_tb: object) -> None:
        """Restores the previous state of signal blocking when exiting the context."""
        self.__obj.blockSignals(self.__was_blocked)
