use crate::math::calc_triangle_surface_normal;
use crate::mesh::Mesh;
use crate::util::builder;
use crate::vector::Vec3;
use crate::vertex::{Builder as VertexBuilder, Vertex};

builder! {
/// Cube mesh creation specification.
#[builder(name = CreationSpecBuilder, derives = (Debug, Default))]
#[derive(Debug, Default)]
#[non_exhaustive]
pub struct CreationSpec
{
    pub width: f32,
    pub height: f32,
    pub depth: f32,
}
}

impl CreationSpec
{
    /// Returns a new `CreationSpec` builder.
    #[must_use]
    pub fn builder() -> CreationSpecBuilder
    {
        CreationSpecBuilder::default()
    }
}

#[derive(Debug)]
pub enum Side
{
    Front,
    Back,
    Left,
    Right,
    Top,
    Bottom,
}

#[derive(Debug)]
pub enum Corner
{
    TopRight,
    TopLeft,
    BottomRight,
    BottomLeft,
}

/// Creates a cube mesh.
pub fn create(
    creation_spec: CreationSpec,
    vertex_builder_cb: impl Fn(VertexBuilder, Side, Corner) -> VertexBuilder,
) -> Mesh
{
    let mut vertices: [Option<Vertex>; VertexIndex::VARIANT_CNT] =
        [(); VertexIndex::VARIANT_CNT].map(|()| None);

    create_front(&creation_spec, &mut vertices, &vertex_builder_cb);
    create_back(&creation_spec, &mut vertices, &vertex_builder_cb);
    create_right(&creation_spec, &mut vertices, &vertex_builder_cb);
    create_left(&creation_spec, &mut vertices, &vertex_builder_cb);
    create_top(&creation_spec, &mut vertices, &vertex_builder_cb);
    create_bottom(&creation_spec, &mut vertices, &vertex_builder_cb);

    let front = [
        // 🮝
        VertexIndex::FrontTopRight,
        VertexIndex::FrontBottomRight,
        VertexIndex::FrontTopLeft,
        //
        // 🮟
        VertexIndex::FrontBottomRight,
        VertexIndex::FrontBottomLeft,
        VertexIndex::FrontTopLeft,
    ];

    let back = [
        // 🮝
        VertexIndex::BackTopRight,
        VertexIndex::BackBottomRight,
        VertexIndex::BackTopLeft,
        //
        // 🮟
        VertexIndex::BackBottomRight,
        VertexIndex::BackBottomLeft,
        VertexIndex::BackTopLeft,
    ];

    let right = [
        // 🮝
        VertexIndex::RightBackTop,
        VertexIndex::RightBackBottom,
        VertexIndex::RightFrontTop,
        //
        // 🮟
        VertexIndex::RightBackBottom,
        VertexIndex::RightFrontBottom,
        VertexIndex::RightFrontTop,
    ];

    let left = [
        // 🮝
        VertexIndex::LeftBackTop,
        VertexIndex::LeftBackBottom,
        VertexIndex::LeftFrontTop,
        //
        // 🮟
        VertexIndex::LeftBackBottom,
        VertexIndex::LeftFrontBottom,
        VertexIndex::LeftFrontTop,
    ];

    let top = [
        // 🮝
        VertexIndex::TopBackRight,
        VertexIndex::TopBackLeft,
        VertexIndex::TopFrontRight,
        //
        // 🮟
        VertexIndex::TopBackLeft,
        VertexIndex::TopFrontLeft,
        VertexIndex::TopFrontRight,
    ];

    let bottom = [
        // 🮝
        VertexIndex::BottomBackRight,
        VertexIndex::BottomBackLeft,
        VertexIndex::BottomFrontRight,
        //
        // 🮟
        VertexIndex::BottomBackLeft,
        VertexIndex::BottomFrontLeft,
        VertexIndex::BottomFrontRight,
    ];

    let indices = [front, back, right, left, top, bottom];

    Mesh::new(
        vertices.map(Option::unwrap).to_vec(),
        Some(
            indices
                .into_iter()
                .flatten()
                .map(|index| index as u32)
                .collect(),
        ),
    )
}

macro_rules! one {
    ($tt: tt) => {
        1
    };
}

macro_rules! enum_with_variant_cnt {
    (
        $(#[$attr: meta])*
        enum $name: ident {
            $($variant: ident,)*
        }
    ) => {
        $(#[$attr])*
        enum $name {
            $($variant,)*
        }

        impl $name {
            const VARIANT_CNT: usize = 0 $(+ one!($variant))*;
        }
    };
}

enum_with_variant_cnt! {
#[repr(u32)]
enum VertexIndex
{
    FrontTopRight,
    FrontBottomRight,
    FrontBottomLeft,
    FrontTopLeft,

    BackTopRight,
    BackBottomRight,
    BackBottomLeft,
    BackTopLeft,

    RightBackTop,
    RightBackBottom,
    RightFrontTop,
    RightFrontBottom,

    LeftBackTop,
    LeftBackBottom,
    LeftFrontTop,
    LeftFrontBottom,

    TopBackRight,
    TopBackLeft,
    TopFrontRight,
    TopFrontLeft,

    BottomBackRight,
    BottomBackLeft,
    BottomFrontRight,
    BottomFrontLeft,
}
}

fn create_front(
    creation_spec: &CreationSpec,
    vertices: &mut [Option<Vertex>],
    vertex_builder_cb: &impl Fn(VertexBuilder, Side, Corner) -> VertexBuilder,
)
{
    let front_top_right_pos = Vec3 {
        x: creation_spec.width / 2.0,
        y: creation_spec.height / 2.0,
        z: -(creation_spec.depth / 2.0),
    };

    let front_bottom_right_pos = Vec3 {
        x: creation_spec.width / 2.0,
        y: -(creation_spec.height / 2.0),
        z: -(creation_spec.depth / 2.0),
    };

    let front_bottom_left_pos = Vec3 {
        x: -(creation_spec.width / 2.0),
        y: -(creation_spec.height / 2.0),
        z: -(creation_spec.depth / 2.0),
    };

    let front_top_left_pos = Vec3 {
        x: -(creation_spec.width / 2.0),
        y: creation_spec.height / 2.0,
        z: -(creation_spec.depth / 2.0),
    };

    let front_normal = calc_triangle_surface_normal(
        &front_top_right_pos,
        &front_bottom_right_pos,
        &front_top_left_pos,
    );

    vertices[VertexIndex::FrontTopRight as usize] = Some(
        vertex_builder_cb(
            VertexBuilder::default()
                .pos(front_top_right_pos)
                .normal(front_normal),
            Side::Front,
            Corner::TopRight,
        )
        .build(),
    );

    vertices[VertexIndex::FrontBottomRight as usize] = Some(
        vertex_builder_cb(
            VertexBuilder::default()
                .pos(front_bottom_right_pos)
                .normal(front_normal),
            Side::Front,
            Corner::BottomRight,
        )
        .build(),
    );

    vertices[VertexIndex::FrontBottomLeft as usize] = Some(
        vertex_builder_cb(
            VertexBuilder::default()
                .pos(front_bottom_left_pos)
                .normal(front_normal),
            Side::Front,
            Corner::BottomLeft,
        )
        .build(),
    );

    vertices[VertexIndex::FrontTopLeft as usize] = Some(
        vertex_builder_cb(
            VertexBuilder::default()
                .pos(front_top_left_pos)
                .normal(front_normal),
            Side::Front,
            Corner::TopLeft,
        )
        .build(),
    );
}

fn create_back(
    creation_spec: &CreationSpec,
    vertices: &mut [Option<Vertex>],
    vertex_builder_cb: &impl Fn(VertexBuilder, Side, Corner) -> VertexBuilder,
)
{
    let back_top_right_pos = Vec3 {
        x: creation_spec.width / 2.0,
        y: creation_spec.height / 2.0,
        z: creation_spec.depth / 2.0,
    };

    let back_bottom_right_pos = Vec3 {
        x: creation_spec.width / 2.0,
        y: -(creation_spec.height / 2.0),
        z: creation_spec.depth / 2.0,
    };

    let back_bottom_left_pos = Vec3 {
        x: -(creation_spec.width / 2.0),
        y: -(creation_spec.height / 2.0),
        z: creation_spec.depth / 2.0,
    };

    let back_top_left_pos = Vec3 {
        x: -(creation_spec.width / 2.0),
        y: creation_spec.height / 2.0,
        z: creation_spec.depth / 2.0,
    };

    let back_normal = -calc_triangle_surface_normal(
        &back_top_right_pos,
        &back_bottom_right_pos,
        &back_top_left_pos,
    );

    vertices[VertexIndex::BackTopRight as usize] = Some(
        vertex_builder_cb(
            VertexBuilder::default()
                .pos(back_top_right_pos)
                .normal(back_normal),
            Side::Back,
            Corner::TopRight,
        )
        .build(),
    );

    vertices[VertexIndex::BackBottomRight as usize] = Some(
        vertex_builder_cb(
            VertexBuilder::default()
                .pos(back_bottom_right_pos)
                .normal(back_normal),
            Side::Back,
            Corner::BottomRight,
        )
        .build(),
    );

    vertices[VertexIndex::BackBottomLeft as usize] = Some(
        vertex_builder_cb(
            VertexBuilder::default()
                .pos(back_bottom_left_pos)
                .normal(back_normal),
            Side::Back,
            Corner::BottomLeft,
        )
        .build(),
    );

    vertices[VertexIndex::BackTopLeft as usize] = Some(
        vertex_builder_cb(
            VertexBuilder::default()
                .pos(back_top_left_pos)
                .normal(back_normal),
            Side::Back,
            Corner::TopLeft,
        )
        .build(),
    );
}

fn create_right(
    creation_spec: &CreationSpec,
    vertices: &mut [Option<Vertex>],
    vertex_builder_cb: &impl Fn(VertexBuilder, Side, Corner) -> VertexBuilder,
)
{
    let right_back_top_pos = Vec3 {
        x: creation_spec.width / 2.0,
        y: creation_spec.height / 2.0,
        z: creation_spec.depth / 2.0,
    };

    let right_back_bottom_pos = Vec3 {
        x: creation_spec.width / 2.0,
        y: -(creation_spec.height / 2.0),
        z: creation_spec.depth / 2.0,
    };

    let right_front_top_pos = Vec3 {
        x: creation_spec.width / 2.0,
        y: creation_spec.height / 2.0,
        z: -(creation_spec.depth / 2.0),
    };

    let right_front_bottom_pos = Vec3 {
        x: creation_spec.width / 2.0,
        y: -(creation_spec.height / 2.0),
        z: -(creation_spec.depth / 2.0),
    };

    let right_normal = calc_triangle_surface_normal(
        &right_back_top_pos,
        &right_back_bottom_pos,
        &right_front_top_pos,
    );

    vertices[VertexIndex::RightBackTop as usize] = Some(
        vertex_builder_cb(
            VertexBuilder::default()
                .pos(right_back_top_pos)
                .normal(right_normal),
            Side::Right,
            Corner::TopLeft,
        )
        .build(),
    );

    vertices[VertexIndex::RightBackBottom as usize] = Some(
        vertex_builder_cb(
            VertexBuilder::default()
                .pos(right_back_bottom_pos)
                .normal(right_normal),
            Side::Right,
            Corner::BottomLeft,
        )
        .build(),
    );

    vertices[VertexIndex::RightFrontTop as usize] = Some(
        vertex_builder_cb(
            VertexBuilder::default()
                .pos(right_front_top_pos)
                .normal(right_normal),
            Side::Right,
            Corner::TopRight,
        )
        .build(),
    );

    vertices[VertexIndex::RightFrontBottom as usize] = Some(
        vertex_builder_cb(
            VertexBuilder::default()
                .pos(right_front_bottom_pos)
                .normal(right_normal),
            Side::Right,
            Corner::BottomRight,
        )
        .build(),
    );
}

fn create_left(
    creation_spec: &CreationSpec,
    vertices: &mut [Option<Vertex>],
    vertex_builder_cb: &impl Fn(VertexBuilder, Side, Corner) -> VertexBuilder,
)
{
    let left_back_top_pos = Vec3 {
        x: -(creation_spec.width / 2.0),
        y: creation_spec.height / 2.0,
        z: creation_spec.depth / 2.0,
    };

    let left_back_bottom_pos = Vec3 {
        x: -(creation_spec.width / 2.0),
        y: -(creation_spec.height / 2.0),
        z: creation_spec.depth / 2.0,
    };

    let left_front_top_pos = Vec3 {
        x: -(creation_spec.width / 2.0),
        y: creation_spec.height / 2.0,
        z: -(creation_spec.depth / 2.0),
    };

    let left_front_bottom_pos = Vec3 {
        x: -(creation_spec.width / 2.0),
        y: -(creation_spec.height / 2.0),
        z: -(creation_spec.depth / 2.0),
    };

    let left_normal = -calc_triangle_surface_normal(
        &left_back_top_pos,
        &left_back_bottom_pos,
        &left_front_top_pos,
    );

    vertices[VertexIndex::LeftBackTop as usize] = Some(
        vertex_builder_cb(
            VertexBuilder::default()
                .pos(left_back_top_pos)
                .normal(left_normal),
            Side::Left,
            Corner::TopRight,
        )
        .build(),
    );

    vertices[VertexIndex::LeftBackBottom as usize] = Some(
        vertex_builder_cb(
            VertexBuilder::default()
                .pos(left_back_bottom_pos)
                .normal(left_normal),
            Side::Left,
            Corner::BottomRight,
        )
        .build(),
    );

    vertices[VertexIndex::LeftFrontTop as usize] = Some(
        vertex_builder_cb(
            VertexBuilder::default()
                .pos(left_front_top_pos)
                .normal(left_normal),
            Side::Left,
            Corner::TopLeft,
        )
        .build(),
    );

    vertices[VertexIndex::LeftFrontBottom as usize] = Some(
        vertex_builder_cb(
            VertexBuilder::default()
                .pos(left_front_bottom_pos)
                .normal(left_normal),
            Side::Left,
            Corner::BottomLeft,
        )
        .build(),
    );
}

fn create_top(
    creation_spec: &CreationSpec,
    vertices: &mut [Option<Vertex>],
    vertex_builder_cb: &impl Fn(VertexBuilder, Side, Corner) -> VertexBuilder,
)
{
    let top_back_right_pos = Vec3 {
        x: creation_spec.width / 2.0,
        y: creation_spec.height / 2.0,
        z: creation_spec.depth / 2.0,
    };

    let top_back_left_pos = Vec3 {
        x: -(creation_spec.width / 2.0),
        y: creation_spec.height / 2.0,
        z: creation_spec.depth / 2.0,
    };

    let top_front_left_pos = Vec3 {
        x: -(creation_spec.width / 2.0),
        y: creation_spec.height / 2.0,
        z: -(creation_spec.depth / 2.0),
    };

    let top_front_right_pos = Vec3 {
        x: creation_spec.width / 2.0,
        y: creation_spec.height / 2.0,
        z: -(creation_spec.depth / 2.0),
    };

    let top_normal = -calc_triangle_surface_normal(
        &top_back_right_pos,
        &top_back_left_pos,
        &top_front_right_pos,
    );

    vertices[VertexIndex::TopBackRight as usize] = Some(
        vertex_builder_cb(
            VertexBuilder::default()
                .pos(top_back_right_pos)
                .normal(top_normal),
            Side::Top,
            Corner::TopRight,
        )
        .build(),
    );

    vertices[VertexIndex::TopBackLeft as usize] = Some(
        vertex_builder_cb(
            VertexBuilder::default()
                .pos(top_back_left_pos)
                .normal(top_normal),
            Side::Top,
            Corner::TopLeft,
        )
        .build(),
    );

    vertices[VertexIndex::TopFrontLeft as usize] = Some(
        vertex_builder_cb(
            VertexBuilder::default()
                .pos(top_front_left_pos)
                .normal(top_normal),
            Side::Top,
            Corner::BottomLeft,
        )
        .build(),
    );

    vertices[VertexIndex::TopFrontRight as usize] = Some(
        vertex_builder_cb(
            VertexBuilder::default()
                .pos(top_front_right_pos)
                .normal(top_normal),
            Side::Top,
            Corner::BottomRight,
        )
        .build(),
    );
}

fn create_bottom(
    creation_spec: &CreationSpec,
    vertices: &mut [Option<Vertex>],
    vertex_builder_cb: &impl Fn(VertexBuilder, Side, Corner) -> VertexBuilder,
)
{
    let bottom_back_right_pos = Vec3 {
        x: creation_spec.width / 2.0,
        y: -(creation_spec.height / 2.0),
        z: (creation_spec.depth / 2.0),
    };

    let bottom_back_left_pos = Vec3 {
        x: -(creation_spec.width / 2.0),
        y: -(creation_spec.height / 2.0),
        z: creation_spec.depth / 2.0,
    };

    let bottom_front_right_pos = Vec3 {
        x: creation_spec.width / 2.0,
        y: -(creation_spec.height / 2.0),
        z: -(creation_spec.depth / 2.0),
    };

    let bottom_front_left_pos = Vec3 {
        x: -(creation_spec.width / 2.0),
        y: -(creation_spec.height / 2.0),
        z: -(creation_spec.depth / 2.0),
    };

    let bottom_normal = calc_triangle_surface_normal(
        &bottom_back_right_pos,
        &bottom_back_left_pos,
        &bottom_front_right_pos,
    );

    vertices[VertexIndex::BottomBackRight as usize] = Some(
        vertex_builder_cb(
            VertexBuilder::default()
                .pos(bottom_back_right_pos)
                .normal(bottom_normal),
            Side::Bottom,
            Corner::BottomRight,
        )
        .build(),
    );

    vertices[VertexIndex::BottomBackLeft as usize] = Some(
        vertex_builder_cb(
            VertexBuilder::default()
                .pos(bottom_back_left_pos)
                .normal(bottom_normal),
            Side::Bottom,
            Corner::BottomLeft,
        )
        .build(),
    );

    vertices[VertexIndex::BottomFrontRight as usize] = Some(
        vertex_builder_cb(
            VertexBuilder::default()
                .pos(bottom_front_right_pos)
                .normal(bottom_normal),
            Side::Bottom,
            Corner::TopRight,
        )
        .build(),
    );

    vertices[VertexIndex::BottomFrontLeft as usize] = Some(
        vertex_builder_cb(
            VertexBuilder::default()
                .pos(bottom_front_left_pos)
                .normal(bottom_normal),
            Side::Bottom,
            Corner::TopLeft,
        )
        .build(),
    );
}