use crate::Shape;

/// A parallel geometry composition.
///
/// Instead of building shapes sequentially (union A then union B then
/// union C), a Branch groups independent operations that evaluate
/// simultaneously. The type system enforces that branches are truly
/// independent — each is a self-contained geometric expression that
/// shares nothing with its siblings except the evaluation point.
///
/// This maps directly to the TrigGraph's DAG structure: each branch
/// becomes an independent subtree, and the join operation (union,
/// intersection, etc.) combines results at the end.
///
/// ```ignore
/// let part = par::branch()
///     .add(sphere(2.0))
///     .add(cylinder(1.0, 5.0).translate(0.0, 0.0, 1.0))
///     .add(cube(1.5).rotate_z(45.0).translate(3.0, 0.0, 0.0))
///     .union();
/// ```
pub struct Branch {
    shapes: Vec<Shape>,
}

/// Start a parallel branch composition.
pub fn branch() -> Branch {
    Branch { shapes: Vec::new() }
}

impl Branch {
    /// Add an independent shape to this parallel group.
    pub fn add(mut self, shape: Shape) -> Self {
        self.shapes.push(shape);
        self
    }

    /// Join all branches via union (min). Additive parallel.
    pub fn union(self) -> Shape {
        Shape::union_all(self.shapes)
    }

    /// Join all branches via intersection (max). Divisive parallel.
    pub fn intersection(self) -> Shape {
        Shape::intersection_all(self.shapes)
    }

    /// Join all branches via smooth union. Additive parallel with blending.
    pub fn smooth_union(self, k: f64) -> Shape {
        let mut shapes = self.shapes;
        assert!(!shapes.is_empty());
        let mut result = shapes.remove(0);
        for s in shapes {
            result = result.smooth_union(s, k);
        }
        result
    }

    /// Number of parallel branches.
    pub fn width(&self) -> usize {
        self.shapes.len()
    }
}

/// A mapped parallel operation: apply a transform to N instances.
///
/// This is multiplicative parallelism — the same base shape evaluated
/// with different parameters. Each instance is independent.
///
/// ```ignore
/// let bolts = par::map(
///     &cylinder(0.5, 2.0),
///     (0..8).map(|i| {
///         let angle = i as f64 * 45.0;
///         move |s: Shape| s.rotate_z(angle).translate(5.0, 0.0, 0.0)
///     })
/// );
/// ```
pub fn map<F>(base: &Shape, transforms: impl IntoIterator<Item = F>) -> Shape
where
    F: FnOnce(Shape) -> Shape,
{
    let shapes: Vec<Shape> = transforms
        .into_iter()
        .map(|f| f(base.clone()))
        .collect();
    Shape::union_all(shapes)
}

/// Symmetric parallel: apply a shape and its mirror simultaneously.
///
/// Both halves evaluate in parallel, then join via union.
pub fn symmetric_x(shape: &Shape) -> Shape {
    branch()
        .add(shape.clone())
        .add(shape.clone().scale(-1.0, 1.0, 1.0))
        .union()
}

pub fn symmetric_y(shape: &Shape) -> Shape {
    branch()
        .add(shape.clone())
        .add(shape.clone().scale(1.0, -1.0, 1.0))
        .union()
}

pub fn symmetric_z(shape: &Shape) -> Shape {
    branch()
        .add(shape.clone())
        .add(shape.clone().scale(1.0, 1.0, -1.0))
        .union()
}

/// Full octant symmetry: mirror across all three planes.
/// 8 parallel evaluations.
pub fn symmetric_xyz(shape: &Shape) -> Shape {
    let mut b = branch();
    for sx in &[1.0, -1.0] {
        for sy in &[1.0, -1.0] {
            for sz in &[1.0, -1.0] {
                b = b.add(shape.clone().scale(*sx, *sy, *sz));
            }
        }
    }
    b.union()
}

/// Polar parallel: N instances around the Z axis, all independent.
pub fn polar(shape: &Shape, count: usize) -> Shape {
    let step = 360.0 / count as f64;
    map(shape, (0..count).map(move |i| {
        let angle = step * i as f64;
        move |s: Shape| s.rotate_z(angle)
    }))
}

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

    #[test]
    fn branch_union() {
        let part = branch()
            .add(sphere(1.0))
            .add(sphere(1.0).translate(3.0, 0.0, 0.0))
            .union();

        assert!(part.eval(0.0, 0.0, 0.0) < 0.0);
        assert!(part.eval(3.0, 0.0, 0.0) < 0.0);
        assert!(part.eval(1.5, 5.0, 0.0) > 0.0);
    }

    #[test]
    fn map_polar() {
        let ring = polar(&sphere(0.3).translate(2.0, 0.0, 0.0), 6);
        // Point at (2,0,0) should be inside one copy
        assert!(ring.eval(2.0, 0.0, 0.0) < 0.0);
        // Origin should be outside all copies
        assert!(ring.eval(0.0, 0.0, 0.0) > 0.0);
    }

    #[test]
    fn symmetric_x_creates_mirror() {
        let half = sphere(1.0).translate(2.0, 0.0, 0.0);
        let full = symmetric_x(&half);
        // Both sides present
        assert!(full.eval(2.0, 0.0, 0.0) < 0.0);
        assert!(full.eval(-2.0, 0.0, 0.0) < 0.0);
    }

    #[test]
    fn branch_width() {
        let b = branch()
            .add(sphere(1.0))
            .add(cube(1.0))
            .add(cylinder(1.0, 2.0));
        assert_eq!(b.width(), 3);
    }

    #[test]
    fn map_linear() {
        let row = map(&sphere(0.5), (0..4).map(|i| {
            let x = i as f64 * 2.0;
            move |s: Shape| s.translate(x, 0.0, 0.0)
        }));
        assert!(row.eval(0.0, 0.0, 0.0) < 0.0);
        assert!(row.eval(6.0, 0.0, 0.0) < 0.0);
        assert!(row.eval(1.0, 0.0, 0.0) > 0.0);
    }
}
