use bone_types::{Angle, Length, Point2, SketchEntityId, millimeter, radian};

use super::{DimensionValue, Sketch, SketchDimension, SketchEntity};

impl Sketch {
    #[must_use]
    pub fn measure(&self, dim: SketchDimension) -> Option<DimensionValue> {
        measure_dimension(self, dim)
    }
}

fn measure_dimension(sketch: &Sketch, dim: SketchDimension) -> Option<DimensionValue> {
    match dim {
        SketchDimension::Linear { a, b, .. } => {
            let pa = point_position(sketch, a)?;
            let pb = point_position(sketch, b)?;
            let (ax, ay) = pa.coords_mm();
            let (bx, by) = pb.coords_mm();
            let dx = bx - ax;
            let dy = by - ay;
            Some(DimensionValue::Length(Length::new::<millimeter>(
                (dx * dx + dy * dy).sqrt(),
            )))
        }
        SketchDimension::Radius { target, .. } => measure_radius(sketch, target)
            .map(|r| DimensionValue::Length(Length::new::<millimeter>(r))),
        SketchDimension::Diameter { target, .. } => measure_radius(sketch, target)
            .map(|r| DimensionValue::Length(Length::new::<millimeter>(2.0 * r))),
        SketchDimension::Angular { a, b, .. } => {
            measure_angle(sketch, a, b).map(|r| DimensionValue::Angle(Angle::new::<radian>(r)))
        }
    }
}

fn point_position(sketch: &Sketch, id: SketchEntityId) -> Option<Point2> {
    match sketch.entities().get(id)? {
        SketchEntity::Point(p) => Some(p.at()),
        _ => None,
    }
}

fn measure_radius(sketch: &Sketch, id: SketchEntityId) -> Option<f64> {
    match sketch.entities().get(id)? {
        SketchEntity::Circle(c) => Some(c.radius().get::<millimeter>()),
        SketchEntity::Arc(a) => {
            let center = point_position(sketch, a.center())?;
            let start = point_position(sketch, a.start())?;
            let (cx, cy) = center.coords_mm();
            let (sx, sy) = start.coords_mm();
            let dx = sx - cx;
            let dy = sy - cy;
            Some((dx * dx + dy * dy).sqrt())
        }
        _ => None,
    }
}

fn measure_angle(sketch: &Sketch, a: SketchEntityId, b: SketchEntityId) -> Option<f64> {
    let la = line_endpoints(sketch, a)?;
    let lb = line_endpoints(sketch, b)?;
    let (d1x, d1y) = (la.1.0 - la.0.0, la.1.1 - la.0.1);
    let (d2x, d2y) = (lb.1.0 - lb.0.0, lb.1.1 - lb.0.1);
    let mag = ((d1x * d1x + d1y * d1y) * (d2x * d2x + d2y * d2y)).sqrt();
    if mag <= 0.0 {
        return None;
    }
    let cos = (d1x * d2x + d1y * d2y) / mag;
    Some(cos.abs().clamp(0.0, 1.0).acos())
}

fn line_endpoints(sketch: &Sketch, id: SketchEntityId) -> Option<((f64, f64), (f64, f64))> {
    match sketch.entities().get(id)? {
        SketchEntity::Line(l) => {
            let a = point_position(sketch, l.a())?.coords_mm();
            let b = point_position(sketch, l.b())?.coords_mm();
            Some((a, b))
        }
        _ => None,
    }
}
