use crate::{SuperScalar, V3X, VX};
use itertools::multizip;
use na::base::storage::ContiguousStorage;
use na::{Dim, Matrix, RealField, Scalar};
use num_traits::NumCast;

/// Convert a storage matrix to a simple matrix.
pub fn slice_to_array<N, R, C, S>(slice: &Matrix<N, R, C, S>) -> V3X<N>
where
    N: SuperScalar,
    R: Dim,
    C: Dim,
    S: ContiguousStorage<N, R, C>,
{
    V3X::from_column_slice(slice.as_slice())
}

/// Create a vector from `start` to `end` with `step`. The last step might be smaller
/// than `step` just to include `end` in the vector.
pub fn linspace<T>(start: f64, end: f64, step: f64) -> VX<T>
where
    T: SuperScalar,
{
    let size = ((end - start) / step) as usize + 1;
    let mut vector = VX::<T>::from_fn(size, |i, _| NumCast::from(start + step * i as f64).unwrap());
    if vector[vector.len() - 1] < NumCast::from(end).unwrap() {
        vector = vector.push(NumCast::from(end).unwrap());
    }
    vector
}

/// Dot product vectorized between two list of vectors.
pub fn dot_vectors<T>(vector_list_1: &V3X<T>, vector_list_2: &V3X<T>) -> VX<T>
where
    T: SuperScalar,
{
    let mut dot_products = VX::<T>::zeros(vector_list_1.ncols());
    for (dot_product, vector_1, vector_2) in multizip((
        dot_products.iter_mut(),
        vector_list_1.column_iter(),
        vector_list_2.column_iter(),
    )) {
        *dot_product = vector_1.dot(&vector_2);
    }
    dot_products
}

/// Clip all elements of a vector between `min` and `max`. If `min` or `max` are `None`
/// there is no limit.
pub fn clip<T>(list: &VX<T>, min: Option<T>, max: Option<T>) -> VX<T>
where
    T: SuperScalar,
{
    let mut work_list = list.clone();
    for element in work_list.iter_mut() {
        if let Some(mini) = min {
            if *element < mini {
                *element = mini
            };
        }
        if let Some(max) = max {
            if *element > max {
                *element = max
            };
        }
    }
    work_list
}

/// Check if a vector contains a value.
#[inline]
pub fn contains<T>(vector: &VX<T>, value: T) -> bool
where
    T: Scalar,
{
    for element in vector.iter() {
        if value == *element {
            return true;
        }
    }
    false
}

/// Compute the element-wise power of a vector.
pub fn pow<T>(vector: &VX<T>, power: i32) -> VX<T>
where
    T: RealField,
{
    VX::from_iterator(vector.nrows(), vector.iter().map(|x| x.powi(power)))
}
