use crate::geometry::Point;
use std::fmt;
use serde::{Serialize, Deserialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BSpline {
    control_points: Vec<Point>,
    knots: Vec<f64>,
    degree: usize,
}

impl BSpline {
    #[inline]
    pub fn new(control_points: Vec<Point>, knots: Vec<f64>, degree: usize) -> Self {
        Self {
            control_points,
            knots,
            degree,
        }
    }

    #[inline]
    pub fn from_points(points: Vec<Point>, degree: usize) -> Self {
        let n = points.len() - 1;
        let mut knots = Vec::with_capacity(n + degree + 2);
        
        for _ in 0..=degree {
            knots.push(0.0);
        }
        for i in 1..n - degree {
            let t = i as f64 / (n - degree + 1) as f64;
            knots.push(t);
        }
        for _ in 0..=degree {
            knots.push(1.0);
        }

        Self {
            control_points: points,
            knots,
            degree,
        }
    }

    #[inline]
    pub fn control_points(&self) -> &[Point] {
        &self.control_points
    }

    #[inline]
    pub fn knots(&self) -> &[f64] {
        &self.knots
    }

    #[inline]
    pub fn degree(&self) -> usize {
        self.degree
    }

    #[inline]
    pub fn order(&self) -> usize {
        self.degree + 1
    }

    #[inline]
    pub fn is_valid(&self) -> bool {
        let n = self.control_points.len();
        if n < 2 {
            return false;
        }
        let expected_knots = n + self.degree + 1;
        self.knots.len() == expected_knots
    }

    #[inline]
    pub fn point_at(&self, t: f64) -> Point {
        self.evaluate_point(t)
    }

    #[inline]
    fn evaluate_point(&self, t: f64) -> Point {
        let t = t.clamp(0.0, 1.0);
        let basis = self.compute_basis_functions(t);
        
        let mut x = 0.0;
        let mut y = 0.0;
        let mut z = 0.0;
        
        for (i, &b) in basis.iter().enumerate() {
            if b > 0.0 {
                x += self.control_points[i].x * b;
                y += self.control_points[i].y * b;
                z += self.control_points[i].z * b;
            }
        }
        
        Point::new(x, y, z)
    }

    fn compute_basis_functions(&self, t: f64) -> Vec<f64> {
        let n = self.control_points.len();
        let mut basis = vec![0.0; n];
        
        if self.degree == 0 {
            for i in 0..n {
                if t >= self.knots[i] && t < self.knots[i + 1] {
                    basis[i] = 1.0;
                }
            }
            return basis;
        }

        let mut ndu = vec![vec![0.0; self.degree + 1]; n];
        for i in 0..n {
            ndu[i][0] = if t >= self.knots[i] && t < self.knots[i + 1] { 1.0 } else { 0.0 };
        }

        for j in 1..=self.degree {
            for i in 0..=n - j - 1 {
                let mut saved = 0.0;
                let denom1 = self.knots[i + j - 1] - self.knots[i];
                let denom2 = self.knots[i + j] - self.knots[i + 1];
                
                if denom1 != 0.0 {
                    saved = ((t - self.knots[i]) / denom1) * ndu[i][j - 1];
                }
                if denom2 != 0.0 {
                    ndu[i][j] = ((self.knots[i + j] - t) / denom2) * ndu[i + 1][j - 1] + saved;
                } else {
                    ndu[i][j] = saved;
                }
            }
        }

        for i in 0..n {
            basis[i] = ndu[i][self.degree];
        }

        basis
    }

    #[inline]
    pub fn derivative(&self, _t: f64) -> Point {
        if self.degree == 0 {
            return Point::origin();
        }

        let mut dx = 0.0;
        let mut dy = 0.0;
        let mut dz = 0.0;
        
        for i in 0..self.control_points.len() - 1 {
            let factor = self.degree as f64 / (self.knots[i + self.degree + 1] - self.knots[i + 1]);
            let point_diff = Point::new(
                self.control_points[i + 1].x - self.control_points[i].x,
                self.control_points[i + 1].y - self.control_points[i].y,
                self.control_points[i + 1].z - self.control_points[i].z,
            );
            dx += point_diff.x * factor;
            dy += point_diff.y * factor;
            dz += point_diff.z * factor;
        }
        
        Point::new(dx, dy, dz)
    }
}

impl fmt::Display for BSpline {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "BSpline(degree: {}, control_points: {}, knots: {})",
            self.degree,
            self.control_points.len(),
            self.knots.len()
        )
    }
}

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

    #[test]
    fn test_bspline_creation() {
        let points = vec![
            Point::new(0.0, 0.0, 0.0),
            Point::new(1.0, 1.0, 0.0),
            Point::new(2.0, 1.0, 0.0),
            Point::new(3.0, 0.0, 0.0),
        ];
        let spline = BSpline::from_points(points, 2);
        
        assert_eq!(spline.degree(), 2);
        assert!(spline.is_valid());
    }

    #[test]
    fn test_bspline_point_at() {
        let points = vec![
            Point::new(0.0, 0.0, 0.0),
            Point::new(1.0, 1.0, 0.0),
            Point::new(2.0, 1.0, 0.0),
            Point::new(3.0, 0.0, 0.0),
        ];
        let spline = BSpline::from_points(points, 2);
        
        let start = spline.point_at(0.0);
        assert!((start.x - 0.0).abs() < 1e-10);
        
        let end = spline.point_at(1.0);
        assert!((end.x - 3.0).abs() < 1e-10);
    }
}
