export const description = `
API validation tests for clearBuffer.
`;

import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { kBufferUsages } from '../../../../capability_info.js';
import { kResourceStates, AllFeaturesMaxLimitsGPUTest } from '../../../../gpu_test.js';
import { kMaxSafeMultipleOf8 } from '../../../../util/math.js';
import * as vtu from '../../validation_test_utils.js';

class F extends AllFeaturesMaxLimitsGPUTest {
  testClearBuffer(options: {
    buffer: GPUBuffer;
    offset: number | undefined;
    size: number | undefined;
    isSuccess: boolean;
  }): void {
    const { buffer, offset, size, isSuccess } = options;

    const commandEncoder = this.device.createCommandEncoder();
    commandEncoder.clearBuffer(buffer, offset, size);

    this.expectValidationError(() => {
      commandEncoder.finish();
    }, !isSuccess);
  }
}

export const g = makeTestGroup(F);

g.test('buffer_state')
  .desc(`Test that clearing an invalid or destroyed buffer fails.`)
  .params(u => u.combine('bufferState', kResourceStates))
  .fn(t => {
    const { bufferState } = t.params;

    const buffer = vtu.createBufferWithState(t, bufferState, {
      size: 8,
      usage: GPUBufferUsage.COPY_DST,
    });

    const commandEncoder = t.device.createCommandEncoder();
    commandEncoder.clearBuffer(buffer, 0, 8);

    if (bufferState === 'invalid') {
      t.expectValidationError(() => {
        commandEncoder.finish();
      });
    } else {
      const cmd = commandEncoder.finish();
      t.expectValidationError(() => {
        t.device.queue.submit([cmd]);
      }, bufferState === 'destroyed');
    }
  });

g.test('buffer,device_mismatch')
  .desc(`Tests clearBuffer cannot be called with buffer created from another device.`)
  .paramsSubcasesOnly(u => u.combine('mismatched', [true, false]))
  .beforeAllSubcases(t => t.usesMismatchedDevice())
  .fn(t => {
    const { mismatched } = t.params;
    const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
    const size = 8;

    const buffer = t.trackForCleanup(
      sourceDevice.createBuffer({
        size,
        usage: GPUBufferUsage.COPY_DST,
      })
    );

    t.testClearBuffer({
      buffer,
      offset: 0,
      size,
      isSuccess: !mismatched,
    });
  });

g.test('default_args')
  .desc(`Test that calling clearBuffer with a default offset and size is valid.`)
  .paramsSubcasesOnly([
    { offset: undefined, size: undefined },
    { offset: 4, size: undefined },
    { offset: undefined, size: 8 },
  ] as const)
  .fn(t => {
    const { offset, size } = t.params;

    const buffer = t.createBufferTracked({
      size: 16,
      usage: GPUBufferUsage.COPY_DST,
    });

    t.testClearBuffer({
      buffer,
      offset,
      size,
      isSuccess: true,
    });
  });

g.test('buffer_usage')
  .desc(`Test that only buffers with COPY_DST usage are valid to use with copyBuffers.`)
  .paramsSubcasesOnly(u =>
    u //
      .combine('usage', kBufferUsages)
  )
  .fn(t => {
    const { usage } = t.params;

    const buffer = t.createBufferTracked({
      size: 16,
      usage,
    });

    t.testClearBuffer({
      buffer,
      offset: 0,
      size: 16,
      isSuccess: usage === GPUBufferUsage.COPY_DST,
    });
  });

g.test('size_alignment')
  .desc(
    `
    Test that the clear size must be 4 byte aligned.
    - Test size is not a multiple of 4.
    - Test size is 0.
    - Test size overflows the buffer size.
    - Test size is omitted.
  `
  )
  .paramsSubcasesOnly([
    { size: 0, _isSuccess: true },
    { size: 2, _isSuccess: false },
    { size: 4, _isSuccess: true },
    { size: 5, _isSuccess: false },
    { size: 8, _isSuccess: true },
    { size: 20, _isSuccess: false },
    { size: undefined, _isSuccess: true },
  ] as const)
  .fn(t => {
    const { size, _isSuccess: isSuccess } = t.params;

    const buffer = t.createBufferTracked({
      size: 16,
      usage: GPUBufferUsage.COPY_DST,
    });

    t.testClearBuffer({
      buffer,
      offset: 0,
      size,
      isSuccess,
    });
  });

g.test('offset_alignment')
  .desc(
    `
    Test that the clear offsets must be 4 byte aligned.
    - Test offset is not a multiple of 4.
    - Test offset is larger than the buffer size.
    - Test offset is omitted.
  `
  )
  .paramsSubcasesOnly([
    { offset: 0, _isSuccess: true },
    { offset: 2, _isSuccess: false },
    { offset: 4, _isSuccess: true },
    { offset: 5, _isSuccess: false },
    { offset: 8, _isSuccess: true },
    { offset: 20, _isSuccess: false },
    { offset: undefined, _isSuccess: true },
  ] as const)
  .fn(t => {
    const { offset, _isSuccess: isSuccess } = t.params;

    const buffer = t.createBufferTracked({
      size: 16,
      usage: GPUBufferUsage.COPY_DST,
    });

    t.testClearBuffer({
      buffer,
      offset,
      size: 8,
      isSuccess,
    });
  });

g.test('overflow')
  .desc(`Test that clears which may cause arithmetic overflows are invalid.`)
  .paramsSubcasesOnly([
    { offset: 0, size: kMaxSafeMultipleOf8 },
    { offset: 16, size: kMaxSafeMultipleOf8 },
    { offset: kMaxSafeMultipleOf8, size: 16 },
    { offset: kMaxSafeMultipleOf8, size: kMaxSafeMultipleOf8 },
  ] as const)
  .fn(t => {
    const { offset, size } = t.params;

    const buffer = t.createBufferTracked({
      size: 16,
      usage: GPUBufferUsage.COPY_DST,
    });

    t.testClearBuffer({
      buffer,
      offset,
      size,
      isSuccess: false,
    });
  });

g.test('out_of_bounds')
  .desc(`Test that clears which exceed the buffer bounds are invalid.`)
  .paramsSubcasesOnly([
    { offset: 0, size: 32, _isSuccess: true },
    { offset: 0, size: 36 },
    { offset: 32, size: 0, _isSuccess: true },
    { offset: 32, size: 4 },
    { offset: 36, size: 4 },
    { offset: 36, size: 0 },
    { offset: 20, size: 16 },
    { offset: 20, size: 12, _isSuccess: true },
  ] as const)
  .fn(t => {
    const { offset, size, _isSuccess = false } = t.params;

    const buffer = t.createBufferTracked({
      size: 32,
      usage: GPUBufferUsage.COPY_DST,
    });

    t.testClearBuffer({
      buffer,
      offset,
      size,
      isSuccess: _isSuccess,
    });
  });
