use super::components::TerrainConfig;
use super::generation::{TerrainGenerationResult, generate_terrain_mesh};
use crate::ecs::bounding_volume::components::{BoundingVolume, OrientedBoundingBox};
use crate::ecs::material::components::{Material, MaterialRef};
use crate::ecs::material::resources::material_registry_insert;
use crate::ecs::mesh::components::RenderMesh;
use crate::ecs::physics::components::{ColliderComponent, ColliderShape, RigidBodyComponent};
use crate::ecs::physics::types::InteractionGroups;
use crate::ecs::prefab::resources::mesh_cache_insert;
use crate::ecs::shadow::components::CastsShadow;
use crate::ecs::transform::components::LocalTransform;
use crate::ecs::visibility::components::Visibility;
use crate::ecs::world::{
    BOUNDING_VOLUME, CASTS_SHADOW, COLLIDER, Entity, GLOBAL_TRANSFORM, LOCAL_TRANSFORM,
    LOCAL_TRANSFORM_DIRTY, MATERIAL_REF, NAME, RENDER_MESH, RIGID_BODY, VISIBILITY, World,
};
use nalgebra_glm::{Quat, Vec3};

pub fn spawn_terrain(world: &mut World, config: TerrainConfig, position: Vec3) -> Entity {
    let TerrainGenerationResult {
        mesh,
        heights,
        min_height,
        max_height,
    } = generate_terrain_mesh(&config);

    let mesh_name = format!("Terrain_{}", rand::random::<u32>());
    mesh_cache_insert(&mut world.resources.mesh_cache, mesh_name.clone(), mesh);

    let entity = world.spawn_entities(
        NAME | LOCAL_TRANSFORM
            | GLOBAL_TRANSFORM
            | LOCAL_TRANSFORM_DIRTY
            | RENDER_MESH
            | MATERIAL_REF
            | BOUNDING_VOLUME
            | CASTS_SHADOW
            | RIGID_BODY
            | COLLIDER
            | VISIBILITY,
        1,
    )[0];

    if let Some(name) = world.get_name_mut(entity) {
        name.0 = "Terrain".to_string();
    }

    world.set_local_transform(
        entity,
        LocalTransform {
            translation: position,
            rotation: Quat::identity(),
            scale: Vec3::new(1.0, 1.0, 1.0),
        },
    );

    if let Some(render_mesh) = world.get_render_mesh_mut(entity) {
        *render_mesh = RenderMesh::new(&mesh_name);
    }

    if let Some(&index) = world
        .resources
        .mesh_cache
        .registry
        .name_to_index
        .get(&mesh_name)
    {
        world.resources.mesh_cache.registry.add_reference(index);
    }

    let heightfield_heights = convert_heights_to_heightfield(
        &heights,
        config.resolution_x as usize,
        config.resolution_z as usize,
    );

    if let Some(rigid_body) = world.get_rigid_body_mut(entity) {
        *rigid_body =
            RigidBodyComponent::new_static().with_translation(position.x, position.y, position.z);
    }

    if let Some(collider) = world.get_collider_mut(entity) {
        *collider = ColliderComponent {
            handle: None,
            shape: ColliderShape::HeightField {
                nrows: config.resolution_z as usize,
                ncols: config.resolution_x as usize,
                heights: heightfield_heights,
                scale: [config.width, 1.0, config.depth],
            },
            friction: 0.9,
            restitution: 0.0,
            density: 0.0,
            is_sensor: false,
            collision_groups: InteractionGroups::all(),
            solver_groups: InteractionGroups::all(),
        };
    }

    if let Some(bv) = world.get_bounding_volume_mut(entity) {
        let half_width = config.width / 2.0;
        let half_depth = config.depth / 2.0;
        let half_height = (max_height - min_height) / 2.0;
        let center_y = (max_height + min_height) / 2.0;

        let sphere_radius =
            (half_width * half_width + half_height * half_height + half_depth * half_depth).sqrt();

        *bv = BoundingVolume {
            obb: OrientedBoundingBox::new(
                Vec3::new(0.0, center_y, 0.0),
                Vec3::new(half_width, half_height, half_depth),
                Quat::identity(),
            ),
            sphere_radius,
        };
    }

    world.set_casts_shadow(entity, CastsShadow);
    world.set_visibility(entity, Visibility { visible: true });

    entity
}

pub fn spawn_terrain_with_material(
    world: &mut World,
    config: TerrainConfig,
    position: Vec3,
    material: Material,
) -> Entity {
    let entity = spawn_terrain(world, config, position);

    let material_name = format!("TerrainMaterial_{}", entity.id);
    material_registry_insert(
        &mut world.resources.material_registry,
        material_name.clone(),
        material,
    );

    if let Some(&index) = world
        .resources
        .material_registry
        .registry
        .name_to_index
        .get(&material_name)
    {
        world
            .resources
            .material_registry
            .registry
            .add_reference(index);
    }

    world.set_material_ref(entity, MaterialRef::new(material_name));

    entity
}

fn convert_heights_to_heightfield(
    heights: &[f32],
    resolution_x: usize,
    resolution_z: usize,
) -> Vec<f32> {
    let mut heightfield_heights = vec![0.0; heights.len()];

    for z in 0..resolution_z {
        for x in 0..resolution_x {
            let mesh_index = z * resolution_x + x;
            let heightfield_index = z + resolution_z * x;
            heightfield_heights[heightfield_index] = heights[mesh_index];
        }
    }

    heightfield_heights
}
