use crate::loaders::mesh::InstanceRaw;
use crate::world::World;
use bytemuck::cast_slice;

pub fn tree_dissolve_update_system(world: &mut World, delta: f32)
{
    for entity in world.tree_instances.all()
    {
        if let Some(tree_instances) = world.tree_instances.get_mut(entity)
        {
            for i in 0..tree_instances.dissolve_amounts.len()
            {
                let diff = tree_instances.dissolve_targets[i] - tree_instances.dissolve_amounts[i];
                tree_instances.dissolve_amounts[i] +=
                    diff * tree_instances.transition_speed * delta;
                tree_instances.dissolve_amounts[i] =
                    tree_instances.dissolve_amounts[i].clamp(0.0, 1.0);
            }
        }
    }
}

pub fn tree_occlusion_system(world: &mut World)
{
    let player_entity = world.player_tags.all().first().copied();
    let player_pos = player_entity.and_then(|e| world.transforms.get(e).map(|t| t.position));

    if let Some(player_pos) = player_pos
    {
        let camera_entity = world.active_camera().map(|(e, _)| e);
        let camera_pos = camera_entity.and_then(|e| world.transforms.get(e).map(|t| t.position));

        if let Some(camera_pos) = camera_pos
        {
            let to_player = player_pos - camera_pos;
            let distance_to_player = to_player.length();

            if distance_to_player < 0.01
            {
                return;
            }

            let to_player_normalized = to_player.normalize();
            let occlusion_radius = 10.0;

            for tree_entity in world.tree_instances.all()
            {
                if let Some(tree_instances) = world.tree_instances.get_mut(tree_entity)
                {
                    for (idx, instance) in tree_instances.instances.iter().enumerate()
                    {
                        let instance_pos = instance.position;
                        let to_instance = instance_pos - camera_pos;
                        let distance_to_instance = to_instance.length();

                        if distance_to_instance < distance_to_player
                        {
                            let projection = to_instance.dot(to_player_normalized);

                            if projection > 0.0 && projection <= distance_to_player
                            {
                                let perpendicular_vec =
                                    to_instance - to_player_normalized * projection;
                                let perp_distance = perpendicular_vec.length();

                                if perp_distance <= occlusion_radius
                                {
                                    let dissolve_amount =
                                        1.0 - (perp_distance / occlusion_radius).clamp(0.0, 1.0);
                                    tree_instances.dissolve_targets[idx] = dissolve_amount;
                                    continue;
                                }
                            }
                        }

                        tree_instances.dissolve_targets[idx] = 0.0;
                    }
                }
            }
        }
    }
}

pub fn tree_instance_buffer_update_system(world: &mut World)
{
    for entity in world.tree_instances.all()
    {
        if let Some(tree_instances) = world.tree_instances.get(entity)
        {
            let instance_data_vec: Vec<InstanceRaw> = tree_instances
                .instances
                .iter()
                .enumerate()
                .map(|(idx, instance)| InstanceRaw {
                    model: instance.to_raw().model,
                    dissolve_amount: tree_instances.dissolve_amounts[idx],
                    _padding: [0.0; 3],
                })
                .collect();

            crate::render::with_queue(|queue| {
                queue.write_buffer(
                    &tree_instances.instance_buffer,
                    0,
                    cast_slice(&instance_data_vec),
                );
            });
        }
    }
}
