use obj::raw::object::Polygon;
use rapier_testbed3d::TestbedViewer;
use rapier3d::parry::bounding_volume;
use rapier3d::prelude::*;
use std::fs::File;
use std::io::BufReader;

pub async fn run(viewer: &mut TestbedViewer) -> anyhow::Result<()> {
    run_impl(viewer, false).await
}

pub async fn run_impl(
    viewer: &mut TestbedViewer,
    use_convex_decomposition: bool,
) -> anyhow::Result<()> {
    /*
     * World
     */
    let mut world = PhysicsWorld::new();

    /*
     * Ground
     */
    //// OPTION 1: floor made of a single big box.
    // let ground_size = 50.0;
    // let ground_height = 0.1;
    // let rigid_body = RigidBodyBuilder::fixed().translation(Vector::new(0.0, -ground_height, 0.0));
    // let handle = bodies.insert(rigid_body);
    // let collider = ColliderBuilder::cuboid(ground_size, ground_height, ground_size);
    // colliders.insert_with_parent(collider, handle, &mut bodies);

    //// OPTION 2: floor made of a wavy mesh.
    let nsubdivs = 100;
    let heights = Array2::from_fn(nsubdivs + 1, nsubdivs + 1, |i, j| {
        -(i as f32 * 40.0 / (nsubdivs as f32) / 2.0).cos()
            - (j as f32 * 40.0 / (nsubdivs as f32) / 2.0).cos()
    });
    let heightfield = HeightField::new(heights, Vector::new(100.0, 2.0, 100.0));
    let mut trimesh = TriMesh::from(heightfield);
    let _ = trimesh.set_flags(TriMeshFlags::FIX_INTERNAL_EDGES);
    world.insert_collider(
        ColliderBuilder::new(SharedShape::new(trimesh.clone())),
        None,
    );

    /*
     * Create the convex decompositions.
     */
    let geoms = models();
    let ngeoms = geoms.len();
    let width = (ngeoms as f32).sqrt() as usize;
    let num_duplications = 4;
    let shift_y = 8.0f32;
    let shift_xz = 9.0f32;

    for (igeom, obj_path) in geoms.into_iter().enumerate() {
        let deltas = Pose::IDENTITY;

        let mut shapes = Vec::new();
        println!("Parsing and decomposing: {obj_path}");
        let input = BufReader::new(File::open(obj_path).unwrap());

        if let Ok(model) = obj::raw::parse_obj(input) {
            let mut vertices: Vec<Vector> = model
                .positions
                .iter()
                .map(|v| Vector::new(v.0, v.1, v.2))
                .collect();
            let indices: Vec<_> = model
                .polygons
                .into_iter()
                .flat_map(|p| match p {
                    Polygon::P(idx) => idx.into_iter(),
                    Polygon::PT(idx) => Vec::from_iter(idx.into_iter().map(|i| i.0)).into_iter(),
                    Polygon::PN(idx) => Vec::from_iter(idx.into_iter().map(|i| i.0)).into_iter(),
                    Polygon::PTN(idx) => Vec::from_iter(idx.into_iter().map(|i| i.0)).into_iter(),
                })
                .collect();

            // Compute the size of the model, to scale it and have similar size for everything.
            let aabb =
                bounding_volume::details::point_cloud_aabb(&deltas, vertices.iter().copied());
            let center = aabb.center();
            let center_v = Vector::new(center.x, center.y, center.z);
            let diag = (aabb.maxs - aabb.mins).length();

            vertices
                .iter_mut()
                .for_each(|p| *p = (*p - center_v) * 10.0 / diag);

            let indices: Vec<_> = indices
                .chunks(3)
                .map(|idx| [idx[0] as u32, idx[1] as u32, idx[2] as u32])
                .collect();

            let decomposed_shape = if use_convex_decomposition {
                SharedShape::convex_decomposition(&vertices, &indices)
            } else {
                // SharedShape::trimesh(vertices, indices)
                SharedShape::trimesh_with_flags(vertices, indices, TriMeshFlags::FIX_INTERNAL_EDGES)
                    .unwrap()
            };
            shapes.push(decomposed_shape);

            // let compound = SharedShape::compound(compound_parts);

            for k in 1..num_duplications + 1 {
                let x =
                    (igeom % width) as f32 * shift_xz - num_duplications as f32 * shift_xz / 2.0;
                let y = (igeom / width) as f32 * shift_y + 7.0;
                let z = k as f32 * shift_xz - num_duplications as f32 * shift_xz / 2.0;

                let body = RigidBodyBuilder::dynamic().translation(Vector::new(x, y, z));
                let handle = world.insert_body(body);

                for shape in &shapes {
                    let collider = ColliderBuilder::new(shape.clone()).contact_skin(0.1);
                    world.insert_collider(collider, Some(handle));
                }
            }
        }
    }

    /*
     * Set up the testbed.
     */
    viewer.set_world(&mut world);
    viewer.look_at(Vec3::new(100.0, 100.0, 100.0), Vec3::ZERO);

    while viewer.render_frame(&mut world).await {
        if viewer.simulating() {
            world.step();
        }
    }
    Ok(())
}

fn models() -> Vec<String> {
    vec![
        "assets/3d/camel_decimated.obj".to_string(),
        "assets/3d/chair.obj".to_string(),
        "assets/3d/cup_decimated.obj".to_string(),
        "assets/3d/dilo_decimated.obj".to_string(),
        "assets/3d/tstTorusModel2.obj".to_string(),
        "assets/3d/feline_decimated.obj".to_string(),
        "assets/3d/genus3_decimated.obj".to_string(),
        // "assets/3d/hand2_decimated.obj".to_string(),
        // "assets/3d/hand_decimated.obj".to_string(),
        "assets/3d/hornbug.obj".to_string(),
        "assets/3d/tstTorusModel.obj".to_string(),
        "assets/3d/octopus_decimated.obj".to_string(),
        "assets/3d/rabbit_decimated.obj".to_string(),
        "assets/3d/rust_logo_simplified.obj".to_string(),
        "assets/3d/screwdriver_decimated.obj".to_string(),
        "assets/3d/table.obj".to_string(),
        "assets/3d/tstTorusModel3.obj".to_string(),
    ]
}
