use crate::core::geometry::{Bounds3f, Lerp, Point3f, Vector3f, VectorLike};
use crate::core::pbrt::Float;
use crate::utils::gpu_array_from_fn;
use crate::utils::math::lerp;
use core::ops::Sub;
use num_traits::Num;

fn bounds_cubic_bezier(cp: &[Point3f]) -> Bounds3f {
    Bounds3f::from_points(cp[0], cp[1]).union(Bounds3f::from_points(cp[2], cp[3]))
}

pub fn bound_cubic_bezier(cp: &[Point3f], u_min: Float, u_max: Float) -> Bounds3f {
    if u_min == 0. && u_max == 1. {
        return bounds_cubic_bezier(cp);
    }
    let cp_seg = cubic_bezier_control_points(cp, u_min, u_max);
    bounds_cubic_bezier(&cp_seg)
}

pub fn cubic_bezier_control_points(cp: &[Point3f], u_min: Float, u_max: Float) -> [Point3f; 4] {
    [
        blossom_cubic_bezier(cp, u_min, u_max, u_min),
        blossom_cubic_bezier(cp, u_min, u_min, u_max),
        blossom_cubic_bezier(cp, u_min, u_max, u_max),
        blossom_cubic_bezier(cp, u_max, u_max, u_max),
    ]
}

fn blossom_cubic_bezier<P, F>(p: &[P], u0: F, u1: F, u2: F) -> P
where
    F: Copy + Num,
    P: Lerp<F>,
{
    let a: [P; 3] = [
        lerp(u0, p[0], p[1]),
        lerp(u0, p[1], p[2]),
        lerp(u0, p[2], p[3]),
    ];
    let b: [P; 2] = [lerp(u1, a[0], a[1]), lerp(u1, a[1], a[2])];
    lerp(u2, b[0], b[1])
}

pub fn subdivide_cubic_bezier(cp: &[Point3f]) -> [Point3f; 7] {
    let v: [Vector3f; 4] = gpu_array_from_fn(|i| Vector3f::from(cp[i]));
    let v01 = (v[0] + v[1]) / 2.0;
    let v12 = (v[1] + v[2]) / 2.0;
    let v23 = (v[2] + v[3]) / 2.0;

    let v012 = (v01 + v12) / 2.0;
    let v123 = (v12 + v23) / 2.0;

    let v0123 = (v012 + v123) / 2.0;
    [
        cp[0],
        v01.into(),
        v012.into(),
        v0123.into(),
        v123.into(),
        v23.into(),
        cp[3],
    ]
}

pub fn elevate_quadratic_bezier_to_cubic(cp: &[Point3f]) -> [Point3f; 4] {
    [
        cp[0],
        lerp(2. / 3., cp[0], cp[1]),
        lerp(1. / 3., cp[1], cp[2]),
        cp[2],
    ]
}

pub fn cubic_bspline_to_bezier(cp: &[Point3f]) -> [Point3f; 4] {
    // Blossom from p012, p123, p234, and p345 to the Bezier control points
    // p222, p223, p233, and p333.
    // https://people.eecs.berkeley.edu/~sequin/CS284/IMGS/cubicbsplinepoints.gif
    let p012 = cp[0];
    let p123 = cp[1];
    let p234 = cp[2];
    let p345 = cp[3];

    let p122 = lerp(2. / 3., p012, p123);
    let p223 = lerp(1. / 3., p123, p234);
    let p233 = lerp(2. / 3., p123, p234);
    let p334 = lerp(1. / 3., p234, p345);

    let p222 = lerp(0.5, p122, p223);
    let p333 = lerp(0.5, p233, p334);

    [p222, p223, p233, p333]
}

pub fn quadratic_bspline_to_bezier(cp: &[Point3f]) -> [Point3f; 3] {
    // We can compute equivalent Bezier control points via some blossoming.
    // We have three control points and a uniform knot vector; we will label
    // the points p01, p12, and p23.  We want the Bezier control points of
    // the equivalent curve, which are p11, p12, and p22.  We already have
    // p12.
    let p11 = lerp(0.5, cp[0], cp[1]);
    let p22 = lerp(0.5, cp[1], cp[2]);
    [p11, cp[1], p22]
}

pub fn evaluate_cubic_bezier(cp: &[Point3f], u: Float) -> (Point3f, Vector3f)
where
{
    let cp1 = [
        lerp(u, cp[0], cp[1]),
        lerp(u, cp[1], cp[2]),
        lerp(u, cp[2], cp[3]),
    ];
    let cp2 = [lerp(u, cp1[0], cp1[1]), lerp(u, cp1[1], cp1[2])];
    let deriv = if (cp2[1] - cp2[0]).norm_squared() > 0. {
        (cp2[1] - cp2[0]) * 3.
    } else {
        cp[3] - cp[0]
    };

    (lerp(u, cp2[0], cp2[1]), deriv)
}
