use std::ops::{Add, Div, Mul, Sub};

//
// 1) Define a float series: FSeries
//

#[derive(Debug, Clone)]
pub struct FSeries {
    data: Vec<f64>,
}

impl FSeries {
    /// Create a new FSeries from a vector of f64 values.
    pub fn new(data: Vec<f64>) -> Self {
        Self { data }
    }

    pub fn len(&self) -> usize {
        self.data.len()
    }

    /// Element‑wise helper applying an operation between two FSeries.
    pub fn apply<F>(&self, other: &Self, op: F) -> Self
    where
        F: Fn(f64, f64) -> f64,
    {
        assert!(
            self.len() == other.len(),
            "FSeries must have the same length to apply operations."
        );
        let data = self
            .data
            .iter()
            .zip(other.data.iter())
            .map(|(&a, &b)| op(a, b))
            .collect();
        FSeries { data }
    }

    /// Access to the underlying data
    pub fn data(&self) -> &[f64] {
        &self.data
    }
}

// Macros for float series arithmetic (element‑wise)
macro_rules! impl_fseries_bin_op {
    ($trait:ident, $method:ident, $op:tt) => {
        impl $trait for FSeries {
            type Output = Self;

            fn $method(self, rhs: Self) -> Self::Output {
                self.apply(&rhs, |a, b| a $op b)
            }
        }
    };
}

impl_fseries_bin_op!(Add, add, +);
impl_fseries_bin_op!(Sub, sub, -);
impl_fseries_bin_op!(Mul, mul, *);
impl_fseries_bin_op!(Div, div, /);

macro_rules! impl_fseries_scalar_op {
    ($trait:ident, $method:ident, $op:tt) => {
        impl $trait<f64> for FSeries {
            type Output = Self;

            fn $method(mut self, scalar: f64) -> Self::Output {
                for x in self.data.iter_mut() {
                    *x = *x $op scalar;
                }
                self
            }
        }
    };
}

impl_fseries_scalar_op!(Add, add, +);
impl_fseries_scalar_op!(Sub, sub, -);
impl_fseries_scalar_op!(Mul, mul, *);
impl_fseries_scalar_op!(Div, div, /);

//
// 2) Define an integer series: ISeries
//

#[derive(Debug, Clone)]
pub struct ISeries {
    data: Vec<i64>,
}

impl ISeries {
    /// Create an ISeries from a vector of i64 values.
    pub fn new(data: Vec<i64>) -> Self {
        Self { data }
    }

    pub fn len(&self) -> usize {
        self.data.len()
    }

    pub fn data(&self) -> &[i64] {
        &self.data
    }

    /// Element‑wise helper for integer series.
    pub fn apply<F>(&self, other: &Self, op: F) -> Self
    where
        F: Fn(i64, i64) -> i64,
    {
        assert!(
            self.len() == other.len(),
            "ISeries must have the same length to apply operations."
        );
        let data = self
            .data
            .iter()
            .zip(other.data.iter())
            .map(|(&a, &b)| op(a, b))
            .collect();
        ISeries { data }
    }
}

// Macros for integer series arithmetic (element‑wise)
macro_rules! impl_iseries_bin_op {
    ($trait:ident, $method:ident, $op:tt) => {
        impl $trait for ISeries {
            type Output = Self;

            fn $method(self, rhs: Self) -> Self::Output {
                self.apply(&rhs, |a, b| a $op b)
            }
        }
    };
}

impl_iseries_bin_op!(Add, add, +);
impl_iseries_bin_op!(Sub, sub, -);
impl_iseries_bin_op!(Mul, mul, *);
impl_iseries_bin_op!(Div, div, /); // integer division (floor trunc)

// Optional scalar operations (for i64)
macro_rules! impl_iseries_scalar_op {
    ($trait:ident, $method:ident, $op:tt) => {
        impl $trait<i64> for ISeries {
            type Output = Self;

            fn $method(mut self, scalar: i64) -> Self::Output {
                for x in self.data.iter_mut() {
                    *x = *x $op scalar;
                }
                self
            }
        }
    };
}

impl_iseries_scalar_op!(Add, add, +);
impl_iseries_scalar_op!(Sub, sub, -);
impl_iseries_scalar_op!(Mul, mul, *);
impl_iseries_scalar_op!(Div, div, /); // floor/trunc division by scalar

/// A boolean series: BSeries
///
#[derive(Debug, Clone)]
pub struct BSeries {
    data: Vec<bool>,
}

impl BSeries {
    pub fn new(data: Vec<bool>) -> Self {
        Self { data }
    }

    pub fn len(&self) -> usize {
        self.data.len()
    }

    pub fn data(&self) -> &[bool] {
        &self.data
    }
}

/// Convert an FSeries to ISeries by truncation (floor cast).
impl From<FSeries> for ISeries {
    fn from(fseries: FSeries) -> Self {
        let data = fseries
            .data
            .into_iter()
            .map(|val| val as i64) // trunc cast
            .collect();
        ISeries::new(data)
    }
}

/// Implement conversion from ISeries to FSeries by casting to f64.
impl From<ISeries> for FSeries {
    fn from(iseries: ISeries) -> Self {
        let data = iseries.data.into_iter().map(|val| val as f64).collect();
        FSeries::new(data)
    }
}

/// Convert an ISeries to BSeries by checking if each value is non-zero.
impl From<ISeries> for BSeries {
    fn from(iseries: ISeries) -> Self {
        let data = iseries.data.into_iter().map(|val| val != 0).collect();
        BSeries::new(data)
    }
}

impl From<BSeries> for ISeries {
    fn from(bseries: BSeries) -> Self {
        let data = bseries
            .data
            .into_iter()
            .map(|val| if val { 1 } else { 0 })
            .collect();
        ISeries::new(data)
    }
}
