from concurrent.futures import ProcessPoolExecutor, as_completed

from .time_domain import (
    mean, std_dev, rms, skewness, kurtosis, zero_crossing_rate,
    max_value, min_value, peak_to_peak, variance, iqr, num_peaks,
    signal_line_length, crest_factor, shape_factor, impulse_factor,
    signal_range, mean_crossing_rate, signal_variability, peak_amplitude,
    energy, median, rss, dasdv, range_ratio
)
from .fractal import (
    higuchi_fractal_dimension, hurst_exponent, lyapunov_exponent,
    katz_fractal_dimension, petrosian_fractal_dimension,
    box_counting_fractal_dimension, correlation_dimension
)
from .complexity import (
    approximate_entropy, sample_entropy, shannon_entropy,
    lempel_ziv_complexity, permutation_entropy, largest_lyapunov_exponent,
    mobility
)
from .information import (
    mutual_information, symbolic_dynamics_entropy, conditional_entropy
)
from .statistical import (
    higher_order_moments, percentiles, l_moments, quantile_range,
    autoregressive_coefficients
)
from .geometric import (
    recurrence_quantification, attractor_reconstruction, max_slope
)
from .utils import extract_windows


def all_features(signal, window_size, overlap, parallel=True):
    """
    Calculate and return all defined feature functions in a dictionary,
    where each key is the feature name and the value is the corresponding
    array of feature values per window. The function precomputes the sliding
    windows once and passes them to each feature function. Optionally, the
    feature computations are performed in parallel.

    :param signal: The input signal as a 1D array or list.
    :type signal: array-like
    :param window_size: The number of samples in each window.
    :type window_size: int
    :param overlap: The number of samples to overlap between consecutive windows.
    :type overlap: int
    :param parallel: If True, compute features concurrently using multiprocessing.
    :type parallel: bool
    :return: A dictionary mapping feature names to their computed arrays.
    :rtype: dict
    """
    # Precompute the sliding windows once.
    windows = extract_windows(signal, window_size, overlap)

    # Dictionary mapping feature names to their functions.
    feature_funcs = {
        'mean': mean,
        'std_dev': std_dev,
        'rms': rms,
        'skewness': skewness,
        'kurtosis': kurtosis,
        'zero_crossing_rate': zero_crossing_rate,
        'max_value': max_value,
        'min_value': min_value,
        'peak_to_peak': peak_to_peak,
        'variance': variance,
        'iqr': iqr,
        'num_peaks': num_peaks,
        'signal_line_length': signal_line_length,
        'crest_factor': crest_factor,
        'shape_factor': shape_factor,
        'impulse_factor': impulse_factor,
        'signal_range': signal_range,
        'mean_crossing_rate': mean_crossing_rate,
        'signal_variability': signal_variability,
        'peak_amplitude': peak_amplitude,
        'energy': energy,
        'median': median,
        'rss': rss,
        'dasdv': dasdv,
        'range_ratio': range_ratio,
        'higuchi_fractal_dimension': higuchi_fractal_dimension,
        'hurst_exponent': hurst_exponent,
        'lyapunov_exponent': lyapunov_exponent,
        'katz_fractal_dimension': katz_fractal_dimension,
        'petrosian_fractal_dimension': petrosian_fractal_dimension,
        'box_counting_fractal_dimension': box_counting_fractal_dimension,
        'correlation_dimension': correlation_dimension,
        'approximate_entropy': approximate_entropy,
        'sample_entropy': sample_entropy,
        'shannon_entropy': shannon_entropy,
        'lempel_ziv_complexity': lempel_ziv_complexity,
        'permutation_entropy': permutation_entropy,
        'largest_lyapunov_exponent': largest_lyapunov_exponent,
        'mobility': mobility,
        'mutual_information': mutual_information,
        'symbolic_dynamics_entropy': symbolic_dynamics_entropy,
        'conditional_entropy': conditional_entropy,
        'higher_order_moments': higher_order_moments,
        'percentiles': percentiles,
        'l_moments': l_moments,
        'quantile_range': quantile_range,
        'autoregressive_coefficients': autoregressive_coefficients,
        'recurrence_quantification': recurrence_quantification,
        'attractor_reconstruction': attractor_reconstruction,
        'max_slope': max_slope,
    }

    results = {}
    if parallel:
        with ProcessPoolExecutor() as executor:
            # Submit each feature function concurrently.
            future_to_feature = {
                executor.submit(func, windows=windows): name
                for name, func in feature_funcs.items()
            }
            for future in as_completed(future_to_feature):
                name = future_to_feature[future]
                try:
                    results[name] = future.result()
                except Exception as e:
                    results[name] = None
                    print(f"Error computing feature {name}: {e}")
    else:
        for name, func in feature_funcs.items():
            results[name] = func(windows=windows)

    return results
