import numpy as np


class MeanSquaredError:
    """
        Класс для вычисления средней квадратной ошибки (Mean Squared Error, MSE).
        Этот класс реализует методы для вычисления потерь и градиента MSE.
        """

    def __call__(self, y_true, y_pred):
        """
        Вычисляет среднюю квадратичную ошибку (MSE) между истинными значениями и предсказаниями.

        Параметры:
        y_true (ndarray): Истинные значения.
        y_pred (ndarray): Предсказанные значения.

        Возвращает:
        float: Средняя квадратичная ошибка (MSE).
        """
        return np.mean((y_true - y_pred) ** 2)

    def gradient(self, y_true, y_pred):
        """
        Вычисляет градиент функции потерь MSE по предсказаниям.

        Параметры:
        y_true (ndarray): Истинные значения.
        y_pred (ndarray): Предсказанные значения.

        Возвращает:
        ndarray: Градиент функции потерь.
        """
        return 2 * (y_pred - y_true) / y_true.size


class CrossEntropyLoss:
    """
       Класс для вычисления кросс-энтропийных потерь (Cross Entropy Loss).
       Этот класс реализует методы для вычисления потерь и градиента кросс-энтропийных потерь.
       """

    def __call__(self, y_true, y_pred):
        """
        Вычисляет значение функции потерь кросс-энтропии между истинными значениями и предсказаниями.

        Параметры:
        y_true (ndarray): Истинные метки (one-hot encoding).
        y_pred (ndarray): Предсказанные вероятности.

        Возвращает:
        float: Значение кросс-энтропийной функции потерь.
        """
        y_pred = np.clip(y_pred, 1e-15, 1 - 1e-15)  # Для предотвращения числовых ошибок с логарифмом
        return -np.sum(y_true * np.log(y_pred))

    def gradient(self, y_true, y_pred):
        """
        Вычисляет градиент функции потерь кросс-энтропии по предсказаниям.

        Параметры:
        y_true (ndarray): Истинные метки (one-hot encoding).
        y_pred (ndarray): Предсказанные вероятности.

        Возвращает:
        ndarray: Градиент функции потерь.
        """
        y_pred = np.clip(y_pred, 1e-15, 1 - 1e-15)  # Для предотвращения числовых ошибок с логарифмом
        return (y_pred - y_true) / y_true.size


class HingeLoss:
    """
       Класс для вычисления потерь по принципу "защиты" (Hinge Loss),
       обычно используется для задач классификации.
       Этот класс реализует методы для вычисления потерь и градиента Hinge Loss.
       """
    def __call__(self, y_true, y_pred):
        """
        Вычисляет значение функции потерь хинжа для задач классификации.

        Параметры:
        y_true (ndarray): Истинные метки (-1 или 1).
        y_pred (ndarray): Предсказанные значения.

        Возвращает:
        float: Значение потерь хинжа.
        """
        return np.mean(np.maximum(0, 1 - y_true * y_pred))

    def gradient(self, y_true, y_pred):
        """
        Вычисляет градиент функции потерь хинжа по предсказаниям.

        Параметры:
        y_true (ndarray): Истинные метки (-1 или 1).
        y_pred (ndarray): Предсказанные значения.

        Возвращает:
        ndarray: Градиент функции потерь.
        """
        return np.where(y_true * y_pred < 1, -y_true, 0)
