from ...forall import *

#DFT Дискретное
def DFT(x):
    """
    Вычисляет дискретное преобразование Фурье (ДПФ) для входного сигнала.

    Теоретическая часть:
    Дискретное преобразование Фурье преобразует конечную последовательность N равномерно распределенных выборок функции
    в последовательность той же длины из равномерно распределенных выборок дискретного по времени преобразования Фурье (DTFT).
    Формула ДПФ: X_k = Σ_{n=0}^{N-1} x_n * exp(-2j * π * k * n / N), где k = 0, ..., N-1 [[1]](https://ru.wikipedia.org/wiki/Дискретное_преобразование_Фурье).

    Практическая реализация:
    - Входной сигнал `x` представляет собой массив NumPy.
    - Создаются массивы индексов `n` и `k`.
    - Вычисляется матрица комплексных экспонент `e`.
    - Результат `X` получается путем матричного умножения `e` на `x`.

    Parameters
    ----------
    x : numpy.ndarray
        Входной одномерный массив (сигнал) длиной N.

    Returns
    -------
    numpy.ndarray
        Массив коэффициентов ДПФ длиной N.

    Examples
    --------
    >>> x = np.array([1.0, 2.0, 3.0, 4.0])
    >>> DFT(x)
    array([10.+0.j, -2.+2.j, -2.+0.j, -2.-2.j])

    Notes
    -----
    1. Данная реализация использует прямой метод вычисления ДПФ.
    2. Вычислительная сложность составляет O(N^2), что делает его неэффективным для больших N.
       Для больших массивов рекомендуется использовать быстрое преобразование Фурье (FFT).

    References
    ----------
    .. [1] "Дискретное преобразование Фурье" — Википедия, https://ru.wikipedia.org/wiki/Дискретное_преобразование_Фурье
    """
    N = len(x)
    n = np.arange(N)
    k = n.reshape((N, 1))
    e = np.exp(-2j * np.pi * k * n / N)
    X = e @ x
    return X


#IDFT Обратное
def IDFT(X):
    """
    Вычисляет обратное дискретное преобразование Фурье (ОДПФ) для массива коэффициентов.

    Теоретическая часть:
    Обратное дискретное преобразование Фурье восстанавливает исходный сигнал из его частотных компонент.
    Формула ОДПФ: x_n = (1/N) * Σ_{k=0}^{N-1} X_k * exp(2j * π * k * n / N), где n = 0, ..., N-1 [[1]](https://ru.wikipedia.org/wiki/Дискретное_преобразование_Фурье#Обратное_преобразование).

    Практическая реализация:
    - Входной массив `X` представляет собой коэффициенты ДПФ.
    - Создаются массивы индексов `n` и `k`.
    - Вычисляется матрица комплексных экспонент `e` с положительным знаком в показателе.
    - Результат `x` получается путем матричного умножения `e` на `X` и последующей нормализации делением на N.

    Parameters
    ----------
    X : numpy.ndarray
        Входной одномерный массив коэффициентов ДПФ длиной N.

    Returns
    -------
    numpy.ndarray
        Восстановленный сигнал длиной N.

    Examples
    --------
    >>> X = np.array([10.+0.j, -2.+2.j, -2.+0.j, -2.-2.j])
    >>> result = IDFT(X)
    >>> np.allclose(result, np.array([1.0, 2.0, 3.0, 4.0]))
    True

    Notes
    -----
    1. Данная реализация использует прямой метод вычисления ОДПФ.
    2. Вычислительная сложность составляет O(N^2).
    3. Из-за ошибок округления с плавающей запятой, восстановленный сигнал может содержать очень малые мнимые части,
       даже если исходный сигнал был вещественным.

    References
    ----------
    .. [1] "Дискретное преобразование Фурье - Обратное преобразование" — Википедия, https://ru.wikipedia.org/wiki/Дискретное_преобразование_Фурье#Обратное_преобразование
    """
    N = len(X)
    k = np.arange(N)
    n = k.reshape((N, 1))
    e = np.exp(2j * np.pi * k * n / N)
    x = (e @ X) / N
    return x
####################################################################################

#FFT Быстрое
def FFT(x):
    """
    Вычисляет быстрое преобразование Фурье (БПФ) с использованием рекурсивного алгоритма Кули-Тьюки.

    Теоретическая часть:
    Быстрое преобразование Фурье — это алгоритм вычисления ДПФ, обладающий значительно меньшей вычислительной сложностью
    по сравнению с прямым вычислением. Алгоритм Кули-Тьюки основан на принципе "разделяй и властвуй",
    рекурсивно разбивая задачу ДПФ на более мелкие подзадачи [[1]](https://ru.wikipedia.org/wiki/Быстрое_преобразование_Фурье).

    Практическая реализация:
    - Если длина входного сигнала `N` меньше или равна 1, возвращается сам сигнал (базовый случай рекурсии).
    - Сигнал делится на две части: с четными и нечетными индексами.
    - БПФ рекурсивно вычисляется для каждой из этих частей.
    - Результаты комбинируются с использованием так называемых "поворотных множителей" (twiddle factors).

    Parameters
    ----------
    x : numpy.ndarray
        Входной одномерный массив (сигнал). Для максимальной эффективности классического алгоритма Кули-Тьюки
        длина массива должна быть степенью двойки, хотя данная реализация будет работать и для других длин.

    Returns
    -------
    numpy.ndarray
        Массив коэффициентов БПФ.

    Examples
    --------
    >>> x = np.array([1.0, 2.0, 3.0, 4.0])
    >>> FFT(x)
    array([10.+0.j, -2.+2.j, -2.+0.j, -2.-2.j])

    Notes
    -----
    1. Вычислительная сложность составляет O(N log N).
    2. Значительно эффективнее прямого ДПФ для больших N.
    3. Данная реализация представляет собой простую рекурсивную версию алгоритма Кули-Тьюки.

    References
    ----------
    .. [1] "Быстрое преобразование Фурье" — Википедия, https://ru.wikipedia.org/wiki/Быстрое_преобразование_Фурье
    """
    N = len(x)
    if N <= 1:  # Базовый случай рекурсии
        return x
    # Рекурсивно вычисляем FFT для четных и нечетных элементов
    even = FFT(x[::2])
    odd = FFT(x[1::2])
    # Комбинируем результаты
    factor = np.exp(-2j * np.pi * np.arange(N) / N)
    return np.concatenate([
        even + factor[:N//2] * odd,
        even + factor[N//2:] * odd
    ])
####################################################################################

def IFFT(X):
    """
    Вычисляет обратное быстрое преобразование Фурье (ОБПФ) с использованием рекурсивного алгоритма.

    Теоретическая часть:
    Обратное быстрое преобразование Фурье аналогично прямому БПФ, но использует сопряженные комплексные
    поворотные множители и включает нормализацию результата на N (длину сигнала).
    Это позволяет восстановить исходный сигнал из его спектра с эффективностью O(N log N) [[1]](https://ru.wikipedia.org/wiki/Быстрое_преобразование_Фурье).

    Практическая реализация:
    - Если длина входного массива коэффициентов `N` меньше или равна 1, возвращается сам массив (базовый случай рекурсии).
    - Массив коэффициентов делится на две части: с четными и нечетными индексами.
    - ОБПФ рекурсивно вычисляется для каждой из этих частей.
    - Результаты комбинируются с использованием поворотных множителей с положительным знаком в экспоненте.
    - На каждом шаге рекурсии результат делится на 2, что в совокупности обеспечивает деление на N.

    Parameters
    ----------
    X : numpy.ndarray
        Входной одномерный массив коэффициентов БПФ.

    Returns
    -------
    numpy.ndarray
        Восстановленный сигнал.

    Examples
    --------
    >>> X = np.array([10.+0.j, -2.+2.j, -2.+0.j, -2.-2.j])
    >>> result = IFFT(X)
    >>> np.allclose(result, np.array([1.0, 2.0, 3.0, 4.0]))
    True

    Notes
    -----
    1. Вычислительная сложность составляет O(N log N).
    2. Нормализация (деление на N) распределена по шагам рекурсии (деление на 2 на каждом уровне).
    3. Из-за ошибок округления с плавающей запятой, восстановленный сигнал может содержать очень малые мнимые части.

    References
    ----------
    .. [1] "Быстрое преобразование Фурье" — Википедия, https://ru.wikipedia.org/wiki/Быстрое_преобразование_Фурье
    """
    N = len(X)
    if N <= 1:  # Базовый случай рекурсии
        return X
    # Рекурсивно вычисляем IFFT для четных и нечетных элементов
    even = IFFT(X[::2])
    odd = IFFT(X[1::2])
    # Комбинируем результаты (используем +2j для обратного преобразования)
    factor = np.exp(2j * np.pi * np.arange(N) / N)
    return np.concatenate([
        even + factor[:N//2] * odd,
        even + factor[N//2:] * odd
    ]) / 2  # Нормализация на 2 (в итоге на N)
####################################################################################
FOURIER = [DFT,IDFT,FFT,IFFT]