import numpy as np
from scipy.signal import find_peaks
from .utils import apply_sliding_window

def mean(signal=None, window_size=None, overlap=None, windows=None):
    """
    Calculate the mean of the signal within each window.

    :param signal: The input signal as a 1D array or list (if windows not provided).
    :param window_size: The number of samples in each window (if windows not provided).
    :param overlap: The number of samples to overlap between consecutive windows (if windows not provided).
    :param windows: Optional precomputed list of windows.
    :return: 1D array of mean values for each window.
    :rtype: numpy.ndarray
    """
    return apply_sliding_window(np.mean, windows=windows, signal=signal, window_size=window_size, overlap=overlap)


def std_dev(signal=None, window_size=None, overlap=None, windows=None):
    """
    Calculate the standard deviation of the signal within each window.

    :param signal: 1D array-like input signal.
    :param window_size: Number of samples in each window.
    :param overlap: Samples to overlap between windows.
    :param windows: Optional precomputed windows.
    :return: 1D array of standard deviation values for each window.
    :rtype: numpy.ndarray
    """
    return apply_sliding_window(np.std, windows=windows, signal=signal, window_size=window_size, overlap=overlap)


def rms(signal=None, window_size=None, overlap=None, windows=None):
    """
    Calculate the root mean square (RMS) of the signal within each window.

    :param signal: 1D array-like input signal.
    :param window_size: Number of samples per window.
    :param overlap: Overlap between windows.
    :param windows: Optional precomputed windows.
    :return: 1D array of RMS values for each window.
    :rtype: numpy.ndarray
    """
    return apply_sliding_window(lambda x: np.sqrt(np.mean(x ** 2)),
                                windows=windows, signal=signal, window_size=window_size, overlap=overlap)


def skewness(signal=None, window_size=None, overlap=None, windows=None):
    """
    Calculate the skewness of the signal within each window.

    :param signal: 1D array-like input signal.
    :param window_size: Number of samples per window.
    :param overlap: Overlap between windows.
    :param windows: Optional precomputed windows.
    :return: 1D array of skewness values for each window.
    :rtype: numpy.ndarray
    """
    return apply_sliding_window(
        lambda x: np.mean(((x - np.mean(x)) / np.std(x)) ** 3),
        windows=windows, signal=signal, window_size=window_size, overlap=overlap)


def kurtosis(signal=None, window_size=None, overlap=None, windows=None):
    """
    Calculate the kurtosis of the signal within each window.

    :param signal: 1D array-like input signal.
    :param window_size: Number of samples per window.
    :param overlap: Overlap between windows.
    :param windows: Optional precomputed windows.
    :return: 1D array of kurtosis values for each window.
    :rtype: numpy.ndarray
    """
    return apply_sliding_window(
        lambda x: np.mean(((x - np.mean(x)) / np.std(x)) ** 4) - 3,
        windows=windows, signal=signal, window_size=window_size, overlap=overlap)


def zero_crossing_rate(signal=None, window_size=None, overlap=None, windows=None):
    """
    Calculate the zero-crossing rate of the signal within each window.

    :param signal: 1D array-like input signal.
    :param window_size: Number of samples per window.
    :param overlap: Overlap between windows.
    :param windows: Optional precomputed windows.
    :return: 1D array of zero-crossing rates for each window.
    :rtype: numpy.ndarray
    """
    return apply_sliding_window(
        lambda x: np.sum(np.diff(np.signbit(x))) / len(x),
        windows=windows, signal=signal, window_size=window_size, overlap=overlap)


def max_value(signal=None, window_size=None, overlap=None, windows=None):
    """
    Calculate the maximum value of the signal within each window.

    :param signal: 1D array-like input signal.
    :param window_size: Number of samples per window.
    :param overlap: Overlap between windows.
    :param windows: Optional precomputed windows.
    :return: 1D array of maximum values for each window.
    :rtype: numpy.ndarray
    """
    return apply_sliding_window(np.max, windows=windows, signal=signal, window_size=window_size, overlap=overlap)


def min_value(signal=None, window_size=None, overlap=None, windows=None):
    """
    Calculate the minimum value of the signal within each window.

    :param signal: 1D array-like input signal.
    :param window_size: Number of samples per window.
    :param overlap: Overlap between windows.
    :param windows: Optional precomputed windows.
    :return: 1D array of minimum values for each window.
    :rtype: numpy.ndarray
    """
    return apply_sliding_window(np.min, windows=windows, signal=signal, window_size=window_size, overlap=overlap)


def peak_to_peak(signal=None, window_size=None, overlap=None, windows=None):
    """
    Calculate the peak-to-peak amplitude (range) of the signal within each window.

    :param signal: 1D array-like input signal.
    :param window_size: Number of samples per window.
    :param overlap: Overlap between windows.
    :param windows: Optional precomputed windows.
    :return: 1D array of peak-to-peak values for each window.
    :rtype: numpy.ndarray
    """
    return apply_sliding_window(np.ptp, windows=windows, signal=signal, window_size=window_size, overlap=overlap)


def variance(signal=None, window_size=None, overlap=None, windows=None):
    """
    Calculate the variance of the signal within each window.

    :param signal: 1D array-like input signal.
    :param window_size: Number of samples per window.
    :param overlap: Overlap between windows.
    :param windows: Optional precomputed windows.
    :return: 1D array of variance values for each window.
    :rtype: numpy.ndarray
    """
    return apply_sliding_window(np.var, windows=windows, signal=signal, window_size=window_size, overlap=overlap)


def iqr(signal=None, window_size=None, overlap=None, windows=None):
    """
    Calculate the interquartile range (IQR) of the signal within each window.

    :param signal: 1D array-like input signal.
    :param window_size: Number of samples per window.
    :param overlap: Overlap between windows.
    :param windows: Optional precomputed windows.
    :return: 1D array of IQR values for each window.
    :rtype: numpy.ndarray
    """
    return apply_sliding_window(
        lambda x: np.percentile(x, 75) - np.percentile(x, 25),
        windows=windows, signal=signal, window_size=window_size, overlap=overlap)


def num_peaks(signal=None, window_size=None, overlap=None, windows=None):
    """
    Calculate the number of peaks in the signal within each window.

    :param signal: 1D array-like input signal.
    :param window_size: Number of samples per window.
    :param overlap: Overlap between windows.
    :param windows: Optional precomputed windows.
    :return: 1D array of peak counts for each window.
    :rtype: numpy.ndarray
    """
    return apply_sliding_window(
        lambda x: len(find_peaks(x)[0]),
        windows=windows, signal=signal, window_size=window_size, overlap=overlap)


def signal_line_length(signal=None, window_size=None, overlap=None, windows=None):
    """
    Calculate the signal line length (sum of absolute differences) within each window.

    :param signal: 1D array-like input signal.
    :param window_size: Number of samples per window.
    :param overlap: Overlap between windows.
    :param windows: Optional precomputed windows.
    :return: 1D array of signal line lengths for each window.
    :rtype: numpy.ndarray
    """
    return apply_sliding_window(
        lambda x: np.sum(np.abs(np.diff(x))),
        windows=windows, signal=signal, window_size=window_size, overlap=overlap)


def crest_factor(signal=None, window_size=None, overlap=None, windows=None):
    """
    Calculate the crest factor of the signal within each window.

    :param signal: 1D array-like input signal.
    :param window_size: Number of samples per window.
    :param overlap: Overlap between windows.
    :param windows: Optional precomputed windows.
    :return: 1D array of crest factor values for each window.
    :rtype: numpy.ndarray
    """
    return apply_sliding_window(
        lambda x: np.max(np.abs(x)) / np.sqrt(np.mean(x ** 2)),
        windows=windows, signal=signal, window_size=window_size, overlap=overlap)


def shape_factor(signal=None, window_size=None, overlap=None, windows=None):
    """
    Calculate the shape factor of the signal within each window, defined as the ratio of RMS to the mean absolute value.

    :param signal: 1D array-like input signal.
    :param window_size: Number of samples per window.
    :param overlap: Overlap between windows.
    :param windows: Optional precomputed windows.
    :return: 1D array of shape factor values for each window.
    :rtype: numpy.ndarray
    """
    return apply_sliding_window(
        lambda x: np.sqrt(np.mean(x ** 2)) / np.mean(np.abs(x)),
        windows=windows, signal=signal, window_size=window_size, overlap=overlap)


def impulse_factor(signal=None, window_size=None, overlap=None, windows=None):
    """
    Calculate the impulse factor of the signal within each window, defined as the ratio of the peak value to the mean absolute value.

    :param signal: 1D array-like input signal.
    :param window_size: Number of samples per window.
    :param overlap: Overlap between windows.
    :param windows: Optional precomputed windows.
    :return: 1D array of impulse factor values for each window.
    :rtype: numpy.ndarray
    """
    return apply_sliding_window(
        lambda x: np.max(np.abs(x)) / np.mean(np.abs(x)),
        windows=windows, signal=signal, window_size=window_size, overlap=overlap)


def signal_range(signal=None, window_size=None, overlap=None, windows=None):
    """
    Calculate the range of the signal (max - min) within each window.

    :param signal: 1D array-like input signal.
    :param window_size: Number of samples per window.
    :param overlap: Overlap between windows.
    :param windows: Optional precomputed windows.
    :return: 1D array of signal ranges for each window.
    :rtype: numpy.ndarray
    """
    return apply_sliding_window(
        lambda x: np.max(x) - np.min(x),
        windows=windows, signal=signal, window_size=window_size, overlap=overlap)


def mean_crossing_rate(signal=None, window_size=None, overlap=None, windows=None):
    """
    Calculate the mean crossing rate of the signal within each window. This measures how often the signal crosses its mean value.

    :param signal: 1D array-like input signal.
    :param window_size: Number of samples per window.
    :param overlap: Overlap between windows.
    :param windows: Optional precomputed windows.
    :return: 1D array of mean crossing rates for each window.
    :rtype: numpy.ndarray
    """
    return apply_sliding_window(
        lambda x: np.sum(np.diff(x > np.mean(x)) != 0) / len(x),
        windows=windows, signal=signal, window_size=window_size, overlap=overlap)


def signal_variability(signal=None, window_size=None, overlap=None, windows=None):
    """
    Calculate the signal variability, defined as the standard deviation divided by the square root of the window length.

    :param signal: 1D array-like input signal.
    :param window_size: Number of samples per window.
    :param overlap: Overlap between windows.
    :param windows: Optional precomputed windows.
    :return: 1D array of signal variability values for each window.
    :rtype: numpy.ndarray
    """
    return apply_sliding_window(
        lambda x: np.std(x) / np.sqrt(len(x)),
        windows=windows, signal=signal, window_size=window_size, overlap=overlap)


def peak_amplitude(signal=None, window_size=None, overlap=None, windows=None):
    """
    Calculate the peak amplitude of the signal within each window.

    :param signal: 1D array-like input signal.
    :param window_size: Number of samples per window.
    :param overlap: Overlap between windows.
    :param windows: Optional precomputed windows.
    :return: 1D array of peak amplitudes for each window.
    :rtype: numpy.ndarray
    """
    return apply_sliding_window(np.max, windows=windows, signal=signal, window_size=window_size, overlap=overlap)


def energy(signal=None, window_size=None, overlap=None, windows=None):
    """
    Calculate the signal energy (sum of squared values) within each window.

    :param signal: 1D array-like input signal.
    :param window_size: Number of samples per window.
    :param overlap: Overlap between windows.
    :param windows: Optional precomputed windows.
    :return: 1D array of energy values for each window.
    :rtype: numpy.ndarray
    """
    return apply_sliding_window(
        lambda x: np.sum(x ** 2),
        windows=windows, signal=signal, window_size=window_size, overlap=overlap)


def median(signal=None, window_size=None, overlap=None, windows=None):
    """
    Calculate the median of the signal within each window.

    :param signal: 1D array-like input signal.
    :param window_size: Number of samples per window.
    :param overlap: Overlap between windows.
    :param windows: Optional precomputed windows.
    :return: 1D array of median values for each window.
    :rtype: numpy.ndarray
    """
    return apply_sliding_window(np.median, windows=windows, signal=signal, window_size=window_size, overlap=overlap)


def rss(signal=None, window_size=None, overlap=None, windows=None):
    """
    Calculate the root sum of squares (RSS) of the signal within each window.

    :param signal: 1D array-like input signal.
    :param window_size: Number of samples per window.
    :param overlap: Overlap between windows.
    :param windows: Optional precomputed windows.
    :return: 1D array of RSS values for each window.
    :rtype: numpy.ndarray
    """
    return apply_sliding_window(
        lambda x: np.sqrt(np.sum(x ** 2)),
        windows=windows, signal=signal, window_size=window_size, overlap=overlap)


def dasdv(signal=None, window_size=None, overlap=None, windows=None):
    """
    Calculate the Difference Absolute Standard Deviation Value (DASDV) of the signal within each window.

    :param signal: 1D array-like input signal.
    :param window_size: Number of samples per window.
    :param overlap: Overlap between windows.
    :param windows: Optional precomputed windows.
    :return: 1D array of DASDV values for each window.
    :rtype: numpy.ndarray
    """
    return apply_sliding_window(
        lambda x: np.sqrt(np.mean(np.diff(x) ** 2)),
        windows=windows, signal=signal, window_size=window_size, overlap=overlap)


def range_ratio(signal=None, window_size=None, overlap=None, windows=None):
    """
    Calculate the ratio of the range (max - min) to the standard deviation of the signal within each window.

    :param signal: 1D array-like input signal.
    :param window_size: Number of samples per window.
    :param overlap: Overlap between windows.
    :param windows: Optional precomputed windows.
    :return: 1D array of range ratios for each window.
    :rtype: numpy.ndarray
    """
    return apply_sliding_window(
        lambda x: (np.max(x) - np.min(x)) / np.std(x),
        windows=windows, signal=signal, window_size=window_size, overlap=overlap)
