use crate::storage::Storage;
use crate::{ComplexField, Dim, Matrix, Scalar, SimdComplexField, SimdPartialOrd, Vector};
use num::{Signed, Zero};
use simba::simd::SimdSigned;

/// # Find the min and max components
impl<N: Scalar, R: Dim, C: Dim, S: Storage<N, R, C>> Matrix<N, R, C, S> {
    /// Returns the absolute value of the component with the largest absolute value.
    /// # Example
    /// ```
    /// # use nalgebra::Vector3;
    /// assert_eq!(Vector3::new(-1.0, 2.0, 3.0).amax(), 3.0);
    /// assert_eq!(Vector3::new(-1.0, -2.0, -3.0).amax(), 3.0);
    /// ```
    #[inline]
    pub fn amax(&self) -> N
    where
        N: Zero + SimdSigned + SimdPartialOrd,
    {
        self.fold_with(
            |e| e.unwrap_or(&N::zero()).simd_abs(),
            |a, b| a.simd_max(b.simd_abs()),
        )
    }

    /// Returns the the 1-norm of the complex component with the largest 1-norm.
    /// # Example
    /// ```
    /// # use nalgebra::{Vector3, Complex};
    /// assert_eq!(Vector3::new(
    ///     Complex::new(-3.0, -2.0),
    ///     Complex::new(1.0, 2.0),
    ///     Complex::new(1.0, 3.0)).camax(), 5.0);
    /// ```
    #[inline]
    pub fn camax(&self) -> N::SimdRealField
    where
        N: SimdComplexField,
    {
        self.fold_with(
            |e| e.unwrap_or(&N::zero()).simd_norm1(),
            |a, b| a.simd_max(b.simd_norm1()),
        )
    }

    /// Returns the component with the largest value.
    /// # Example
    /// ```
    /// # use nalgebra::Vector3;
    /// assert_eq!(Vector3::new(-1.0, 2.0, 3.0).max(), 3.0);
    /// assert_eq!(Vector3::new(-1.0, -2.0, -3.0).max(), -1.0);
    /// assert_eq!(Vector3::new(5u32, 2, 3).max(), 5);
    /// ```
    #[inline]
    pub fn max(&self) -> N
    where
        N: SimdPartialOrd + Zero,
    {
        self.fold_with(
            |e| e.map(|e| e.inlined_clone()).unwrap_or_else(N::zero),
            |a, b| a.simd_max(b.inlined_clone()),
        )
    }

    /// Returns the absolute value of the component with the smallest absolute value.
    /// # Example
    /// ```
    /// # use nalgebra::Vector3;
    /// assert_eq!(Vector3::new(-1.0, 2.0, -3.0).amin(), 1.0);
    /// assert_eq!(Vector3::new(10.0, 2.0, 30.0).amin(), 2.0);
    /// ```
    #[inline]
    pub fn amin(&self) -> N
    where
        N: Zero + SimdPartialOrd + SimdSigned,
    {
        self.fold_with(
            |e| e.map(|e| e.simd_abs()).unwrap_or_else(N::zero),
            |a, b| a.simd_min(b.simd_abs()),
        )
    }

    /// Returns the the 1-norm of the complex component with the smallest 1-norm.
    /// # Example
    /// ```
    /// # use nalgebra::{Vector3, Complex};
    /// assert_eq!(Vector3::new(
    ///     Complex::new(-3.0, -2.0),
    ///     Complex::new(1.0, 2.0),
    ///     Complex::new(1.0, 3.0)).camin(), 3.0);
    /// ```
    #[inline]
    pub fn camin(&self) -> N::SimdRealField
    where
        N: SimdComplexField,
    {
        self.fold_with(
            |e| {
                e.map(|e| e.simd_norm1())
                    .unwrap_or_else(N::SimdRealField::zero)
            },
            |a, b| a.simd_min(b.simd_norm1()),
        )
    }

    /// Returns the component with the smallest value.
    /// # Example
    /// ```
    /// # use nalgebra::Vector3;
    /// assert_eq!(Vector3::new(-1.0, 2.0, 3.0).min(), -1.0);
    /// assert_eq!(Vector3::new(1.0, 2.0, 3.0).min(), 1.0);
    /// assert_eq!(Vector3::new(5u32, 2, 3).min(), 2);
    /// ```
    #[inline]
    pub fn min(&self) -> N
    where
        N: SimdPartialOrd + Zero,
    {
        self.fold_with(
            |e| e.map(|e| e.inlined_clone()).unwrap_or_else(N::zero),
            |a, b| a.simd_min(b.inlined_clone()),
        )
    }

    /// Computes the index of the matrix component with the largest absolute value.
    ///
    /// # Examples:
    ///
    /// ```
    /// # extern crate num_complex;
    /// # extern crate nalgebra;
    /// # use num_complex::Complex;
    /// # use nalgebra::Matrix2x3;
    /// let mat = Matrix2x3::new(Complex::new(11.0, 1.0), Complex::new(-12.0, 2.0), Complex::new(13.0, 3.0),
    ///                          Complex::new(21.0, 43.0), Complex::new(22.0, 5.0), Complex::new(-23.0, 0.0));
    /// assert_eq!(mat.icamax_full(), (1, 0));
    /// ```
    #[inline]
    pub fn icamax_full(&self) -> (usize, usize)
    where
        N: ComplexField,
    {
        assert!(!self.is_empty(), "The input matrix must not be empty.");

        let mut the_max = unsafe { self.get_unchecked((0, 0)).norm1() };
        let mut the_ij = (0, 0);

        for j in 0..self.ncols() {
            for i in 0..self.nrows() {
                let val = unsafe { self.get_unchecked((i, j)).norm1() };

                if val > the_max {
                    the_max = val;
                    the_ij = (i, j);
                }
            }
        }

        the_ij
    }
}

impl<N: Scalar + PartialOrd + Signed, R: Dim, C: Dim, S: Storage<N, R, C>> Matrix<N, R, C, S> {
    /// Computes the index of the matrix component with the largest absolute value.
    ///
    /// # Examples:
    ///
    /// ```
    /// # use nalgebra::Matrix2x3;
    /// let mat = Matrix2x3::new(11, -12, 13,
    ///                          21, 22, -23);
    /// assert_eq!(mat.iamax_full(), (1, 2));
    /// ```
    #[inline]
    pub fn iamax_full(&self) -> (usize, usize) {
        assert!(!self.is_empty(), "The input matrix must not be empty.");

        let mut the_max = unsafe { self.get_unchecked((0, 0)).abs() };
        let mut the_ij = (0, 0);

        for j in 0..self.ncols() {
            for i in 0..self.nrows() {
                let val = unsafe { self.get_unchecked((i, j)).abs() };

                if val > the_max {
                    the_max = val;
                    the_ij = (i, j);
                }
            }
        }

        the_ij
    }
}

// TODO: find a way to avoid code duplication just for complex number support.
/// # Find the min and max components (vector-specific methods)
impl<N: Scalar, D: Dim, S: Storage<N, D>> Vector<N, D, S> {
    /// Computes the index of the vector component with the largest complex or real absolute value.
    ///
    /// # Examples:
    ///
    /// ```
    /// # extern crate num_complex;
    /// # extern crate nalgebra;
    /// # use num_complex::Complex;
    /// # use nalgebra::Vector3;
    /// let vec = Vector3::new(Complex::new(11.0, 3.0), Complex::new(-15.0, 0.0), Complex::new(13.0, 5.0));
    /// assert_eq!(vec.icamax(), 2);
    /// ```
    #[inline]
    pub fn icamax(&self) -> usize
    where
        N: ComplexField,
    {
        assert!(!self.is_empty(), "The input vector must not be empty.");

        let mut the_max = unsafe { self.vget_unchecked(0).norm1() };
        let mut the_i = 0;

        for i in 1..self.nrows() {
            let val = unsafe { self.vget_unchecked(i).norm1() };

            if val > the_max {
                the_max = val;
                the_i = i;
            }
        }

        the_i
    }

    /// Computes the index and value of the vector component with the largest value.
    ///
    /// # Examples:
    ///
    /// ```
    /// # use nalgebra::Vector3;
    /// let vec = Vector3::new(11, -15, 13);
    /// assert_eq!(vec.argmax(), (2, 13));
    /// ```
    #[inline]
    pub fn argmax(&self) -> (usize, N)
    where
        N: PartialOrd,
    {
        assert!(!self.is_empty(), "The input vector must not be empty.");

        let mut the_max = unsafe { self.vget_unchecked(0) };
        let mut the_i = 0;

        for i in 1..self.nrows() {
            let val = unsafe { self.vget_unchecked(i) };

            if val > the_max {
                the_max = val;
                the_i = i;
            }
        }

        (the_i, the_max.inlined_clone())
    }

    /// Computes the index of the vector component with the largest value.
    ///
    /// # Examples:
    ///
    /// ```
    /// # use nalgebra::Vector3;
    /// let vec = Vector3::new(11, -15, 13);
    /// assert_eq!(vec.imax(), 2);
    /// ```
    #[inline]
    pub fn imax(&self) -> usize
    where
        N: PartialOrd,
    {
        self.argmax().0
    }

    /// Computes the index of the vector component with the largest absolute value.
    ///
    /// # Examples:
    ///
    /// ```
    /// # use nalgebra::Vector3;
    /// let vec = Vector3::new(11, -15, 13);
    /// assert_eq!(vec.iamax(), 1);
    /// ```
    #[inline]
    pub fn iamax(&self) -> usize
    where
        N: PartialOrd + Signed,
    {
        assert!(!self.is_empty(), "The input vector must not be empty.");

        let mut the_max = unsafe { self.vget_unchecked(0).abs() };
        let mut the_i = 0;

        for i in 1..self.nrows() {
            let val = unsafe { self.vget_unchecked(i).abs() };

            if val > the_max {
                the_max = val;
                the_i = i;
            }
        }

        the_i
    }

    /// Computes the index and value of the vector component with the smallest value.
    ///
    /// # Examples:
    ///
    /// ```
    /// # use nalgebra::Vector3;
    /// let vec = Vector3::new(11, -15, 13);
    /// assert_eq!(vec.argmin(), (1, -15));
    /// ```
    #[inline]
    pub fn argmin(&self) -> (usize, N)
    where
        N: PartialOrd,
    {
        assert!(!self.is_empty(), "The input vector must not be empty.");

        let mut the_min = unsafe { self.vget_unchecked(0) };
        let mut the_i = 0;

        for i in 1..self.nrows() {
            let val = unsafe { self.vget_unchecked(i) };

            if val < the_min {
                the_min = val;
                the_i = i;
            }
        }

        (the_i, the_min.inlined_clone())
    }

    /// Computes the index of the vector component with the smallest value.
    ///
    /// # Examples:
    ///
    /// ```
    /// # use nalgebra::Vector3;
    /// let vec = Vector3::new(11, -15, 13);
    /// assert_eq!(vec.imin(), 1);
    /// ```
    #[inline]
    pub fn imin(&self) -> usize
    where
        N: PartialOrd,
    {
        self.argmin().0
    }

    /// Computes the index of the vector component with the smallest absolute value.
    ///
    /// # Examples:
    ///
    /// ```
    /// # use nalgebra::Vector3;
    /// let vec = Vector3::new(11, -15, 13);
    /// assert_eq!(vec.iamin(), 0);
    /// ```
    #[inline]
    pub fn iamin(&self) -> usize
    where
        N: PartialOrd + Signed,
    {
        assert!(!self.is_empty(), "The input vector must not be empty.");

        let mut the_min = unsafe { self.vget_unchecked(0).abs() };
        let mut the_i = 0;

        for i in 1..self.nrows() {
            let val = unsafe { self.vget_unchecked(i).abs() };

            if val < the_min {
                the_min = val;
                the_i = i;
            }
        }

        the_i
    }
}
