//! Enhanced memory-efficient filter implementations using scirs2-core
//!
//! This module provides advanced memory-efficient versions of filters that leverage
//! scirs2-core's memory management infrastructure for optimal performance with large arrays.

use scirs2_core::ndarray::{Array, ArrayView, Dimension, Ix1, Ix2, IxDyn};
use scirs2_core::numeric::{Float, FromPrimitive, NumCast};
use std::fmt::Debug;

use scirs2_core::error::CoreResult;
use scirs2_core::memory_efficient::MemoryMappedArray;
use scirs2_core::MemoryMappedChunks;

use crate::chunked_v2::ChunkConfigV2;
use crate::error::{NdimageError, NdimageResult};
use crate::filters::{
    bilateral_filter, gaussian_filter, median_filter, uniform_filter, BorderMode,
};
use crate::mmap_io::create_temp_mmap;

/// Advanced configuration for memory-efficient filtering
#[derive(Debug, Clone)]
pub struct MemoryEfficientConfig {
    /// Base chunking configuration
    pub chunk_config: ChunkConfigV2,
    /// Enable adaptive chunking based on available memory
    pub adaptive: bool,
    /// Target memory usage in bytes
    pub target_memory_usage: usize,
    /// Use zero-copy operations where possible
    pub enable_zero_copy: bool,
    /// Cache size for frequently accessed chunks
    pub cache_size_mb: usize,
}

impl Default for MemoryEfficientConfig {
    fn default() -> Self {
        Self {
            chunk_config: ChunkConfigV2::default(),
            adaptive: true,
            target_memory_usage: 512 * 1024 * 1024, // 512 MB
            enable_zero_copy: true,
            cache_size_mb: 64,
        }
    }
}

/// Apply a filter to a memory-mapped image
#[allow(dead_code)]
pub fn filter_mmap<T, F>(
    input_mmap: &MemoryMappedArray<T>,
    filter_fn: F,
    config: Option<MemoryEfficientConfig>,
) -> NdimageResult<MemoryMappedArray<T>>
where
    T: Float + FromPrimitive + NumCast + Debug + Clone + Send + Sync + 'static,
    F: Fn(&ArrayView<T, IxDyn>) -> NdimageResult<Array<T, IxDyn>> + Send + Sync,
{
    let config = config.unwrap_or_default();

    // Create temporary output file
    let (mut output_mmap, _output_temp_path) = create_temp_mmap::<T>(&input_mmap.shape)?;

    // Determine chunking strategy
    let strategy = config.chunk_config.strategy;

    let total_size = input_mmap.size;
    let chunk_size = match strategy.clone() {
        scirs2_core::memory_efficient::ChunkingStrategy::Fixed(size) => size,
        scirs2_core::memory_efficient::ChunkingStrategy::NumChunks(n) => total_size.div_ceil(n),
        scirs2_core::memory_efficient::ChunkingStrategy::Auto => (total_size / 100).max(1),
        scirs2_core::memory_efficient::ChunkingStrategy::FixedBytes(bytes) => {
            (bytes / std::mem::size_of::<T>()).max(1)
        }
        scirs2_core::memory_efficient::ChunkingStrategy::Advanced(_) => (total_size / 100).max(1),
    };

    // Process input chunks and assemble filtered output in a flat buffer
    let chunk_results = input_mmap.process_chunks(strategy, |chunk_data, chunk_idx| {
        let chunk_array = Array::from_shape_vec(chunk_data.len(), chunk_data.to_vec())
            .ok()
            .map(|a| a.into_dyn());
        let result = chunk_array.and_then(|a| filter_fn(&a.view()).ok());
        result.map(|r| (chunk_idx, r))
    });

    // Assemble the full output data from chunk results, then write via process_chunks_mut
    let mut flat_output: Vec<T> = vec![T::zero(); total_size];
    for entry in chunk_results.into_iter().flatten() {
        let (chunk_idx, result) = entry;
        let start = chunk_idx * chunk_size;
        let result_data: Vec<T> = result.into_iter().collect();
        for (offset, val) in result_data.into_iter().enumerate() {
            let pos = start + offset;
            if pos < total_size {
                flat_output[pos] = val;
            }
        }
    }

    // Write the assembled output into the output mmap
    output_mmap.process_chunks_mut(
        scirs2_core::memory_efficient::ChunkingStrategy::Fixed(chunk_size),
        |chunk, chunk_idx| {
            let start = chunk_idx * chunk_size;
            for (i, elem) in chunk.iter_mut().enumerate() {
                let pos = start + i;
                if pos < total_size {
                    *elem = flat_output[pos];
                }
            }
        },
    );

    Ok(output_mmap)
}

/// Memory-efficient Gaussian filter with automatic optimization
#[allow(dead_code)]
pub fn gaussian_filter_auto<T, D>(
    input: &Array<T, D>,
    sigma: &[T],
    config: Option<MemoryEfficientConfig>,
) -> NdimageResult<Array<T, D>>
where
    T: Float + FromPrimitive + NumCast + Debug + Clone + Send + Sync + 'static,
    D: Dimension + 'static,
{
    let config = config.unwrap_or_default();
    let input_size = input.len() * std::mem::size_of::<T>();

    // Decide strategy based on input size
    if input_size > config.chunk_config.use_mmap_threshold.unwrap_or(usize::MAX) {
        // Use memory-mapped processing for very large inputs
        gaussian_filter_mmap(input, sigma, config)
    } else if input_size > config.target_memory_usage / 4 {
        // Use chunked processing for moderately large inputs
        gaussian_filter_chunked_adaptive(input, sigma, config)
    } else if config.enable_zero_copy && can_use_separable(sigma) {
        // Use separable filtering with zero-copy for efficiency
        gaussian_filter_separable_zerocopy(input, sigma)
    } else {
        // Use regular filtering for small inputs
        // Convert input to f64, apply filter, then convert back
        let input_f64 = input.mapv(|x| x.to_f64().unwrap_or(0.0));
        let sigma_f64 = sigma[0].to_f64().unwrap_or(1.0);
        let result_f64 = gaussian_filter(&input_f64, sigma_f64, Some(BorderMode::Reflect), None)?;
        let result = result_f64.mapv(|x| T::from_f64(x).unwrap_or(T::zero()));
        Ok(result)
    }
}

/// Gaussian filter using memory-mapped arrays
#[allow(dead_code)]
fn gaussian_filter_mmap<T, D>(
    input: &Array<T, D>,
    sigma: &[T],
    config: MemoryEfficientConfig,
) -> NdimageResult<Array<T, D>>
where
    T: Float + FromPrimitive + NumCast + Debug + Clone + Send + Sync + 'static,
    D: Dimension + 'static,
{
    // Create temporary memory-mapped arrays
    let (input_mmap, _input_temp_path) = create_temp_mmap::<T>(input.shape())?;
    let (output_mmap, _output_temp_path) = create_temp_mmap::<T>(input.shape())?;

    // Copy input to memory-mapped array
    // (Simplified - would need proper implementation)

    // Apply filter using memory-mapped processing
    let sigma_vec = sigma.to_vec();
    let filter_result = filter_mmap(
        &input_mmap,
        move |chunk| {
            // Convert chunk to f64, apply filter, then convert back
            let chunk_f64 = chunk.to_owned().mapv(|x| x.to_f64().unwrap_or(0.0));
            let sigma_f64 = sigma_vec[0].to_f64().unwrap_or(1.0);
            let result_f64 =
                gaussian_filter(&chunk_f64, sigma_f64, Some(BorderMode::Reflect), None)?;
            let result = result_f64.mapv(|x| T::from_f64(x).unwrap_or(T::zero()));
            Ok(result.into_dyn())
        },
        Some(config),
    )?;

    // Convert result back to regular array
    let result_view = filter_result
        .as_array::<D>()
        .map_err(|e| NdimageError::ProcessingError(format!("Failed to get result: {}", e)))?;

    // result_view is already of type Array<T, D>, no need for conversion
    Ok(result_view.to_owned())
}

/// Gaussian filter with adaptive chunking
#[allow(dead_code)]
fn gaussian_filter_chunked_adaptive<T, D>(
    input: &Array<T, D>,
    sigma: &[T],
    config: MemoryEfficientConfig,
) -> NdimageResult<Array<T, D>>
where
    T: Float + FromPrimitive + NumCast + Debug + Clone + Send + Sync + 'static,
    D: Dimension + 'static,
{
    use crate::chunked_v2::process_chunked_v2;

    let sigma_vec = sigma.to_vec();
    let border_mode = BorderMode::Reflect;

    let op = move |chunk: &ArrayView<T, IxDyn>| -> CoreResult<Array<T, IxDyn>> {
        // Convert chunk to f64, apply filter, then convert back
        let chunk_f64 = chunk.to_owned().mapv(|x| x.to_f64().unwrap_or(0.0));
        let sigma_f64 = sigma_vec[0].to_f64().unwrap_or(1.0);
        let result_f64 = gaussian_filter(&chunk_f64, sigma_f64, Some(border_mode), None)
            .expect("Gaussian filter should not fail");

        let result = result_f64.mapv(|x| T::from_f64(x).unwrap_or(T::zero()));
        Ok(result.into_dyn())
    };

    process_chunked_v2(&input.view(), &GaussianProcessorV2, &config.chunk_config)
}

/// Helper processor for Gaussian filter
struct GaussianProcessorV2;

impl<T: Float + Send + Sync, D: Dimension> crate::chunked_v2::ChunkProcessorV2<T, D>
    for GaussianProcessorV2
{
    fn create_processor(
        &self,
    ) -> Box<
        dyn Fn(&ArrayView<T, IxDyn>) -> scirs2_core::error::CoreResult<Array<T, IxDyn>>
            + Send
            + Sync,
    > {
        Box::new(|chunk| {
            // Placeholder implementation
            Ok(chunk.to_owned().into_dyn())
        })
    }

    fn required_overlap(&self) -> usize {
        0 // Handled by the operation
    }
}

/// Check if separable filtering can be used
#[allow(dead_code)]
fn can_use_separable<T: Float>(sigma: &[T]) -> bool {
    // Separable filtering is beneficial when all sigmas are reasonably large
    sigma.iter().all(|&s| {
        let s_f64: f64 = NumCast::from(s).unwrap_or(0.0);
        s_f64 > 0.5
    })
}

/// Gaussian filter using separable convolution with zero-copy operations
#[allow(dead_code)]
fn gaussian_filter_separable_zerocopy<T, D>(
    input: &Array<T, D>,
    sigma: &[T],
) -> NdimageResult<Array<T, D>>
where
    T: Float + FromPrimitive + NumCast + Debug + Clone + Send + Sync + 'static,
    D: Dimension + 'static,
{
    // For separable Gaussian, we can process each dimension independently
    // This is more memory-efficient than full convolution

    let mut result = input.clone();

    // Process each dimension
    for (dim, &sigma_dim) in sigma.iter().enumerate() {
        // Generate 1D Gaussian kernel
        let kernel_1d = generate_gaussian_kernel_1d(sigma_dim);

        // Apply 1D convolution along this dimension
        // (Simplified - would need proper implementation)
        result = convolve_1d_along_axis(&result, &kernel_1d, dim)?;
    }

    Ok(result)
}

/// Generate 1D Gaussian kernel
#[allow(dead_code)]
fn generate_gaussian_kernel_1d<T>(sigma: T) -> Array<T, Ix1>
where
    T: Float + FromPrimitive,
{
    use scirs2_core::ndarray::Array1;

    let sigma_f64: f64 = NumCast::from(sigma).unwrap_or(1.0);
    let truncate = 4.0;
    let kernel_size = (2.0 * truncate * sigma_f64).ceil() as usize + 1;
    let half_size = kernel_size / 2;

    let mut kernel = Array1::<T>::zeros(kernel_size);
    let norm_factor = T::from_f64(1.0 / (sigma_f64 * (2.0 * std::f64::consts::PI).sqrt()))
        .expect("Operation failed");

    for i in 0..kernel_size {
        let x = (i as i32 - half_size as i32) as f64;
        let value = (-0.5 * x * x / (sigma_f64 * sigma_f64)).exp();
        kernel[i] = norm_factor * T::from_f64(value).expect("Operation failed");
    }

    // Normalize
    let sum = kernel.sum();
    kernel.mapv_inplace(|x| x / sum);

    kernel
}

/// Apply 1D convolution along a specific axis
#[allow(dead_code)]
fn convolve_1d_along_axis<T, D>(
    input: &Array<T, D>,
    _kernel: &Array<T, Ix1>,
    _axis: usize,
) -> NdimageResult<Array<T, D>>
where
    T: Float + FromPrimitive + NumCast + Debug + Clone + Send + Sync,
    D: Dimension,
{
    // This would need a proper implementation
    // For now, just return a clone
    Ok(input.clone())
}

/// Memory-efficient bilateral filter
#[allow(dead_code)]
pub fn bilateral_filter_efficient<T>(
    input: &Array<T, Ix2>,
    spatial_sigma: T,
    range_sigma: T,
    config: Option<MemoryEfficientConfig>,
) -> NdimageResult<Array<T, Ix2>>
where
    T: Float
        + FromPrimitive
        + NumCast
        + Debug
        + Clone
        + Send
        + Sync
        + std::ops::DivAssign
        + std::ops::AddAssign
        + std::fmt::Display
        + 'static,
{
    let config = config.unwrap_or_default();

    // Bilateral filter is memory-intensive, so we always use chunking for large images
    let input_size = input.len() * std::mem::size_of::<T>();

    if input_size > config.target_memory_usage / 2 {
        // Use chunked processing
        use crate::chunked_v2::process_chunked_v2;

        let op = move |chunk: &ArrayView<T, IxDyn>| -> CoreResult<Array<T, IxDyn>> {
            let chunk_2d = chunk.to_owned().into_dimensionality::<Ix2>().map_err(|_| {
                scirs2_core::error::CoreError::DimensionError(
                    scirs2_core::error::ErrorContext::new("Expected 2D array".to_string()),
                )
            })?;

            bilateral_filter(&chunk_2d, spatial_sigma, range_sigma, None)
                .map(|r| r.into_dyn())
                .map_err(|e| {
                    scirs2_core::error::CoreError::ComputationError(
                        scirs2_core::error::ErrorContext::new(e.to_string()),
                    )
                })
        };

        process_chunked_v2(&input.view(), &BilateralProcessorV2, &config.chunk_config)
    } else {
        // Use regular processing
        bilateral_filter(input, spatial_sigma, range_sigma, None)
    }
}

/// Helper processor for bilateral filter
struct BilateralProcessorV2;

impl<T: Float + Send + Sync> crate::chunked_v2::ChunkProcessorV2<T, Ix2> for BilateralProcessorV2 {
    fn create_processor(
        &self,
    ) -> Box<
        dyn Fn(&ArrayView<T, IxDyn>) -> scirs2_core::error::CoreResult<Array<T, IxDyn>>
            + Send
            + Sync,
    > {
        Box::new(|chunk| {
            // Placeholder implementation
            Ok(chunk.to_owned().into_dyn())
        })
    }

    fn required_overlap(&self) -> usize {
        10 // Bilateral filter needs significant overlap
    }
}

/// Process a filter pipeline efficiently
#[allow(dead_code)]
pub fn filter_pipeline<T, D>(
    input: &Array<T, D>,
    operations: Vec<FilterOp<T>>,
    config: Option<MemoryEfficientConfig>,
) -> NdimageResult<Array<T, D>>
where
    T: Float
        + FromPrimitive
        + NumCast
        + Debug
        + Clone
        + Send
        + Sync
        + std::ops::DivAssign
        + std::ops::AddAssign
        + std::fmt::Display
        + 'static,
    D: Dimension + 'static,
{
    let config = config.unwrap_or_default();

    // For pipeline operations, we can optimize by keeping intermediate results
    // in memory-mapped arrays if they're large

    let mut current = input.clone();

    for op in operations {
        current = match op {
            FilterOp::Gaussian { sigma } => {
                gaussian_filter_auto(&current, &sigma, Some(config.clone()))?
            }
            FilterOp::Median { size } => median_filter(&current, &size, Some(BorderMode::Reflect))?,
            FilterOp::Uniform { size } => {
                uniform_filter(&current, &size, Some(BorderMode::Reflect), None)?
            }
            FilterOp::Custom(f) => {
                let current_dyn = current.clone().into_dyn();
                let result_dyn = f(&current_dyn)?;
                result_dyn.into_dimensionality::<D>().map_err(|_| {
                    NdimageError::DimensionError(
                        "Failed to convert result back to original dimension".into(),
                    )
                })?
            }
        };
    }

    Ok(current)
}

/// Filter operation for pipeline
pub enum FilterOp<T> {
    Gaussian { sigma: Vec<T> },
    Median { size: Vec<usize> },
    Uniform { size: Vec<usize> },
    Custom(Box<dyn Fn(&Array<T, IxDyn>) -> NdimageResult<Array<T, IxDyn>> + Send + Sync>),
}

/// Builder for memory-efficient configuration
pub struct MemoryEfficientConfigBuilder {
    config: MemoryEfficientConfig,
}

impl MemoryEfficientConfigBuilder {
    pub fn new() -> Self {
        Self {
            config: MemoryEfficientConfig::default(),
        }
    }

    pub fn chunk_config(mut self, config: ChunkConfigV2) -> Self {
        self.config.chunk_config = config;
        self
    }

    pub fn adaptive(mut self, adaptive: bool) -> Self {
        self.config.adaptive = adaptive;
        self
    }

    pub fn target_memory_mb(mut self, mb: usize) -> Self {
        self.config.target_memory_usage = mb * 1024 * 1024;
        self
    }

    pub fn enable_zero_copy(mut self, enable: bool) -> Self {
        self.config.enable_zero_copy = enable;
        self
    }

    pub fn cache_size_mb(mut self, mb: usize) -> Self {
        self.config.cache_size_mb = mb;
        self
    }

    pub fn build(self) -> MemoryEfficientConfig {
        self.config
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use approx::assert_abs_diff_eq;
    use scirs2_core::ndarray::Array2;

    #[test]
    fn test_gaussian_filter_auto() {
        let input = Array2::<f64>::ones((50, 50));
        let sigma = vec![1.0, 1.0];

        let config = MemoryEfficientConfigBuilder::new()
            .target_memory_mb(1)
            .enable_zero_copy(true)
            .build();

        let result = gaussian_filter_auto(&input, &sigma, Some(config)).expect("Operation failed");

        assert_eq!(result.shape(), input.shape());

        // Center values should be close to 1 for uniform input
        assert_abs_diff_eq!(result[[25, 25]], 1.0, epsilon = 0.01);
    }

    #[test]
    fn test_filter_pipeline() {
        let input = Array2::<f64>::from_elem((30, 30), 1.0);

        let operations = vec![
            FilterOp::Gaussian {
                sigma: vec![0.5, 0.5],
            },
            FilterOp::Uniform { size: vec![3, 3] },
        ];

        let result = filter_pipeline(&input, operations, None).expect("Operation failed");

        assert_eq!(result.shape(), input.shape());
    }

    #[test]
    fn test_config_builder() {
        let config = MemoryEfficientConfigBuilder::new()
            .target_memory_mb(256)
            .adaptive(true)
            .enable_zero_copy(true)
            .cache_size_mb(32)
            .build();

        assert_eq!(config.target_memory_usage, 256 * 1024 * 1024);
        assert!(config.adaptive);
        assert!(config.enable_zero_copy);
        assert_eq!(config.cache_size_mb, 32);
    }
}
