use bone_types::{Angle, IconId, LinearRgba, degree};

use crate::icon::{IconMaterial, IconModel, IconView, Prim, Rotation};

const STEEL: LinearRgba = LinearRgba::new(0.20, 0.32, 0.52, 1.0);
const STEEL_LIGHT: LinearRgba = LinearRgba::new(0.42, 0.54, 0.72, 1.0);
const AMBER: LinearRgba = LinearRgba::new(0.86, 0.42, 0.06, 1.0);
const AMBER_LIGHT: LinearRgba = LinearRgba::new(0.95, 0.62, 0.18, 1.0);
const GREEN: LinearRgba = LinearRgba::new(0.20, 0.50, 0.22, 1.0);
const RED: LinearRgba = LinearRgba::new(0.62, 0.13, 0.10, 1.0);
const GRAY: LinearRgba = LinearRgba::new(0.52, 0.54, 0.58, 1.0);
const LIGHT: LinearRgba = LinearRgba::new(0.82, 0.85, 0.90, 1.0);
const DARK: LinearRgba = LinearRgba::new(0.12, 0.14, 0.18, 1.0);
const WHITE: LinearRgba = LinearRgba::new(1.0, 1.0, 1.0, 1.0);
const AX_X: LinearRgba = LinearRgba::new(0.72, 0.16, 0.12, 1.0);
const AX_Y: LinearRgba = LinearRgba::new(0.16, 0.56, 0.20, 1.0);
const AX_Z: LinearRgba = LinearRgba::new(0.16, 0.30, 0.70, 1.0);

const BAR: f32 = 0.09;
const TUBE: f32 = 0.075;
const DOT: f32 = 0.16;
const DOT_SM: f32 = 0.1;

#[must_use]
pub fn model(id: IconId) -> IconModel {
    let prims = match id {
        IconId::Point => point(),
        IconId::Line => line(),
        IconId::CenterpointArc => centerpoint_arc(),
        IconId::TangentArc => tangent_arc(),
        IconId::ThreePointArc => three_point_arc(),
        IconId::Circle => circle(),
        IconId::PerimeterCircle => perimeter_circle(),
        IconId::CornerRectangle => corner_rectangle(),
        IconId::CenterRectangle => center_rectangle(),
        IconId::ThreePointCornerRectangle => three_point_corner_rectangle(),
        IconId::ThreePointCenterRectangle => three_point_center_rectangle(),
        IconId::Parallelogram => parallelogram(),
        IconId::SmartDimension => smart_dimension(),
        IconId::Coincident => coincident(),
        IconId::Horizontal => horizontal(),
        IconId::Vertical => vertical(),
        IconId::Parallel => parallel(),
        IconId::Perpendicular => perpendicular(),
        IconId::Tangent => tangent(),
        IconId::Equal => equal(),
        IconId::Concentric => concentric(),
        IconId::Midpoint => midpoint(),
        IconId::Symmetric => symmetric(),
        IconId::Fix => fix(),
        IconId::ExtrudedBossBase => extruded_boss_base(),
        IconId::ExtrudedCut => extruded_cut(),
        IconId::TreeFeature => tree_feature(),
        IconId::TreePlane => tree_plane(),
        IconId::TreeSketch => tree_sketch(),
        IconId::TreeOrigin => tree_origin(),
        IconId::TabTree => tab_tree(),
        IconId::TabProperties => tab_properties(),
        IconId::TabConfiguration => tab_configuration(),
        IconId::TabDimensionExpert => tab_dimension_expert(),
        IconId::TabDisplay => tab_display(),
        IconId::ZoomToFit => zoom_to_fit(),
        IconId::ZoomToArea => zoom_to_area(),
        IconId::PreviousView => previous_view(),
        IconId::SectionView => section_view(),
        IconId::ViewOrientation => view_orientation(),
        IconId::DisplayStyle => display_style(),
        IconId::HideShowItems => hide_show_items(),
        IconId::EditAppearance => edit_appearance(),
        IconId::ViewSettings => view_settings(),
        IconId::Check => check(),
        IconId::Cross => cross(),
    };
    IconModel::new(prims).with_view(view_for(id))
}

fn view_for(id: IconId) -> IconView {
    match id {
        IconId::ExtrudedBossBase
        | IconId::ExtrudedCut
        | IconId::TreeFeature
        | IconId::TreePlane
        | IconId::TreeOrigin
        | IconId::SectionView
        | IconId::ViewOrientation
        | IconId::DisplayStyle
        | IconId::TabConfiguration
        | IconId::TabDisplay
        | IconId::EditAppearance => IconView::Iso,
        _ => IconView::Front,
    }
}

fn mat(color: LinearRgba) -> IconMaterial {
    IconMaterial::new(color)
}

fn z0(p: [f32; 2]) -> [f32; 3] {
    [p[0], p[1], 0.0]
}

fn bar(a: [f32; 2], b: [f32; 2], radius: f32, color: LinearRgba) -> Prim {
    Prim::bar(z0(a), z0(b), radius, mat(color))
}

fn dot(p: [f32; 2], radius: f32, color: LinearRgba) -> Prim {
    Prim::ball(z0(p), radius, mat(color))
}

fn circle_path(center: [f32; 2], radius: f32, n: u16) -> Vec<[f32; 3]> {
    (0..n)
        .map(|i| {
            let t = core::f32::consts::TAU * f32::from(i) / f32::from(n);
            [
                center[0] + radius * t.cos(),
                center[1] + radius * t.sin(),
                0.0,
            ]
        })
        .collect()
}

fn arc_path(
    center: [f32; 2],
    radius: f32,
    start_deg: f32,
    sweep_deg: f32,
    n: u16,
) -> Vec<[f32; 3]> {
    let start = start_deg.to_radians();
    let sweep = sweep_deg.to_radians();
    (0..=n)
        .map(|i| {
            let t = start + sweep * f32::from(i) / f32::from(n);
            [
                center[0] + radius * t.cos(),
                center[1] + radius * t.sin(),
                0.0,
            ]
        })
        .collect()
}

fn ring(center: [f32; 2], radius: f32, tube: f32, color: LinearRgba) -> Prim {
    Prim::loop_tube(circle_path(center, radius, 48), tube, mat(color))
}

fn arc(
    center: [f32; 2],
    radius: f32,
    start_deg: f32,
    sweep_deg: f32,
    tube: f32,
    color: LinearRgba,
) -> Prim {
    Prim::open_tube(
        arc_path(center, radius, start_deg, sweep_deg, 24),
        tube,
        mat(color),
    )
}

fn rotate2(p: [f32; 2], angle_deg: f32) -> [f32; 2] {
    let r = angle_deg.to_radians();
    [
        p[0] * r.cos() - p[1] * r.sin(),
        p[0] * r.sin() + p[1] * r.cos(),
    ]
}

fn rect(corners: [[f32; 2]; 4], tube: f32, color: LinearRgba) -> Prim {
    Prim::loop_tube(corners.into_iter().map(z0).collect(), tube, mat(color))
}

fn tilted_rect(half_w: f32, half_h: f32, angle_deg: f32, tube: f32, color: LinearRgba) -> Prim {
    let corners = [
        rotate2([-half_w, -half_h], angle_deg),
        rotate2([half_w, -half_h], angle_deg),
        rotate2([half_w, half_h], angle_deg),
        rotate2([-half_w, half_h], angle_deg),
    ];
    rect(corners, tube, color)
}

fn turn(value_deg: f32) -> Angle {
    Angle::new::<degree>(f64::from(value_deg))
}

fn point() -> Vec<Prim> {
    vec![
        dot([0.0, 0.0], 0.24, STEEL),
        bar([-0.6, 0.0], [0.6, 0.0], 0.04, AMBER),
        bar([0.0, -0.6], [0.0, 0.6], 0.04, AMBER),
    ]
}

fn line() -> Vec<Prim> {
    vec![
        bar([-0.7, -0.5], [0.7, 0.5], BAR, STEEL),
        dot([-0.7, -0.5], DOT_SM, AMBER),
        dot([0.7, 0.5], DOT_SM, AMBER),
    ]
}

fn centerpoint_arc() -> Vec<Prim> {
    vec![
        arc([0.0, -0.35], 0.78, 30.0, 120.0, TUBE, STEEL),
        dot([0.0, -0.35], DOT, AMBER),
        bar([0.0, -0.35], [-0.675, 0.04], 0.045, STEEL_LIGHT),
    ]
}

fn tangent_arc() -> Vec<Prim> {
    vec![
        bar([-0.8, -0.55], [-0.1, -0.55], BAR, STEEL),
        arc([-0.1, 0.05], 0.6, -90.0, 95.0, TUBE, AMBER),
    ]
}

fn three_point_arc() -> Vec<Prim> {
    vec![
        arc([0.0, -0.45], 0.85, 30.0, 120.0, TUBE, STEEL),
        dot([-0.736, 0.025], DOT_SM, AMBER),
        dot([0.736, 0.025], DOT_SM, AMBER),
        dot([0.0, 0.4], DOT_SM, AMBER),
    ]
}

fn circle() -> Vec<Prim> {
    vec![
        ring([0.0, 0.0], 0.7, TUBE, STEEL),
        dot([0.0, 0.0], DOT_SM, STEEL_LIGHT),
    ]
}

fn perimeter_circle() -> Vec<Prim> {
    vec![
        ring([0.0, 0.0], 0.7, TUBE, STEEL),
        dot([0.0, 0.7], DOT_SM, AMBER),
        dot([-0.606, -0.35], DOT_SM, AMBER),
        dot([0.606, -0.35], DOT_SM, AMBER),
    ]
}

fn corner_rectangle() -> Vec<Prim> {
    vec![
        rect(
            [[-0.7, -0.5], [0.7, -0.5], [0.7, 0.5], [-0.7, 0.5]],
            TUBE,
            STEEL,
        ),
        dot([-0.7, -0.5], DOT_SM, AMBER),
        dot([0.7, 0.5], DOT_SM, AMBER),
    ]
}

fn center_rectangle() -> Vec<Prim> {
    vec![
        rect(
            [[-0.7, -0.5], [0.7, -0.5], [0.7, 0.5], [-0.7, 0.5]],
            TUBE,
            STEEL,
        ),
        dot([0.0, 0.0], DOT_SM, AMBER),
    ]
}

fn three_point_corner_rectangle() -> Vec<Prim> {
    let lower_left = rotate2([-0.72, -0.46], 18.0);
    let upper_right = rotate2([0.72, 0.46], 18.0);
    vec![
        tilted_rect(0.72, 0.46, 18.0, TUBE, STEEL),
        dot(lower_left, DOT_SM, AMBER),
        dot(upper_right, DOT_SM, AMBER),
    ]
}

fn three_point_center_rectangle() -> Vec<Prim> {
    vec![
        tilted_rect(0.72, 0.46, 18.0, TUBE, STEEL),
        dot([0.0, 0.0], DOT_SM, AMBER),
    ]
}

fn parallelogram() -> Vec<Prim> {
    vec![rect(
        [[-0.8, -0.5], [0.4, -0.5], [0.8, 0.5], [-0.4, 0.5]],
        TUBE,
        STEEL,
    )]
}

fn smart_dimension() -> Vec<Prim> {
    vec![
        bar([-0.6, -0.7], [-0.6, 0.25], 0.05, STEEL_LIGHT),
        bar([0.6, -0.7], [0.6, 0.25], 0.05, STEEL_LIGHT),
        Prim::arrow(
            z0([-0.05, -0.2]),
            z0([-0.6, -0.2]),
            0.05,
            0.16,
            0.22,
            mat(AMBER),
        ),
        Prim::arrow(
            z0([0.05, -0.2]),
            z0([0.6, -0.2]),
            0.05,
            0.16,
            0.22,
            mat(AMBER),
        ),
    ]
}

fn coincident() -> Vec<Prim> {
    vec![
        ring([0.0, 0.0], 0.62, TUBE, STEEL),
        dot([0.0, 0.0], 0.24, AMBER),
    ]
}

fn horizontal() -> Vec<Prim> {
    vec![
        bar([-0.78, 0.0], [0.78, 0.0], 0.11, AMBER),
        dot([-0.78, 0.0], DOT_SM, STEEL),
        dot([0.78, 0.0], DOT_SM, STEEL),
    ]
}

fn vertical() -> Vec<Prim> {
    vec![
        bar([0.0, -0.78], [0.0, 0.78], 0.11, AMBER),
        dot([0.0, -0.78], DOT_SM, STEEL),
        dot([0.0, 0.78], DOT_SM, STEEL),
    ]
}

fn parallel() -> Vec<Prim> {
    vec![
        bar([-0.45, -0.65], [-0.05, 0.65], BAR, AMBER),
        bar([0.25, -0.65], [0.65, 0.65], BAR, AMBER),
    ]
}

fn perpendicular() -> Vec<Prim> {
    vec![
        bar([-0.35, -0.6], [-0.35, 0.65], BAR, AMBER),
        bar([-0.45, -0.6], [0.7, -0.6], BAR, AMBER),
    ]
}

fn tangent() -> Vec<Prim> {
    vec![
        ring([-0.15, -0.12], 0.5, TUBE, STEEL),
        bar([-0.85, 0.46], [0.7, 0.46], BAR, AMBER),
    ]
}

fn equal() -> Vec<Prim> {
    vec![
        bar([-0.55, 0.2], [0.55, 0.2], 0.1, AMBER),
        bar([-0.55, -0.2], [0.55, -0.2], 0.1, AMBER),
    ]
}

fn concentric() -> Vec<Prim> {
    vec![
        ring([0.0, 0.0], 0.72, TUBE, STEEL),
        ring([0.0, 0.0], 0.38, TUBE, AMBER),
        dot([0.0, 0.0], DOT_SM, STEEL_LIGHT),
    ]
}

fn midpoint() -> Vec<Prim> {
    vec![
        bar([-0.75, -0.3], [0.75, 0.3], BAR, STEEL),
        dot([0.0, 0.0], 0.2, AMBER),
    ]
}

fn symmetric() -> Vec<Prim> {
    vec![
        bar([0.0, -0.7], [0.0, 0.7], 0.05, STEEL_LIGHT),
        dot([-0.5, 0.0], 0.2, AMBER),
        dot([0.5, 0.0], 0.2, AMBER),
    ]
}

fn fix() -> Vec<Prim> {
    vec![
        bar([-0.7, -0.5], [0.7, -0.5], 0.07, DARK),
        bar([-0.5, -0.5], [-0.7, -0.72], 0.05, DARK),
        bar([-0.15, -0.5], [-0.35, -0.72], 0.05, DARK),
        bar([0.2, -0.5], [0.0, -0.72], 0.05, DARK),
        bar([0.55, -0.5], [0.35, -0.72], 0.05, DARK),
        bar([0.0, -0.5], [0.0, 0.05], 0.06, STEEL_LIGHT),
        dot([0.0, 0.28], 0.26, STEEL),
    ]
}

fn extruded_boss_base() -> Vec<Prim> {
    vec![
        Prim::cuboid([0.0, -0.5, 0.0], [0.78, 0.12, 0.55], mat(GRAY)),
        Prim::cuboid([0.0, 0.02, 0.0], [0.4, 0.32, 0.4], mat(GREEN)),
        Prim::arrow(
            z0([0.0, 0.42]),
            z0([0.0, 0.95]),
            0.07,
            0.2,
            0.28,
            mat(AMBER),
        ),
    ]
}

fn extruded_cut() -> Vec<Prim> {
    vec![
        Prim::cuboid([0.0, -0.1, 0.0], [0.72, 0.5, 0.6], mat(GRAY)),
        Prim::cuboid([0.0, 0.45, 0.0], [0.3, 0.22, 0.32], mat(RED)),
        Prim::arrow(z0([0.0, 0.98]), z0([0.0, 0.42]), 0.07, 0.2, 0.28, mat(RED)),
    ]
}

fn tree_feature() -> Vec<Prim> {
    vec![Prim::cuboid(
        [0.0, 0.0, 0.0],
        [0.56, 0.56, 0.56],
        mat(STEEL),
    )]
}

fn tree_plane() -> Vec<Prim> {
    vec![Prim::tilted_cuboid(
        [0.0, 0.0, 0.0],
        [0.72, 0.72, 0.035],
        Rotation::about_x(turn(24.0)),
        mat(AMBER_LIGHT),
    )]
}

fn tree_sketch() -> Vec<Prim> {
    vec![
        bar([-0.6, -0.4], [0.6, -0.4], BAR, STEEL),
        ring([0.18, 0.28], 0.35, TUBE, AMBER),
    ]
}

fn tree_origin() -> Vec<Prim> {
    vec![
        Prim::bar([0.0, 0.0, 0.0], [0.85, 0.0, 0.0], 0.07, mat(AX_X)),
        Prim::bar([0.0, 0.0, 0.0], [0.0, 0.85, 0.0], 0.07, mat(AX_Y)),
        Prim::bar([0.0, 0.0, 0.0], [0.0, 0.0, 0.85], 0.07, mat(AX_Z)),
        Prim::ball([0.0, 0.0, 0.0], 0.16, mat(DARK)),
    ]
}

fn tab_tree() -> Vec<Prim> {
    vec![
        dot([-0.55, 0.45], DOT_SM, AMBER),
        dot([-0.55, 0.0], DOT_SM, AMBER),
        dot([-0.55, -0.45], DOT_SM, AMBER),
        bar([-0.3, 0.45], [0.6, 0.45], 0.07, STEEL),
        bar([-0.3, 0.0], [0.6, 0.0], 0.07, STEEL),
        bar([-0.3, -0.45], [0.6, -0.45], 0.07, STEEL),
    ]
}

fn tab_properties() -> Vec<Prim> {
    vec![
        Prim::cuboid([0.0, 0.0, 0.0], [0.52, 0.68, 0.05], mat(LIGHT)),
        bar([-0.32, 0.38], [0.32, 0.38], 0.06, STEEL),
        bar([-0.32, 0.08], [0.32, 0.08], 0.06, STEEL),
        bar([-0.32, -0.22], [0.1, -0.22], 0.06, STEEL),
    ]
}

fn tab_configuration() -> Vec<Prim> {
    vec![
        Prim::cuboid([-0.2, -0.2, -0.12], [0.42, 0.42, 0.1], mat(STEEL_LIGHT)),
        Prim::cuboid([0.18, 0.18, 0.12], [0.42, 0.42, 0.1], mat(STEEL)),
    ]
}

fn tab_dimension_expert() -> Vec<Prim> {
    vec![
        bar([-0.6, -0.55], [-0.6, 0.3], 0.05, STEEL_LIGHT),
        bar([0.6, -0.55], [0.6, 0.3], 0.05, STEEL_LIGHT),
        Prim::arrow(
            z0([-0.05, -0.1]),
            z0([-0.6, -0.1]),
            0.05,
            0.16,
            0.22,
            mat(AMBER),
        ),
        Prim::arrow(
            z0([0.05, -0.1]),
            z0([0.6, -0.1]),
            0.05,
            0.16,
            0.22,
            mat(AMBER),
        ),
    ]
}

fn tab_display() -> Vec<Prim> {
    vec![
        Prim::ball([0.0, -0.05, 0.0], 0.6, mat(STEEL_LIGHT)),
        Prim::ball([-0.22, 0.2, 0.4], 0.14, mat(LIGHT)),
    ]
}

fn zoom_to_fit() -> Vec<Prim> {
    vec![
        ring([-0.12, 0.16], 0.46, 0.1, STEEL),
        bar([0.2, -0.16], [0.68, -0.64], 0.12, DARK),
        bar([-0.7, 0.6], [-0.4, 0.6], 0.06, AMBER),
        bar([-0.7, 0.6], [-0.7, 0.3], 0.06, AMBER),
        bar([0.7, -0.6], [0.4, -0.6], 0.06, AMBER),
        bar([0.7, -0.6], [0.7, -0.3], 0.06, AMBER),
    ]
}

fn zoom_to_area() -> Vec<Prim> {
    vec![
        ring([-0.18, 0.18], 0.42, 0.1, STEEL),
        bar([0.14, -0.14], [0.62, -0.62], 0.12, DARK),
        rect(
            [[-0.45, -0.1], [0.35, -0.1], [0.35, 0.5], [-0.45, 0.5]],
            0.05,
            AMBER,
        ),
    ]
}

fn previous_view() -> Vec<Prim> {
    vec![
        arc([0.0, -0.05], 0.62, 35.0, 250.0, 0.1, STEEL),
        Prim::arrow(
            z0([0.62, 0.18]),
            z0([0.06, 0.5]),
            0.05,
            0.2,
            0.26,
            mat(AMBER),
        ),
    ]
}

fn section_view() -> Vec<Prim> {
    vec![
        Prim::cuboid([0.0, 0.0, 0.0], [0.55, 0.55, 0.55], mat(STEEL)),
        Prim::tilted_cuboid(
            [0.0, 0.0, 0.0],
            [0.66, 0.66, 0.025],
            Rotation::about_y(turn(45.0)),
            mat(AMBER_LIGHT),
        ),
    ]
}

fn view_orientation() -> Vec<Prim> {
    vec![
        Prim::cuboid([0.0, 0.0, 0.0], [0.5, 0.5, 0.5], mat(STEEL_LIGHT)),
        Prim::bar([-0.55, -0.55, -0.55], [0.1, -0.55, -0.55], 0.06, mat(AX_X)),
        Prim::bar([-0.55, -0.55, -0.55], [-0.55, 0.1, -0.55], 0.06, mat(AX_Y)),
        Prim::bar([-0.55, -0.55, -0.55], [-0.55, -0.55, 0.1], 0.06, mat(AX_Z)),
    ]
}

fn display_style() -> Vec<Prim> {
    vec![
        Prim::cuboid([0.0, -0.08, 0.0], [0.56, 0.48, 0.56], mat(STEEL)),
        Prim::cuboid([0.0, 0.46, 0.0], [0.56, 0.06, 0.56], mat(LIGHT)),
    ]
}

fn hide_show_items() -> Vec<Prim> {
    vec![
        arc([0.0, -0.62], 0.92, 48.0, 84.0, 0.09, STEEL),
        arc([0.0, 0.62], 0.92, 228.0, 84.0, 0.09, STEEL),
        ring([0.0, 0.0], 0.32, 0.08, STEEL_LIGHT),
        dot([0.0, 0.0], 0.18, DARK),
    ]
}

fn edit_appearance() -> Vec<Prim> {
    vec![
        Prim::ball([-0.1, -0.16, 0.24], 0.5, mat(AX_X)),
        Prim::ball([-0.24, 0.18, -0.12], 0.46, mat(AX_Y)),
        Prim::ball([0.2, -0.12, -0.22], 0.46, mat(AX_Z)),
        bar([0.18, 0.16], [0.7, 0.68], 0.09, AMBER),
        dot([0.16, 0.14], 0.16, DARK),
    ]
}

fn view_settings() -> Vec<Prim> {
    let teeth = (0..8u16).map(|k| {
        let angle = f32::from(k) * 45.0;
        let r = angle.to_radians();
        let center = [0.62 * r.cos(), 0.62 * r.sin(), 0.0];
        Prim::tilted_cuboid(
            center,
            [0.16, 0.12, 0.16],
            Rotation::about_z(turn(angle)),
            mat(STEEL),
        )
    });
    core::iter::once(ring([0.0, 0.0], 0.52, 0.13, STEEL))
        .chain(teeth)
        .chain(core::iter::once(dot([0.0, 0.0], 0.22, DARK)))
        .collect()
}

fn check() -> Vec<Prim> {
    vec![
        bar([-0.52, 0.04], [-0.08, -0.42], 0.1, WHITE),
        bar([-0.08, -0.42], [0.56, 0.52], 0.1, WHITE),
    ]
}

fn cross() -> Vec<Prim> {
    vec![
        bar([-0.46, -0.46], [0.46, 0.46], 0.095, WHITE),
        bar([-0.46, 0.46], [0.46, -0.46], 0.095, WHITE),
    ]
}
