use bone_types::{Parameter, Point2, Tolerance, UnitVec2, Vec2};

use crate::aabb::Aabb2;
use crate::arc2::Arc2;
use crate::circle2::Circle2;
use crate::closest::ClosestPoint2;
use crate::curvature::Curvature;
use crate::line2::Line2;

pub trait Curve2: Copy {
    fn evaluate(&self, t: Parameter) -> Point2;
    fn derivative(&self, t: Parameter) -> Vec2;
    fn tangent(&self, t: Parameter) -> UnitVec2;
    fn curvature(&self, t: Parameter) -> Curvature;
    fn bounding_box(&self) -> Aabb2;
    fn closest_point(&self, p: Point2, tolerance: Tolerance) -> ClosestPoint2;
    fn as_kind(&self) -> Curve2Kind;
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Curve2Kind {
    Line(Line2),
    Arc(Arc2),
    Circle(Circle2),
}

impl Curve2 for Curve2Kind {
    fn evaluate(&self, t: Parameter) -> Point2 {
        match self {
            Self::Line(c) => c.evaluate(t),
            Self::Arc(c) => c.evaluate(t),
            Self::Circle(c) => c.evaluate(t),
        }
    }
    fn derivative(&self, t: Parameter) -> Vec2 {
        match self {
            Self::Line(c) => c.derivative(t),
            Self::Arc(c) => c.derivative(t),
            Self::Circle(c) => c.derivative(t),
        }
    }
    fn tangent(&self, t: Parameter) -> UnitVec2 {
        match self {
            Self::Line(c) => c.tangent(t),
            Self::Arc(c) => c.tangent(t),
            Self::Circle(c) => c.tangent(t),
        }
    }
    fn curvature(&self, t: Parameter) -> Curvature {
        match self {
            Self::Line(c) => c.curvature(t),
            Self::Arc(c) => c.curvature(t),
            Self::Circle(c) => c.curvature(t),
        }
    }
    fn bounding_box(&self) -> Aabb2 {
        match self {
            Self::Line(c) => c.bounding_box(),
            Self::Arc(c) => c.bounding_box(),
            Self::Circle(c) => c.bounding_box(),
        }
    }
    fn closest_point(&self, p: Point2, tolerance: Tolerance) -> ClosestPoint2 {
        match self {
            Self::Line(c) => c.closest_point(p, tolerance),
            Self::Arc(c) => c.closest_point(p, tolerance),
            Self::Circle(c) => c.closest_point(p, tolerance),
        }
    }
    fn as_kind(&self) -> Curve2Kind {
        *self
    }
}
