use num_traits::{Float, FromPrimitive, One, Zero};

use crate::smooth::{factory::DefaultSmoothFactory, SmoothError};

pub enum SmoothType {
    SMA,
    EMA,
    RMA,
    MEDIAN
}

pub trait SmoothApply<T> where
    T: Float + FromPrimitive + Zero + Clone + One + 'static,
{
    fn apply(&self, data: &Vec<T>, window_size: usize, precision: Option<f64>) -> Result<Vec<T>, SmoothError>;
    fn apply_inplase(&self, data: &mut Vec<T>, window_size: usize, precision: Option<f64>) -> Result<(), SmoothError>;
}

pub trait SmoothFactory<T>
where
    T: Float + FromPrimitive + Clone + Zero + One + 'static,
{
    fn create(&self, smooth_type: SmoothType) -> Box<dyn SmoothApply<T>>;
}

pub fn smooth_factory<T>() -> Box<dyn SmoothFactory<T>>
where
    T: Float + FromPrimitive + Clone + Zero + One + 'static,
{
    Box::new(DefaultSmoothFactory)
}

pub fn sma<T: Float + FromPrimitive + Clone + Zero + One + 'static>(
    data: &Vec<T>,
    window_size: usize
) -> Result<Vec<T>, SmoothError> {
    let smooth = smooth_factory::<T>().create(SmoothType::SMA);
    smooth.apply(data, window_size, None)
}
pub fn sma_inplase<T: Float + FromPrimitive + Clone + Zero + One + 'static>(
    data: &mut Vec<T>,
    window_size: usize
) -> Result<(), SmoothError> {
    let smooth = smooth_factory::<T>().create(SmoothType::SMA);
    smooth.apply_inplase(data, window_size, None)
}

pub fn ema<T: Float + FromPrimitive + Clone + Zero + One + 'static>(
    data: &Vec<T>,
    window_size: usize
) -> Result<Vec<T>, SmoothError> {
    let smooth = smooth_factory::<T>().create(SmoothType::EMA);
    smooth.apply(data, window_size, None)
}
pub fn rma<T: Float + FromPrimitive + Clone + Zero + One + 'static>(
    data: &Vec<T>,
    window_size: usize
) -> Result<Vec<T>, SmoothError> {
    let smooth = smooth_factory::<T>().create(SmoothType::RMA);
    smooth.apply(data, window_size, None)
}
pub fn median<T: Float + FromPrimitive + Clone + Zero + One + 'static>(
    data: &Vec<T>,
    window_size: usize,
    precision: f64
) -> Result<Vec<T>, SmoothError> {
    let smooth = smooth_factory::<T>().create(SmoothType::MEDIAN);
    smooth.apply(data, window_size, Some(precision))
}