use super::super::pass::SkinnedMeshPass;
use crate::render::wgpu::rendergraph::PassExecutionContext;

impl SkinnedMeshPass {
    fn transparent_indirect_offset(&self) -> u64 {
        (self.state().opaque_draw_groups.len()
            * std::mem::size_of::<super::super::types::SkinnedDrawIndexedIndirect>()) as u64
    }

    pub(in super::super) fn execute_pass_node<'r, 'e>(
        &mut self,
        context: PassExecutionContext<'r, 'e, crate::ecs::world::World>,
    ) -> crate::render::wgpu::rendergraph::Result<
        Vec<crate::render::wgpu::rendergraph::SubGraphRunCommand<'r>>,
    > {
        if !context.is_pass_enabled() {
            return Ok(context.into_sub_graph_commands());
        }
        if self.state().skinned_entities.is_empty() {
            return Ok(vec![]);
        }

        if self.animation.skeleton_count() > 0 {
            let mut animation_pass =
                context
                    .encoder
                    .begin_compute_pass(&wgpu::ComputePassDescriptor {
                        label: Some("Animation Compute Pass"),
                        timestamp_writes: None,
                    });
            self.animation.dispatch(&mut animation_pass);
        }

        if self.total_joints_to_dispatch > 0 {
            let mut compute_pass =
                context
                    .encoder
                    .begin_compute_pass(&wgpu::ComputePassDescriptor {
                        label: Some("Skinning Compute Pass"),
                        timestamp_writes: None,
                    });

            compute_pass.set_pipeline(&self.skinning_compute_pipeline);
            compute_pass.set_bind_group(0, &self.skinning_compute_bind_group, &[]);

            let num_workgroups = self.total_joints_to_dispatch.div_ceil(64);
            compute_pass.dispatch_workgroups(num_workgroups, 1, 1);
        }

        if self.gpu_batching_enabled {
            let object_count = self.state().skinned_entities.len() as u32;
            let mesh_count = self.skinned_mesh_data.len().max(1) as u32;
            if self.gpu().batch_bind_group.is_none() {
                let bind_group = context
                    .device
                    .create_bind_group(&wgpu::BindGroupDescriptor {
                        label: Some("Skinned Mesh Batch Bind Group"),
                        layout: &self.batch_bind_group_layout,
                        entries: &[
                            wgpu::BindGroupEntry {
                                binding: 0,
                                resource: self.gpu().object_buffer.as_entire_binding(),
                            },
                            wgpu::BindGroupEntry {
                                binding: 1,
                                resource: self.gpu().cull_objects_buffer.as_entire_binding(),
                            },
                            wgpu::BindGroupEntry {
                                binding: 2,
                                resource: self.gpu().dense_capacity_buffer.as_entire_binding(),
                            },
                            wgpu::BindGroupEntry {
                                binding: 3,
                                resource: self.gpu().command_map_buffer.as_entire_binding(),
                            },
                            wgpu::BindGroupEntry {
                                binding: 4,
                                resource: self.gpu().indirect_commands_buffer.as_entire_binding(),
                            },
                            wgpu::BindGroupEntry {
                                binding: 5,
                                resource: self.gpu().batch_meta_buffer.as_entire_binding(),
                            },
                            wgpu::BindGroupEntry {
                                binding: 6,
                                resource: self.batch_mesh_geo_buffer.as_entire_binding(),
                            },
                            wgpu::BindGroupEntry {
                                binding: 7,
                                resource: self.batch_params_buffer.as_entire_binding(),
                            },
                        ],
                    });
                self.gpu_mut().batch_bind_group = Some(bind_group);
            }
            let passes: [(&wgpu::ComputePipeline, u32); 4] = [
                (&self.batch_clear_pipeline, (2 * mesh_count).div_ceil(64)),
                (&self.batch_count_pipeline, object_count.max(1).div_ceil(64)),
                (&self.batch_build_pipeline, 1),
                (
                    &self.batch_assign_pipeline,
                    object_count.max(1).div_ceil(64),
                ),
            ];
            for (pipeline, dispatch) in passes {
                let mut pass = context
                    .encoder
                    .begin_compute_pass(&wgpu::ComputePassDescriptor {
                        label: Some("Skinned Mesh Batch Pass"),
                        timestamp_writes: None,
                    });
                pass.set_pipeline(pipeline);
                pass.set_bind_group(0, self.gpu().batch_bind_group.as_ref().unwrap(), &[]);
                pass.dispatch_workgroups(dispatch, 1, 1);
            }
        }

        {
            let object_count = self.state().skinned_entities.len() as u32;
            let mut cull_pass = context
                .encoder
                .begin_compute_pass(&wgpu::ComputePassDescriptor {
                    label: Some("Skinned Mesh Cull Pass"),
                    timestamp_writes: None,
                });
            cull_pass.set_pipeline(&self.cull_pipeline);
            cull_pass.set_bind_group(0, &self.gpu().cull_bind_group, &[]);
            cull_pass.dispatch_workgroups(object_count.div_ceil(64), 1, 1);
        }

        {
            let dispatch_x = super::super::types::CLUSTER_GRID_X.div_ceil(8);
            let dispatch_y = super::super::types::CLUSTER_GRID_Y.div_ceil(8);
            let dispatch_z = super::super::types::CLUSTER_GRID_Z;
            {
                let mut compute_pass =
                    context
                        .encoder
                        .begin_compute_pass(&wgpu::ComputePassDescriptor {
                            label: Some("Skinned Cluster Bounds Pass"),
                            timestamp_writes: None,
                        });
                compute_pass.set_pipeline(&self.cluster_bounds_pipeline);
                compute_pass.set_bind_group(0, &self.gpu().cluster_bounds_bind_group, &[]);
                compute_pass.dispatch_workgroups(dispatch_x, dispatch_y, dispatch_z);
            }

            let light_grid_size = (std::mem::size_of::<super::super::types::LightGrid>()
                * super::super::types::TOTAL_CLUSTERS as usize)
                as u64;
            context.encoder.copy_buffer_to_buffer(
                &self.gpu().light_grid_reset_buffer,
                0,
                &self.gpu().light_grid_buffer,
                0,
                light_grid_size,
            );

            if let Some(cluster_assign_bind_group) = self.gpu().cluster_assign_bind_group.as_ref() {
                let mut compute_pass =
                    context
                        .encoder
                        .begin_compute_pass(&wgpu::ComputePassDescriptor {
                            label: Some("Skinned Cluster Light Assignment Pass"),
                            timestamp_writes: None,
                        });
                compute_pass.set_pipeline(&self.cluster_assign_pipeline);
                compute_pass.set_bind_group(0, cluster_assign_bind_group, &[]);
                compute_pass.dispatch_workgroups(dispatch_x, dispatch_y, dispatch_z);
            }
        }

        let (color_view, color_load, color_store) = context.get_color_attachment("color")?;
        let (depth_view, depth_load, depth_store) = context.get_depth_attachment("depth")?;
        let (shadow_depth_view, _, _) = context.get_depth_attachment("shadow_depth")?;
        let (spotlight_shadow_atlas_view, _, _) =
            context.get_depth_attachment("spotlight_shadow_atlas")?;
        let (entity_id_view, entity_id_load, _entity_id_store) =
            context.get_color_attachment("entity_id")?;

        let world_state = self.state();
        let brdf_view = world_state
            .ibl_brdf_lut_view
            .as_ref()
            .or(self.brdf_lut_view.as_ref())
            .unwrap_or(&self.dummy_white_view);
        let irradiance_view = world_state
            .ibl_irradiance_view
            .as_ref()
            .or(self.irradiance_map_view.as_ref())
            .unwrap_or(&self.dummy_cube_view);
        let prefiltered_view = world_state
            .ibl_prefiltered_view
            .as_ref()
            .or(self.prefiltered_env_view.as_ref())
            .unwrap_or(&self.dummy_cube_view);
        let irradiance_b_view = world_state
            .ibl_irradiance_b_view
            .as_ref()
            .or(self.irradiance_b_view.as_ref())
            .unwrap_or(&self.dummy_cube_view);
        let prefiltered_b_view = world_state
            .ibl_prefiltered_b_view
            .as_ref()
            .or(self.prefiltered_b_view.as_ref())
            .unwrap_or(&self.dummy_cube_view);

        self.shadow_bind_group = context
            .device
            .create_bind_group(&wgpu::BindGroupDescriptor {
                label: Some("Skinned Mesh Shadow Bind Group"),
                layout: &self.shadow_bind_group_layout,
                entries: &[
                    wgpu::BindGroupEntry {
                        binding: 0,
                        resource: wgpu::BindingResource::TextureView(shadow_depth_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 1,
                        resource: wgpu::BindingResource::Sampler(&self.shadow_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 2,
                        resource: wgpu::BindingResource::TextureView(spotlight_shadow_atlas_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 3,
                        resource: wgpu::BindingResource::Sampler(&self.shadow_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 4,
                        resource: self.spotlight_shadow_buffer.as_entire_binding(),
                    },
                    wgpu::BindGroupEntry {
                        binding: 5,
                        resource: wgpu::BindingResource::TextureView(brdf_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 6,
                        resource: wgpu::BindingResource::Sampler(&self.ibl_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 7,
                        resource: wgpu::BindingResource::TextureView(irradiance_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 8,
                        resource: wgpu::BindingResource::Sampler(&self.ibl_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 9,
                        resource: wgpu::BindingResource::TextureView(prefiltered_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 10,
                        resource: wgpu::BindingResource::Sampler(&self.ibl_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 11,
                        resource: wgpu::BindingResource::TextureView(
                            &self.point_shadow_cubemap_view,
                        ),
                    },
                    wgpu::BindGroupEntry {
                        binding: 12,
                        resource: wgpu::BindingResource::Sampler(&self.point_shadow_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 13,
                        resource: self.point_shadow_buffer.as_entire_binding(),
                    },
                    wgpu::BindGroupEntry {
                        binding: 14,
                        resource: wgpu::BindingResource::TextureView(irradiance_b_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 15,
                        resource: wgpu::BindingResource::TextureView(prefiltered_b_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 16,
                        resource: wgpu::BindingResource::TextureView(&self.dummy_white_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 17,
                        resource: wgpu::BindingResource::Sampler(&self.dummy_sampler),
                    },
                ],
            });

        if self.material_bind_group.is_none() && self.material_bindless_max.is_none() {
            self.rebuild_material_bind_group(context.device);
        }
        let material_bind_group = self.material_bind_group.clone().unwrap();

        if !self.state().opaque_draw_groups.is_empty() {
            let mut render_pass = context
                .encoder
                .begin_render_pass(&wgpu::RenderPassDescriptor {
                    label: Some("Skinned Mesh Opaque Pass"),
                    color_attachments: &[
                        Some(wgpu::RenderPassColorAttachment {
                            view: color_view,
                            resolve_target: None,
                            ops: wgpu::Operations {
                                load: color_load,
                                store: color_store,
                            },
                            depth_slice: None,
                        }),
                        Some(wgpu::RenderPassColorAttachment {
                            view: entity_id_view,
                            resolve_target: None,
                            ops: wgpu::Operations {
                                load: entity_id_load,
                                store: wgpu::StoreOp::Store,
                            },
                            depth_slice: None,
                        }),
                    ],
                    depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
                        view: depth_view,
                        depth_ops: Some(wgpu::Operations {
                            load: depth_load,
                            store: depth_store,
                        }),
                        stencil_ops: None,
                    }),
                    timestamp_writes: None,
                    occlusion_query_set: None,
                    multiview_mask: None,
                });

            render_pass.set_pipeline(&self.render_pipeline);
            render_pass.set_bind_group(0, &self.uniform_bind_group, &[]);
            render_pass.set_bind_group(1, &self.gpu().instance_bind_group, &[]);
            render_pass.set_bind_group(3, &self.shadow_bind_group, &[]);

            render_pass.set_vertex_buffer(0, self.skinned_vertex_buffer.slice(..));
            render_pass.set_index_buffer(
                self.skinned_index_buffer.slice(..),
                wgpu::IndexFormat::Uint32,
            );

            render_pass.set_bind_group(2, &material_bind_group, &[]);
            let opaque_command_count = self.state().opaque_draw_groups.len() as u32;
            if opaque_command_count > 0 {
                render_pass.multi_draw_indexed_indirect(
                    &self.gpu().indirect_commands_buffer,
                    0,
                    opaque_command_count,
                );
            }

            drop(render_pass);
        }

        if !self.state().transparent_draw_groups.is_empty() {
            let (width, height) = context.get_texture_size("color")?;
            self.resize_oit_textures(context.device, width, height);

            {
                let mut blend_opaque_prepass =
                    context
                        .encoder
                        .begin_render_pass(&wgpu::RenderPassDescriptor {
                            label: Some("Skinned Mesh Blend Opaque Depth Prepass"),
                            color_attachments: &[],
                            depth_stencil_attachment: Some(
                                wgpu::RenderPassDepthStencilAttachment {
                                    view: depth_view,
                                    depth_ops: Some(wgpu::Operations {
                                        load: wgpu::LoadOp::Load,
                                        store: wgpu::StoreOp::Store,
                                    }),
                                    stencil_ops: None,
                                },
                            ),
                            timestamp_writes: None,
                            occlusion_query_set: None,
                            multiview_mask: None,
                        });

                blend_opaque_prepass.set_pipeline(&self.blend_opaque_depth_prepass_pipeline);
                blend_opaque_prepass.set_bind_group(0, &self.uniform_bind_group, &[]);
                blend_opaque_prepass.set_bind_group(1, &self.gpu().instance_bind_group, &[]);
                blend_opaque_prepass.set_bind_group(3, &self.shadow_bind_group, &[]);

                blend_opaque_prepass.set_vertex_buffer(0, self.skinned_vertex_buffer.slice(..));
                blend_opaque_prepass.set_index_buffer(
                    self.skinned_index_buffer.slice(..),
                    wgpu::IndexFormat::Uint32,
                );

                blend_opaque_prepass.set_bind_group(2, &material_bind_group, &[]);
                let transparent_command_count = self.state().transparent_draw_groups.len() as u32;
                if transparent_command_count > 0 {
                    blend_opaque_prepass.multi_draw_indexed_indirect(
                        &self.gpu().indirect_commands_buffer,
                        self.transparent_indirect_offset(),
                        transparent_command_count,
                    );
                }

                drop(blend_opaque_prepass);
            }

            {
                let mut oit_pass = context
                    .encoder
                    .begin_render_pass(&wgpu::RenderPassDescriptor {
                        label: Some("Skinned Mesh OIT Pass"),
                        color_attachments: &[
                            Some(wgpu::RenderPassColorAttachment {
                                view: &self.oit_accum_view,
                                resolve_target: None,
                                ops: wgpu::Operations {
                                    load: wgpu::LoadOp::Clear(wgpu::Color {
                                        r: 0.0,
                                        g: 0.0,
                                        b: 0.0,
                                        a: 0.0,
                                    }),
                                    store: wgpu::StoreOp::Store,
                                },
                                depth_slice: None,
                            }),
                            Some(wgpu::RenderPassColorAttachment {
                                view: &self.oit_reveal_view,
                                resolve_target: None,
                                ops: wgpu::Operations {
                                    load: wgpu::LoadOp::Clear(wgpu::Color {
                                        r: 1.0,
                                        g: 0.0,
                                        b: 0.0,
                                        a: 1.0,
                                    }),
                                    store: wgpu::StoreOp::Store,
                                },
                                depth_slice: None,
                            }),
                            Some(wgpu::RenderPassColorAttachment {
                                view: entity_id_view,
                                resolve_target: None,
                                ops: wgpu::Operations {
                                    load: wgpu::LoadOp::Load,
                                    store: wgpu::StoreOp::Store,
                                },
                                depth_slice: None,
                            }),
                        ],
                        depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
                            view: depth_view,
                            depth_ops: Some(wgpu::Operations {
                                load: wgpu::LoadOp::Load,
                                store: wgpu::StoreOp::Store,
                            }),
                            stencil_ops: None,
                        }),
                        timestamp_writes: None,
                        occlusion_query_set: None,
                        multiview_mask: None,
                    });

                oit_pass.set_pipeline(&self.oit_render_pipeline);
                oit_pass.set_bind_group(0, &self.uniform_bind_group, &[]);
                oit_pass.set_bind_group(1, &self.gpu().instance_bind_group, &[]);
                oit_pass.set_bind_group(3, &self.shadow_bind_group, &[]);

                oit_pass.set_vertex_buffer(0, self.skinned_vertex_buffer.slice(..));
                oit_pass.set_index_buffer(
                    self.skinned_index_buffer.slice(..),
                    wgpu::IndexFormat::Uint32,
                );

                oit_pass.set_bind_group(2, &material_bind_group, &[]);
                let transparent_command_count = self.state().transparent_draw_groups.len() as u32;
                if transparent_command_count > 0 {
                    oit_pass.multi_draw_indexed_indirect(
                        &self.gpu().indirect_commands_buffer,
                        self.transparent_indirect_offset(),
                        transparent_command_count,
                    );
                }
            }

            let oit_composite_bind_group =
                context
                    .device
                    .create_bind_group(&wgpu::BindGroupDescriptor {
                        label: Some("Skinned Mesh OIT Composite Bind Group"),
                        layout: &self.oit_composite_bind_group_layout,
                        entries: &[
                            wgpu::BindGroupEntry {
                                binding: 0,
                                resource: wgpu::BindingResource::TextureView(&self.oit_accum_view),
                            },
                            wgpu::BindGroupEntry {
                                binding: 1,
                                resource: wgpu::BindingResource::Sampler(&self.oit_sampler),
                            },
                            wgpu::BindGroupEntry {
                                binding: 2,
                                resource: wgpu::BindingResource::TextureView(&self.oit_reveal_view),
                            },
                            wgpu::BindGroupEntry {
                                binding: 3,
                                resource: wgpu::BindingResource::Sampler(&self.oit_sampler),
                            },
                        ],
                    });

            let mut composite_pass =
                context
                    .encoder
                    .begin_render_pass(&wgpu::RenderPassDescriptor {
                        label: Some("Skinned Mesh OIT Composite Pass"),
                        color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                            view: color_view,
                            resolve_target: None,
                            ops: wgpu::Operations {
                                load: wgpu::LoadOp::Load,
                                store: wgpu::StoreOp::Store,
                            },
                            depth_slice: None,
                        })],
                        depth_stencil_attachment: None,
                        timestamp_writes: None,
                        occlusion_query_set: None,
                        multiview_mask: None,
                    });

            composite_pass.set_pipeline(&self.oit_composite_pipeline);
            composite_pass.set_bind_group(0, &oit_composite_bind_group, &[]);
            composite_pass.draw(0..3, 0..1);
        }

        Ok(vec![])
    }
}
