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

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

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

    #[inline]
    pub fn from_points(points: Vec<Point>, degree: usize) -> Self {
        let n = points.len() - 1;
        let weights = vec![1.0; points.len()];
        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,
            weights,
            knots,
            degree,
        }
    }

    #[inline]
    pub fn with_circle(center: Point, radius: f64, segments: usize) -> Self {
        let mut points = Vec::with_capacity(segments);
        let mut weights = Vec::with_capacity(segments);
        
        for i in 0..segments {
            let angle = 2.0 * std::f64::consts::PI * i as f64 / segments as f64;
            points.push(Point::new(
                center.x + radius * angle.cos(),
                center.y + radius * angle.sin(),
                center.z,
            ));
            weights.push(1.0);
        }
        
        Self::from_points(points, 2)
    }

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

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

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

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

    #[inline]
    pub fn is_rational(&self) -> bool {
        self.weights.iter().any(|&w| (w - 1.0).abs() > 1e-10)
    }

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

    fn evaluate_point(&self, t: f64) -> Point {
        let basis = self.compute_rational_basis(t);
        
        let mut wx = 0.0;
        let mut wy = 0.0;
        let mut wz = 0.0;
        let mut w = 0.0;
        
        for (i, &b) in basis.iter().enumerate() {
            if b > 0.0 {
                wx += self.control_points[i].x * self.weights[i] * b;
                wy += self.control_points[i].y * self.weights[i] * b;
                wz += self.control_points[i].z * self.weights[i] * b;
                w += self.weights[i] * b;
            }
        }
        
        if w.abs() < 1e-10 {
            Point::origin()
        } else {
            Point::new(wx / w, wy / w, wz / w)
        }
    }

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

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

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

        basis
    }
}

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

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

    #[test]
    fn test_nurbs_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 nurbs = NURBS::from_points(points, 2);
        
        assert_eq!(nurbs.degree(), 2);
    }

    #[test]
    fn test_nurbs_is_rational() {
        let points = vec![
            Point::new(0.0, 0.0, 0.0),
            Point::new(1.0, 1.0, 0.0),
        ];
        let nurbs = NURBS::from_points(points, 1);
        
        assert!(!nurbs.is_rational());
    }

    #[test]
    fn test_nurbs_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 nurbs = NURBS::from_points(points, 2);
        
        let start = nurbs.point_at(0.0);
        assert!((start.x - 0.0).abs() < 1e-10);
    }
}
