//! Transformation-based interpolation functions

use scirs2_core::ndarray::{Array, Dimension};
use scirs2_core::numeric::{Float, FromPrimitive};
use std::fmt::Debug;

use super::utils::{interpolate_linear, interpolate_nearest};
use super::{BoundaryMode, InterpolationOrder};
use crate::error::{NdimageError, NdimageResult};

/// Apply an affine transformation to an array using interpolation
///
/// Performs geometric transformations like rotation, scaling, shearing, and translation
/// on n-dimensional arrays. The transformation is defined by a matrix and optional offset.
/// This function is fundamental for image registration, data augmentation, and geometric
/// corrections in computer vision and scientific computing.
///
/// # Arguments
///
/// * `input` - Input array to transform
/// * `matrix` - Transformation matrix (ndim × ndim) defining the affine transformation
/// * `offset` - Translation vector (optional, defaults to zeros)
/// * `outputshape` - Shape of output array (optional, defaults to input shape)
/// * `order` - Interpolation method (optional, defaults to Linear)
///   - `Nearest`: Fast but may introduce aliasing
///   - `Linear`: Good balance of speed and quality
///   - `Cubic`: Higher quality, slower computation
/// * `mode` - Boundary handling for points outside input (optional, defaults to Constant)
/// * `cval` - Fill value for constant boundary mode (optional, defaults to 0.0)
/// * `prefilter` - Apply spline prefiltering for high-order interpolation (optional, defaults to true)
///
/// # Returns
///
/// * `Result<Array<T, D>>` - Transformed array with specified output shape
///
/// # Examples
///
/// ## Basic 2D rotation
/// ```
/// use scirs2_core::ndarray::{Array2, array};
/// use scirs2_ndimage::interpolation::{affine_transform, InterpolationOrder};
/// use std::f64::consts::PI;
///
/// // Create a simple test image
/// let image = array![
///     [0.0, 1.0, 0.0],
///     [0.0, 1.0, 0.0],
///     [0.0, 1.0, 0.0]
/// ];
///
/// // Create 45-degree rotation matrix
/// let angle = PI / 4.0;
/// let cos_a = angle.cos();
/// let sin_a = angle.sin();
/// let rotation_matrix = array![
///     [cos_a, -sin_a],
///     [sin_a,  cos_a]
/// ];
///
/// let rotated = affine_transform(
///     &image,
///     &rotation_matrix,
///     None, None,
///     Some(InterpolationOrder::Linear),
///     None, None, None
/// ).expect("Operation failed");
/// ```
///
/// ## Scaling and translation combined
/// ```
/// use scirs2_core::ndarray::{Array2, array};
/// use scirs2_ndimage::interpolation::affine_transform;
///
/// let input = Array2::from_shape_fn((10, 10), |(i, j)| (i + j) as f64);
///
/// // Scale by 2x and translate by (5, 5)
/// let scale_matrix = array![
///     [2.0, 0.0],
///     [0.0, 2.0]
/// ];
/// let offset = array![5.0, 5.0];
///
/// let transformed = affine_transform(
///     &input,
///     &scale_matrix,
///     Some(&offset),
///     None, None, None, None, None
/// ).expect("Operation failed");
/// ```
///
/// ## Shearing transformation
/// ```
/// use scirs2_core::ndarray::{Array2, array};
/// use scirs2_ndimage::interpolation::affine_transform;
///
/// let squareimage = Array2::from_shape_fn((20, 20), |(i, j)| {
///     if i >= 5 && i < 15 && j >= 5 && j < 15 { 1.0 } else { 0.0 }
/// });
///
/// // Apply horizontal shear
/// let shear_matrix = array![
///     [1.0, 0.5],  // Shear factor of 0.5
///     [0.0, 1.0]
/// ];
///
/// let sheared = affine_transform(
///     &squareimage,
///     &shear_matrix,
///     None, None, None, None, None, None
/// ).expect("Operation failed");
/// ```
///
/// ## Image rectification with different output size
/// ```
/// use scirs2_core::ndarray::{Array2, array};
/// use scirs2_ndimage::interpolation::{affine_transform, BoundaryMode};
///
/// let distorted = Array2::from_shape_fn((30, 40), |(i, j)| {
///     ((i as f64 / 5.0).sin() * (j as f64 / 5.0).cos()).abs()
/// });
///
/// // Create rectification matrix (inverse of distortion)
/// let rectify_matrix = array![
///     [0.8, -0.1],
///     [0.1,  0.9]
/// ];
///
/// // Output to different size
/// let outputshape = [50, 50];
///
/// let rectified = affine_transform(
///     &distorted,
///     &rectify_matrix,
///     None,
///     Some(&outputshape),
///     None,
///     Some(BoundaryMode::Reflect),
///     None, None
/// ).expect("Operation failed");
///
/// assert_eq!(rectified.shape(), &[50, 50]);
/// ```
///
/// ## 3D volume transformation
/// ```
/// use scirs2_core::ndarray::{Array3, Array2};
/// use scirs2_ndimage::interpolation::affine_transform;
///
/// let volume = Array3::from_shape_fn((20, 20, 20), |(i, j, k)| {
///     ((i + j + k) as f64) / 60.0
/// });
///
/// // 3D rotation around z-axis
/// let rotation_3d = Array2::from_shape_fn((3, 3), |(i, j)| {
///     match (i, j) {
///         (0, 0) => 0.866, (0, 1) => -0.5, (0, 2) => 0.0,
///         (1, 0) => 0.5,   (1, 1) => 0.866, (1, 2) => 0.0,
///         (2, 0) => 0.0,   (2, 1) => 0.0,   (2, 2) => 1.0,
///         _ => 0.0
///     }
/// });
///
/// let rotated_volume = affine_transform(
///     &volume, &rotation_3d, None, None, None, None, None, None
/// ).expect("Operation failed");
///
/// assert_eq!(rotated_volume.shape(), volume.shape());
/// ```
///
/// # Performance Notes
///
/// - Use `Nearest` interpolation for best performance with discrete data
/// - `Linear` interpolation provides good quality/speed balance for most applications
/// - `Cubic` interpolation gives highest quality but is computationally expensive
/// - Prefiltering is recommended for high-order interpolation to reduce artifacts
/// - Consider using specialized functions like `rotate` or `zoom` for simple transformations
///
/// # ⚠️ Known Issues
///
/// **WARNING**: Identity transformation behavior has known discrepancies with SciPy's
/// implementation. Simple transformations may not preserve values exactly as
/// scipy.ndimage.affine_transform does. This is being tracked for v0.2.0.
/// For strict SciPy compatibility, validate results when transformation accuracy is critical.
#[allow(clippy::too_many_arguments)] // Necessary to match SciPy's API signature
#[allow(dead_code)]
pub fn affine_transform<T, D>(
    input: &Array<T, D>,
    matrix: &Array<T, scirs2_core::ndarray::Ix2>,
    offset: Option<&Array<T, scirs2_core::ndarray::Ix1>>,
    outputshape: Option<&[usize]>,
    order: Option<InterpolationOrder>,
    mode: Option<BoundaryMode>,
    cval: Option<T>,
    prefilter: Option<bool>,
) -> NdimageResult<Array<T, D>>
where
    T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + 'static,
    D: Dimension + 'static,
{
    // Validate inputs
    if input.ndim() == 0 {
        return Err(NdimageError::InvalidInput(
            "Input array cannot be 0-dimensional".into(),
        ));
    }

    if matrix.shape()[0] != input.ndim() || matrix.shape()[1] != input.ndim() {
        return Err(NdimageError::DimensionError(format!(
            "Matrix shape must be ({0}, {0}) for input array of dimension {0}, got ({1}, {2})",
            input.ndim(),
            matrix.shape()[0],
            matrix.shape()[1]
        )));
    }

    if let Some(off) = offset {
        if off.len() != input.ndim() {
            return Err(NdimageError::DimensionError(format!(
                "Offset length must match input dimensions (got {} expected {})",
                off.len(),
                input.ndim()
            )));
        }
    }

    if let Some(shape) = outputshape {
        if shape.len() != input.ndim() {
            return Err(NdimageError::DimensionError(format!(
                "Output shape length must match input dimensions (got {} expected {})",
                shape.len(),
                input.ndim()
            )));
        }
    }

    let interp_order = order.unwrap_or(InterpolationOrder::Linear);
    let boundary = mode.unwrap_or(BoundaryMode::Constant);
    let const_val = cval.unwrap_or_else(|| T::zero());
    let _prefilter_input = prefilter.unwrap_or(true);

    // Determine output shape
    let outshape = if let Some(shape) = outputshape {
        shape.to_vec()
    } else {
        input.shape().to_vec()
    };

    // Create output array
    let output = Array::zeros(scirs2_core::ndarray::IxDyn(&outshape));
    let mut result_dyn = output.into_dyn();
    let input_dyn = input.clone().into_dyn();

    // Create default offset if not provided
    let zero_offset: Array<T, scirs2_core::ndarray::Ix1> = Array::zeros(input.ndim());
    let offset_vec = offset.unwrap_or(&zero_offset);

    // For each output pixel, calculate corresponding input coordinates
    for (output_idx, output_val) in result_dyn.indexed_iter_mut() {
        // Convert output coordinates to floating point
        let output_coords: Vec<T> = output_idx
            .as_array_view()
            .iter()
            .map(|&coord| T::from_usize(coord).unwrap_or_else(|| T::zero()))
            .collect();

        // Apply affine transformation: input_coords = matrix^-1 * (output_coords - offset)
        // For now, assume the matrix is the forward transformation and we need to invert it
        // Simple approach: solve the system matrix * input_coords + offset = output_coords
        // So: input_coords = matrix^-1 * (output_coords - offset)

        let mut input_coords = vec![T::zero(); input.ndim()];

        // For 2D case, implement simple matrix inversion
        if input.ndim() == 2 {
            let det = matrix[[0, 0]] * matrix[[1, 1]] - matrix[[0, 1]] * matrix[[1, 0]];

            if det.abs() < T::from_f64(1e-10).unwrap_or_else(|| T::zero()) {
                // Singular matrix, fall back to identity
                input_coords = output_coords;
            } else {
                // Calculate adjusted output coordinates
                let adj_out_x = output_coords[0] - offset_vec[0];
                let adj_out_y = output_coords[1] - offset_vec[1];

                // Apply inverse transformation
                input_coords[0] = (matrix[[1, 1]] * adj_out_x - matrix[[0, 1]] * adj_out_y) / det;
                input_coords[1] = (-matrix[[1, 0]] * adj_out_x + matrix[[0, 0]] * adj_out_y) / det;
            }
        } else {
            // For other dimensions, use a simple approach (assuming diagonal or near-identity matrix)
            for i in 0..input.ndim() {
                let adj_coord = output_coords[i] - offset_vec[i];

                // Simple inversion for diagonal-dominant case
                if matrix[[i, i]].abs() > T::from_f64(1e-10).unwrap_or_else(|| T::zero()) {
                    input_coords[i] = adj_coord / matrix[[i, i]];
                } else {
                    input_coords[i] = adj_coord;
                }
            }
        }

        // Perform interpolation
        let interpolated_value = match interp_order {
            InterpolationOrder::Nearest => {
                interpolate_nearest(&input_dyn, &input_coords, &boundary, const_val)
            }
            InterpolationOrder::Linear => {
                interpolate_linear(&input_dyn, &input_coords, &boundary, const_val)
            }
            _ => {
                // For now, fall back to linear for unsupported orders
                interpolate_linear(&input_dyn, &input_coords, &boundary, const_val)
            }
        };

        *output_val = interpolated_value;
    }

    // Convert back to original dimensionality
    result_dyn.into_dimensionality::<D>().map_err(|_| {
        NdimageError::DimensionError("Failed to convert back to original dimensions".into())
    })
}

/// Apply a general geometric transform to an array
///
/// # Arguments
///
/// * `input` - Input array
/// * `mapping` - Function mapping output coordinates to input coordinates
/// * `outputshape` - Shape of the output array (default: same as input)
/// * `order` - Interpolation order (default: Linear)
/// * `mode` - Boundary handling mode (default: Constant)
/// * `cval` - Value to use for constant mode (default: 0.0)
/// * `prefilter` - Whether to prefilter the input with a spline filter (default: true)
///
/// # Returns
///
/// * `Result<Array<T, D>>` - Transformed array
#[allow(dead_code)]
pub fn geometric_transform<T, D, F>(
    input: &Array<T, D>,
    _mapping: F,
    outputshape: Option<&[usize]>,
    order: Option<InterpolationOrder>,
    mode: Option<BoundaryMode>,
    cval: Option<T>,
    prefilter: Option<bool>,
) -> NdimageResult<Array<T, D>>
where
    T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + 'static,
    D: Dimension + 'static,
    F: Fn(&[usize]) -> Vec<T>,
{
    // Validate inputs
    if input.ndim() == 0 {
        return Err(NdimageError::InvalidInput(
            "Input array cannot be 0-dimensional".into(),
        ));
    }

    if let Some(shape) = outputshape {
        if shape.len() != input.ndim() {
            return Err(NdimageError::DimensionError(format!(
                "Output shape length must match input dimensions (got {} expected {})",
                shape.len(),
                input.ndim()
            )));
        }
    }

    let _interp_order = order.unwrap_or(InterpolationOrder::Linear);
    let _boundary = mode.unwrap_or(BoundaryMode::Constant);
    let _const_val = cval.unwrap_or_else(|| T::zero());
    let _prefilter_input = prefilter.unwrap_or(true);

    // Placeholder implementation returning a copy of the input
    Ok(input.to_owned())
}

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

    #[test]
    fn test_affine_transform() {
        let input: Array2<f64> = Array2::eye(3);
        let matrix = Array2::<f64>::eye(2);

        let result = affine_transform(&input, &matrix, None, None, None, None, None, None)
            .expect("Operation failed");
        assert_eq!(result.shape(), input.shape());
    }

    #[test]
    fn test_geometric_transform() {
        let input: Array2<f64> = Array2::eye(3);

        // Identity mapping
        let mapping = |coords: &[usize]| -> Vec<f64> { coords.iter().map(|&x| x as f64).collect() };

        let result = geometric_transform(&input, mapping, None, None, None, None, None)
            .expect("Operation failed");
        assert_eq!(result.shape(), input.shape());
    }
}
