use boxddd::{
    Aabb, BodyDef, BodyType, BoxHull, Capsule, Compound, CompoundChild, Error, HeightField, Hull,
    MeshData, Quat, ShapeDef, ShapeType, Sphere, SurfaceMaterial, Transform, Vec3, World, WorldDef,
};

#[test]
fn shape_creation_covers_value_and_native_resources() {
    let mut world = World::new(WorldDef::default()).unwrap();
    let static_body = world.create_body(BodyDef::builder().body_type(BodyType::Static).build());
    let def = ShapeDef::builder().density(1.0).build();

    let sphere = world.create_sphere_shape(static_body, &def, &Sphere::new([0.0, 0.0, 0.0], 0.5));
    assert_eq!(world.try_shape_type(sphere).unwrap(), ShapeType::Sphere);

    let capsule = world
        .try_create_capsule_shape(
            static_body,
            &def,
            &Capsule::new([0.0, 0.0, 0.0], [0.0, 1.0, 0.0], 0.25),
        )
        .unwrap();
    assert_eq!(world.try_shape_type(capsule).unwrap(), ShapeType::Capsule);

    let box_hull = world.create_hull_shape(
        static_body,
        &def,
        &BoxHull::offset(0.5, 0.5, 0.5, [1.0, 0.0, 0.0]),
    );
    assert_eq!(world.try_shape_type(box_hull).unwrap(), ShapeType::Hull);
    let hull_view = world.try_shape_hull(box_hull).unwrap();
    assert_eq!(hull_view.vertex_count(), 8);
    assert!(hull_view.surface_area() > 0.0);
    assert_eq!(
        world.try_shape_hull(sphere).unwrap_err(),
        Error::InvalidArgument
    );

    let created_hull = Hull::rock(0.5).unwrap();
    let created_hull_shape = world
        .try_create_created_hull_shape(static_body, &def, &created_hull)
        .unwrap();
    drop(created_hull);
    assert!(created_hull_shape.is_valid());
    assert_eq!(
        world.try_shape_type(created_hull_shape).unwrap(),
        ShapeType::Hull
    );

    let transformed_hull = Hull::cylinder(1.0, 0.25, 0.0, 8).unwrap();
    let cloned_hull = transformed_hull.try_clone().unwrap();
    assert_ne!(
        cloned_hull.as_hull_data() as *const _,
        transformed_hull.as_hull_data() as *const _
    );
    assert_eq!(
        cloned_hull.as_hull_data().byteCount,
        transformed_hull.as_hull_data().byteCount
    );
    let moved_hull = transformed_hull
        .try_clone_transformed(Transform::new(Vec3::X, Quat::IDENTITY), [1.0, 1.0, 1.0])
        .unwrap();
    assert_ne!(
        moved_hull.as_hull_data().center.x,
        transformed_hull.as_hull_data().center.x
    );
    assert_eq!(
        transformed_hull
            .try_clone_transformed(
                Transform::new(Vec3::ZERO, Quat::new(Vec3::ZERO, f32::NAN)),
                [1.0, 1.0, 1.0],
            )
            .unwrap_err(),
        Error::InvalidArgument
    );
    assert_eq!(
        transformed_hull
            .try_clone_transformed(Transform::IDENTITY, [0.0, 1.0, 1.0])
            .unwrap_err(),
        Error::InvalidArgument
    );

    let scaled_box =
        BoxHull::scale_box([1.0, 2.0, 3.0], Transform::IDENTITY, [-2.0, 0.5, 1.5], 0.1).unwrap();
    assert!(scaled_box.half_widths.x >= 0.1);
    assert!(scaled_box.half_widths.y >= 0.1);
    assert!(scaled_box.half_widths.z >= 0.1);
    assert!(scaled_box.transform.is_valid());
    assert_eq!(
        BoxHull::scale_box(
            [1.0, 2.0, 3.0],
            Transform::IDENTITY,
            [f32::NAN, 1.0, 1.0],
            0.1
        )
        .unwrap_err(),
        Error::InvalidArgument
    );
    assert_eq!(
        BoxHull::scale_box([1.0, 2.0, 3.0], Transform::IDENTITY, [1.0, 1.0, 1.0], 0.0).unwrap_err(),
        Error::InvalidArgument
    );

    let transformed_hull_shape = world
        .try_create_transformed_hull_shape(
            static_body,
            &def,
            &transformed_hull,
            Transform::default(),
            [1.0, 1.0, 1.0],
        )
        .unwrap();
    assert_eq!(
        world.try_shape_type(transformed_hull_shape).unwrap(),
        ShapeType::Hull
    );

    let mesh_shape = world
        .try_create_mesh_shape(
            static_body,
            &def,
            MeshData::box_mesh([0.0, 0.0, 0.0], [1.0, 1.0, 1.0], true).unwrap(),
            [1.0, 1.0, 1.0],
        )
        .unwrap();
    assert_eq!(world.try_shape_type(mesh_shape).unwrap(), ShapeType::Mesh);
    let mesh_view = world.try_shape_mesh(mesh_shape).unwrap();
    assert_eq!(mesh_view.scale(), Vec3::new(1.0, 1.0, 1.0));
    assert!(mesh_view.vertex_count() > 0);
    assert!(mesh_view.triangle_count() > 0);

    let custom_mesh = MeshData::builder(vec![Vec3::ZERO, Vec3::X, Vec3::Y], vec![0, 1, 2])
        .material_indices(vec![0])
        .build()
        .unwrap();
    assert_eq!(custom_mesh.material_count(), 1);
    assert_eq!(custom_mesh.triangle_count(), 1);
    assert!(custom_mesh.tree_height() >= 0);
    assert!(
        MeshData::wave_mesh(2, 2, 1.0, 0.25, 1.0, 1.0)
            .unwrap()
            .triangle_count()
            > 0
    );
    assert!(
        MeshData::torus_mesh(8, 8, 1.0, 0.25)
            .unwrap()
            .triangle_count()
            > 0
    );
    assert!(
        MeshData::hollow_box_mesh(Vec3::ZERO, [1.0, 1.0, 1.0])
            .unwrap()
            .triangle_count()
            > 0
    );
    assert!(
        MeshData::platform_mesh(Vec3::ZERO, 1.0, 0.5, 1.0)
            .unwrap()
            .triangle_count()
            > 0
    );

    let height_shape = world
        .try_create_height_field_shape(
            static_body,
            &def,
            HeightField::grid(4, 4, [1.0, 1.0, 1.0], false).unwrap(),
        )
        .unwrap();
    assert_eq!(
        world.try_shape_type(height_shape).unwrap(),
        ShapeType::HeightField
    );
    let height_view = world.try_shape_height_field(height_shape).unwrap();
    assert_eq!(height_view.column_count(), 4);
    assert_eq!(height_view.row_count(), 4);

    let custom_height = HeightField::builder(2, 2, vec![0.0, 1.0, 0.5, 1.5])
        .material_indices(vec![boxddd::HEIGHT_FIELD_HOLE])
        .clockwise_winding(true)
        .build([1.0, 1.0, 1.0])
        .unwrap();
    assert_eq!(custom_height.row_count(), 2);
    assert_eq!(custom_height.column_count(), 2);
    assert!(custom_height.byte_count() > 0);
    let wave_height = HeightField::wave(4, 4, [1.0, 1.0, 1.0], 1.0, 1.0, false).unwrap();
    assert_eq!(wave_height.row_count(), 4);
    assert_eq!(wave_height.column_count(), 4);

    let compound_mesh = MeshData::box_mesh([0.0, 0.0, 0.0], [0.25, 0.25, 0.25], true).unwrap();
    let mut compound_builder = Compound::builder();
    compound_builder
        .add_capsule(
            Capsule::new([-1.0, 0.0, 0.0], [-1.0, 0.75, 0.0], 0.2),
            SurfaceMaterial {
                user_material_id: 11,
                ..Default::default()
            },
        )
        .unwrap();
    compound_builder
        .add_hull(
            &transformed_hull,
            Transform::IDENTITY,
            SurfaceMaterial {
                user_material_id: 12,
                ..Default::default()
            },
        )
        .unwrap();
    compound_builder
        .add_mesh(
            &compound_mesh,
            Transform::IDENTITY,
            [1.0, 1.0, 1.0],
            [SurfaceMaterial {
                user_material_id: 13,
                ..Default::default()
            }],
        )
        .unwrap();
    compound_builder
        .add_sphere(
            Sphere::new([1.0, 0.0, 0.0], 0.25),
            SurfaceMaterial {
                user_material_id: 14,
                ..Default::default()
            },
        )
        .unwrap();
    let compound = compound_builder.build().unwrap();
    assert_eq!(compound.child_count(), 4);
    assert_eq!(compound.capsule_count(), 1);
    assert_eq!(compound.hull_count(), 1);
    assert_eq!(compound.mesh_count(), 1);
    assert_eq!(compound.sphere_count(), 1);
    assert_eq!(compound.material_count(), 4);
    assert_eq!(compound.material(0).unwrap().user_material_id, 11);
    assert_eq!(compound.material(3).unwrap().user_material_id, 14);
    assert_eq!(compound.material(4).unwrap_err(), Error::IndexOutOfRange);
    let child: CompoundChild<'_> = compound.child(0).unwrap();
    assert_eq!(child.shape_type(), ShapeType::Capsule);
    assert_eq!(compound.child(1).unwrap().shape_type(), ShapeType::Hull);
    assert_eq!(compound.child(2).unwrap().shape_type(), ShapeType::Mesh);
    assert_eq!(compound.child(3).unwrap().shape_type(), ShapeType::Sphere);
    assert_eq!(compound.child(4).unwrap_err(), Error::IndexOutOfRange);

    let query_bounds = boxddd::Aabb {
        lower_bound: [-2.0, -2.0, -2.0].into(),
        upper_bound: [2.0, 2.0, 2.0].into(),
    };
    let query_hits = compound.query_aabb(query_bounds).unwrap();
    assert_eq!(query_hits.len(), compound.child_count() as usize);
    assert!(query_hits.iter().all(|hit| hit.child_index >= 0));
    assert!(
        query_hits
            .iter()
            .any(|hit| hit.child.shape_type() == ShapeType::Capsule)
    );
    assert!(
        query_hits
            .iter()
            .any(|hit| hit.child.shape_type() == ShapeType::Hull)
    );
    assert!(
        query_hits
            .iter()
            .any(|hit| hit.child.shape_type() == ShapeType::Mesh)
    );
    assert!(
        query_hits
            .iter()
            .any(|hit| hit.child.shape_type() == ShapeType::Sphere)
    );

    let mut reusable_hits = Vec::with_capacity(8);
    compound
        .query_aabb_into(query_bounds, &mut reusable_hits)
        .unwrap();
    assert_eq!(reusable_hits.len(), query_hits.len());

    let mut visited = 0;
    compound
        .visit_query_aabb(query_bounds, |hit| {
            visited += 1;
            assert!(hit.child_index >= 0);
            false
        })
        .unwrap();
    assert_eq!(visited, 1);

    compound
        .visit_query_aabb(query_bounds, |_| {
            assert_eq!(
                compound.query_aabb(query_bounds).unwrap_err(),
                Error::InCallback
            );
            false
        })
        .unwrap();

    assert_eq!(
        compound
            .query_aabb(boxddd::Aabb {
                lower_bound: [1.0, 0.0, 0.0].into(),
                upper_bound: [0.0, 0.0, 0.0].into(),
            })
            .unwrap_err(),
        Error::InvalidArgument
    );

    let compound_byte_count = compound.byte_count();
    let compound_bytes = compound.into_bytes();
    assert_eq!(compound_bytes.byte_count(), compound_byte_count);
    assert_eq!(
        compound_bytes.as_slice().len(),
        compound_byte_count as usize
    );
    assert!(!compound_bytes.as_slice().is_empty());
    let compound = compound_bytes.into_compound().unwrap();
    assert_eq!(compound.child_count(), 4);
    assert_eq!(compound.material(0).unwrap().user_material_id, 11);
    drop(
        Compound::single_sphere(Sphere::new(Vec3::ZERO, 0.25), SurfaceMaterial::default())
            .unwrap()
            .into_bytes(),
    );

    let compound_shape = world
        .try_create_compound_shape(static_body, &def, compound)
        .unwrap();
    assert_eq!(
        world.try_shape_type(compound_shape).unwrap(),
        ShapeType::Compound
    );
    assert_eq!(
        world
            .try_shape_compound(compound_shape)
            .unwrap()
            .child_count(),
        4
    );
}

#[test]
fn borrowed_resource_shapes_are_rejected_on_dynamic_bodies() {
    let mut world = World::new(WorldDef::default()).unwrap();
    let dynamic_body = world.create_body(
        BodyDef::builder()
            .body_type(BodyType::Dynamic)
            .position([0.0, 1.0, 0.0])
            .build(),
    );
    let def = ShapeDef::default();

    assert_eq!(
        world
            .try_create_mesh_shape(
                dynamic_body,
                &def,
                MeshData::box_mesh([0.0, 0.0, 0.0], [1.0, 1.0, 1.0], true).unwrap(),
                [1.0, 1.0, 1.0],
            )
            .unwrap_err(),
        Error::InvalidArgument
    );
    assert_eq!(
        world
            .try_create_height_field_shape(
                dynamic_body,
                &def,
                HeightField::grid(4, 4, [1.0, 1.0, 1.0], false).unwrap(),
            )
            .unwrap_err(),
        Error::InvalidArgument
    );
    assert_eq!(
        world
            .try_create_compound_shape(
                dynamic_body,
                &def,
                Compound::single_sphere(
                    Sphere::new([0.0, 0.0, 0.0], 0.25),
                    SurfaceMaterial::default(),
                )
                .unwrap(),
            )
            .unwrap_err(),
        Error::InvalidArgument
    );

    let sphere = world.create_sphere_shape(dynamic_body, &def, &Sphere::new([0.0, 0.0, 0.0], 0.25));
    assert_eq!(
        world
            .try_set_shape_mesh(
                sphere,
                MeshData::box_mesh([0.0, 0.0, 0.0], [1.0, 1.0, 1.0], true).unwrap(),
                [1.0, 1.0, 1.0],
            )
            .unwrap_err(),
        Error::InvalidArgument
    );
}

#[test]
fn mesh_and_height_field_query_visitors_return_owned_triangles() {
    let mesh = MeshData::box_mesh([0.0, 0.0, 0.0], [1.0, 1.0, 1.0], true).unwrap();
    let broad_bounds = Aabb {
        lower_bound: [-1.25, -1.25, -1.25].into(),
        upper_bound: [1.25, 1.25, 1.25].into(),
    };

    let mesh_hits = mesh.query_triangles(broad_bounds, [1.0, 1.0, 1.0]).unwrap();
    assert!(!mesh_hits.is_empty());
    assert!(mesh_hits.iter().all(|hit| {
        hit.triangle_index >= 0 && hit.a.is_valid() && hit.b.is_valid() && hit.c.is_valid()
    }));

    let mut reusable_hits = Vec::with_capacity(16);
    mesh.query_triangles_into(broad_bounds, [1.0, 1.0, 1.0], &mut reusable_hits)
        .unwrap();
    assert_eq!(reusable_hits.len(), mesh_hits.len());

    let mut visited = 0;
    mesh.visit_triangles(broad_bounds, [1.0, 1.0, 1.0], |hit| {
        visited += 1;
        assert!(hit.triangle_index >= 0);
        false
    })
    .unwrap();
    assert_eq!(visited, 1);

    let narrow_hits = mesh
        .query_triangles(
            Aabb {
                lower_bound: [-0.1, -0.1, -0.1].into(),
                upper_bound: [0.1, 0.1, 0.1].into(),
            },
            [1.0, 1.0, 1.0],
        )
        .unwrap();
    assert!(narrow_hits.len() <= mesh_hits.len());

    assert_eq!(
        mesh.query_triangles(broad_bounds, [0.0, 1.0, 1.0])
            .unwrap_err(),
        Error::InvalidArgument
    );
    assert_eq!(
        mesh.query_triangles(
            Aabb {
                lower_bound: [1.0, 0.0, 0.0].into(),
                upper_bound: [0.0, 0.0, 0.0].into(),
            },
            [1.0, 1.0, 1.0],
        )
        .unwrap_err(),
        Error::InvalidArgument
    );

    mesh.visit_triangles(broad_bounds, [1.0, 1.0, 1.0], |_| {
        assert_eq!(
            mesh.query_triangles(broad_bounds, [1.0, 1.0, 1.0])
                .unwrap_err(),
            Error::InCallback
        );
        false
    })
    .unwrap();

    let height_field = HeightField::grid(3, 3, [1.0, 1.0, 1.0], false).unwrap();
    let height_bounds = Aabb {
        lower_bound: [-0.25, -1.0, -0.25].into(),
        upper_bound: [2.25, 1.0, 2.25].into(),
    };
    let height_hits = height_field.query_triangles(height_bounds).unwrap();
    assert!(!height_hits.is_empty());
    assert!(height_hits.iter().all(|hit| {
        hit.triangle_index >= 0 && hit.a.is_valid() && hit.b.is_valid() && hit.c.is_valid()
    }));

    let mut height_reusable_hits = Vec::new();
    height_field
        .query_triangles_into(height_bounds, &mut height_reusable_hits)
        .unwrap();
    assert_eq!(height_reusable_hits.len(), height_hits.len());

    let mut height_visited = 0;
    height_field
        .visit_triangles(height_bounds, |hit| {
            height_visited += 1;
            assert!(hit.triangle_index >= 0);
            assert_eq!(
                height_field.query_triangles(height_bounds).unwrap_err(),
                Error::InCallback
            );
        })
        .unwrap();
    assert_eq!(height_visited, height_hits.len());

    assert_eq!(
        height_field
            .query_triangles(Aabb {
                lower_bound: [1.0, 0.0, 0.0].into(),
                upper_bound: [0.0, 0.0, 0.0].into(),
            })
            .unwrap_err(),
        Error::InvalidArgument
    );
}
