//! Tests for buffer usages validation.

use wgpu::BufferAddress;
use wgpu::{BufferUsages as Bu, MapMode as Ma};
use wgpu_test::{fail_if, gpu_test, GpuTestConfiguration, TestParameters, TestingContext};

const BUFFER_SIZE: BufferAddress = 1234;

const ALWAYS_VALID: &[Bu; 4] = &[
    Bu::MAP_READ,
    Bu::MAP_WRITE,
    Bu::MAP_READ.union(Bu::COPY_DST),
    Bu::MAP_WRITE.union(Bu::COPY_SRC),
];
// MAP_READ can only be paired with COPY_DST and MAP_WRITE can only be paired with COPY_SRC
// (unless Features::MAPPABlE_PRIMARY_BUFFERS is enabled).
const NEEDS_MAPPABLE_PRIMARY_BUFFERS: &[Bu; 7] = &[
    Bu::MAP_READ.union(Bu::COPY_DST.union(Bu::COPY_SRC)),
    Bu::MAP_WRITE.union(Bu::COPY_SRC.union(Bu::COPY_DST)),
    Bu::MAP_READ.union(Bu::MAP_WRITE),
    Bu::MAP_WRITE.union(Bu::MAP_READ),
    Bu::MAP_READ.union(Bu::COPY_DST.union(Bu::STORAGE)),
    Bu::MAP_WRITE.union(Bu::COPY_SRC.union(Bu::STORAGE)),
    // these two require acceleration_structures feature
    Bu::all().intersection(Bu::BLAS_INPUT.union(Bu::TLAS_INPUT).complement()),
];
const INVALID_BITS: Bu = Bu::from_bits_retain(0b1111111111111);
const ALWAYS_FAIL: &[Bu; 2] = &[Bu::empty(), INVALID_BITS];

fn try_create(ctx: TestingContext, usages: &[(bool, &[wgpu::BufferUsages])]) {
    for (expect_validation_error, usage) in usages
        .iter()
        .flat_map(|&(expect_error, usages)| usages.iter().copied().map(move |u| (expect_error, u)))
    {
        fail_if(
            &ctx.device,
            expect_validation_error,
            || {
                let _buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor {
                    label: None,
                    size: BUFFER_SIZE,
                    usage,
                    mapped_at_creation: false,
                });
            },
            None,
        );
    }
}

#[gpu_test]
static BUFFER_USAGE: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| {
    try_create(
        ctx,
        &[
            (false, ALWAYS_VALID),
            (true, NEEDS_MAPPABLE_PRIMARY_BUFFERS),
            (true, ALWAYS_FAIL),
        ],
    );
});

#[gpu_test]
static BUFFER_USAGE_MAPPABLE_PRIMARY_BUFFERS: GpuTestConfiguration = GpuTestConfiguration::new()
    .parameters(TestParameters::default().features(wgpu::Features::MAPPABLE_PRIMARY_BUFFERS))
    .run_sync(|ctx| {
        try_create(
            ctx,
            &[
                (false, ALWAYS_VALID),
                (false, NEEDS_MAPPABLE_PRIMARY_BUFFERS),
                (true, ALWAYS_FAIL),
            ],
        );
    });

async fn map_test(
    ctx: &TestingContext,
    usage_type: &str,
    map_mode_type: Ma,
    before_unmap: bool,
    before_destroy: bool,
    after_unmap: bool,
    after_destroy: bool,
) {
    log::info!("map_test usage_type:{usage_type} map_mode_type:{:?} before_unmap:{before_unmap} before_destroy:{before_destroy} after_unmap:{after_unmap} after_destroy:{after_destroy}", map_mode_type);

    let size = 8;
    let usage = match usage_type {
        "read" => Bu::COPY_DST | Bu::MAP_READ,
        "write" => Bu::COPY_SRC | Bu::MAP_WRITE,
        _ => Bu::from_bits(0).unwrap(),
    };
    let buffer_creation_validation_error = usage.is_empty();

    let mut buffer = None;

    fail_if(
        &ctx.device,
        buffer_creation_validation_error,
        || {
            buffer = Some(ctx.device.create_buffer(&wgpu::BufferDescriptor {
                label: None,
                size,
                usage,
                mapped_at_creation: false,
            }));
        },
        None,
    );
    if buffer_creation_validation_error {
        return;
    }

    let buffer = buffer.unwrap();

    let map_async_validation_error = buffer_creation_validation_error
        || (map_mode_type == Ma::Read && !usage.contains(Bu::MAP_READ))
        || (map_mode_type == Ma::Write && !usage.contains(Bu::MAP_WRITE));

    fail_if(
        &ctx.device,
        map_async_validation_error,
        || {
            buffer.slice(0..size).map_async(map_mode_type, |_| {});
        },
        None,
    );

    if map_async_validation_error {
        return;
    }

    if before_unmap {
        buffer.unmap();
    }

    if before_destroy {
        buffer.destroy();
    }

    ctx.async_poll(wgpu::PollType::wait()).await.unwrap();

    if !before_unmap && !before_destroy {
        {
            let view = buffer.slice(0..size).get_mapped_range();
            assert!(!view.is_empty());
        }

        if after_unmap {
            buffer.unmap();
        }

        if after_destroy {
            buffer.destroy();
        }
    }
}

#[gpu_test]
static BUFFER_MAP_ASYNC_MAP_STATE: GpuTestConfiguration = GpuTestConfiguration::new()
    .parameters(TestParameters::default().features(wgpu::Features::MAPPABLE_PRIMARY_BUFFERS))
    .run_async(move |ctx| async move {
        for usage_type in ["invalid", "read", "write"] {
            for map_mode_type in [Ma::Read, Ma::Write] {
                for before_unmap in [false, true] {
                    for before_destroy in [false, true] {
                        for after_unmap in [false, true] {
                            for after_destroy in [false, true] {
                                map_test(
                                    &ctx,
                                    usage_type,
                                    map_mode_type,
                                    before_unmap,
                                    before_destroy,
                                    after_unmap,
                                    after_destroy,
                                )
                                .await
                            }
                        }
                    }
                }
            }
        }
    });
