use wgpu::InstanceFlags;
use wgpu_test::{gpu_test, FailureCase, GpuTestConfiguration, TestParameters};

#[gpu_test]
static OCCLUSION_QUERY: GpuTestConfiguration = GpuTestConfiguration::new()
    .parameters(
        TestParameters::default()
            .expect_fail(FailureCase::webgl2())
            // Ensure timestamp normalization does not interfere with occlusion query results
            .instance_flags(InstanceFlags::AUTOMATIC_TIMESTAMP_NORMALIZATION),
    )
    .run_async(|ctx| async move {
        // Create depth texture
        let depth_texture = ctx.device.create_texture(&wgpu::TextureDescriptor {
            label: Some("Depth texture"),
            size: wgpu::Extent3d {
                width: 64,
                height: 64,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::Depth32Float,
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
            view_formats: &[],
        });
        let depth_texture_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default());

        // Setup pipeline using a simple shader with hardcoded vertices
        let shader = ctx
            .device
            .create_shader_module(wgpu::include_wgsl!("shader.wgsl"));
        let pipeline = ctx
            .device
            .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
                label: Some("Pipeline"),
                layout: None,
                vertex: wgpu::VertexState {
                    module: &shader,
                    entry_point: Some("vs_main"),
                    compilation_options: Default::default(),
                    buffers: &[],
                },
                fragment: None,
                primitive: wgpu::PrimitiveState::default(),
                depth_stencil: Some(wgpu::DepthStencilState {
                    format: wgpu::TextureFormat::Depth32Float,
                    depth_write_enabled: true,
                    depth_compare: wgpu::CompareFunction::Less,
                    stencil: wgpu::StencilState::default(),
                    bias: wgpu::DepthBiasState::default(),
                }),
                multisample: wgpu::MultisampleState::default(),
                multiview: None,
                cache: None,
            });

        // Create occlusion query set
        let query_set = ctx.device.create_query_set(&wgpu::QuerySetDescriptor {
            label: Some("Query set"),
            ty: wgpu::QueryType::Occlusion,
            count: 3,
        });

        let mut encoder = ctx
            .device
            .create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
        {
            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                label: Some("Render pass"),
                color_attachments: &[],
                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
                    view: &depth_texture_view,
                    depth_ops: Some(wgpu::Operations {
                        load: wgpu::LoadOp::Clear(1.0),
                        store: wgpu::StoreOp::Store,
                    }),
                    stencil_ops: None,
                }),
                timestamp_writes: None,
                occlusion_query_set: Some(&query_set),
            });
            render_pass.set_pipeline(&pipeline);

            // Not occluded (z = 1.0, nothing drawn yet)
            render_pass.begin_occlusion_query(0);
            render_pass.draw(4..7, 0..1);
            render_pass.end_occlusion_query();

            // Not occluded (z = 0.0)
            render_pass.begin_occlusion_query(1);
            render_pass.draw(0..3, 0..1);
            render_pass.end_occlusion_query();

            // Occluded (z = 1.0)
            render_pass.begin_occlusion_query(2);
            render_pass.draw(4..7, 0..1);
            render_pass.end_occlusion_query();
        }

        // Resolve query set to buffer
        let query_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Query buffer"),
            size: size_of::<u64>() as u64 * 3,
            usage: wgpu::BufferUsages::QUERY_RESOLVE | wgpu::BufferUsages::COPY_SRC,
            mapped_at_creation: false,
        });
        encoder.resolve_query_set(&query_set, 0..3, &query_buffer, 0);

        let mapping_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Mapping buffer"),
            size: query_buffer.size(),
            usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });
        encoder.copy_buffer_to_buffer(&query_buffer, 0, &mapping_buffer, 0, query_buffer.size());

        ctx.queue.submit(Some(encoder.finish()));

        mapping_buffer
            .slice(..)
            .map_async(wgpu::MapMode::Read, |_| ());
        ctx.async_poll(wgpu::PollType::wait()).await.unwrap();
        let query_buffer_view = mapping_buffer.slice(..).get_mapped_range();
        let query_data: &[u64; 3] = bytemuck::from_bytes(&query_buffer_view);

        // WebGPU only defines query results as zero/non-zero
        assert_ne!(query_data[0], 0);
        assert_ne!(query_data[1], 0);
        assert_eq!(query_data[2], 0);
    });
