export const description = `
Validation tests for the WGSL immediate address space.
`;

import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { keysOf } from '../../../../common/util/data_tables.js';
import { ShaderValidationTest } from '../shader_validation_test.js';

import { skipIfImmediateDataNotSupported } from './util.js';

export const g = makeTestGroup(ShaderValidationTest);

const kImmediateFeature = 'immediate_address_space' as const;
const kImmediateHeader = `requires ${kImmediateFeature};`;

const kValidStoreTypes = {
  u32: { enable: ``, prelude: ``, type: `u32` },
  i32: { enable: ``, prelude: ``, type: `i32` },
  f32: { enable: ``, prelude: ``, type: `f32` },
  f16: { enable: `enable f16;`, prelude: ``, type: `f16` },
  vec2u: { enable: ``, prelude: ``, type: `vec2u` },
  vec3i: { enable: ``, prelude: ``, type: `vec3i` },
  vec4f: { enable: ``, prelude: ``, type: `vec4f` },
  vec3h: { enable: `enable f16;`, prelude: ``, type: `vec3h` },
  mat2x2f: { enable: ``, prelude: ``, type: `mat2x2f` },
  struct_numeric: { enable: ``, prelude: `struct S { a : u32, b : vec4f }`, type: `S` },
} as const;

const kInvalidStoreTypes = {
  bool: { enable: ``, prelude: ``, type: `bool` },
  vec2_bool: { enable: ``, prelude: ``, type: `vec2<bool>` },
  atomic_u32: { enable: ``, prelude: ``, type: `atomic<u32>` },
  ptr_function_u32: { enable: ``, prelude: ``, type: `ptr<function, u32>` },
  sampler: { enable: ``, prelude: ``, type: `sampler` },
  sampler_comparison: { enable: ``, prelude: ``, type: `sampler_comparison` },
  texture_2d: { enable: ``, prelude: ``, type: `texture_2d<f32>` },
  runtime_array: { enable: ``, prelude: ``, type: `array<u32>` },
  fixed_array: { enable: ``, prelude: ``, type: `array<u32, 4>` },
  struct_runtime_array: { enable: ``, prelude: `struct S { data : array<u32> }`, type: `S` },
  struct_fixed_array: {
    enable: ``,
    prelude: `struct S { data : array<vec4u, 4> }`,
    type: `S`,
  },
} as const;

g.test('store_type,valid')
  .desc('Validates immediate store types supported by the current WGSL immediate implementation.')
  .params(u => u.combine('type', keysOf(kValidStoreTypes)))
  .fn(t => {
    skipIfImmediateDataNotSupported(t);
    const testcase = kValidStoreTypes[t.params.type];
    if (testcase.enable.includes('f16')) {
      t.skipIfDeviceDoesNotHaveFeature('shader-f16');
    }
    const wgsl = `
${kImmediateHeader}
${testcase.enable}
${testcase.prelude}
var<immediate> data : ${testcase.type};
@compute @workgroup_size(1)
fn main() {
  _ = data;
}`;
    t.expectCompileResult(true, wgsl);
  });

g.test('store_type,invalid')
  .desc('Validates types that cannot be used for immediate variables.')
  .params(u => u.combine('type', keysOf(kInvalidStoreTypes)))
  .fn(t => {
    skipIfImmediateDataNotSupported(t);
    const testcase = kInvalidStoreTypes[t.params.type];
    const wgsl = `
${kImmediateHeader}
${testcase.enable}
${testcase.prelude}
var<immediate> data : ${testcase.type};
@compute @workgroup_size(1)
fn main() {
  _ = data;
}`;
    t.expectCompileResult(false, wgsl);
  });

g.test('scope')
  .desc('Validates that immediate variables cannot be declared at function scope.')
  .params(u => u.combine('addressSpace', ['function', 'immediate'] as const))
  .fn(t => {
    skipIfImmediateDataNotSupported(t);
    const wgsl = `
${kImmediateHeader}
@compute @workgroup_size(1)
fn main() {
  var<${t.params.addressSpace}> data : u32;
  _ = data;
}`;
    t.expectCompileResult(t.params.addressSpace === 'function', wgsl);
  });

g.test('binding_attributes')
  .desc('Validates that @group and @binding are not allowed on immediate variables.')
  .params(u =>
    u.combine('group', ['', '@group(0)'] as const).combine('binding', ['', '@binding(0)'] as const)
  )
  .fn(t => {
    skipIfImmediateDataNotSupported(t);
    const wgsl = `
${kImmediateHeader}
${t.params.group} ${t.params.binding} var<immediate> data : u32;
@compute @workgroup_size(1)
fn main() {
  _ = data;
}`;
    t.expectCompileResult(t.params.group === '' && t.params.binding === '', wgsl);
  });

g.test('access_mode')
  .desc('Validates that immediate variables cannot spell an access mode.')
  .params(u => u.combine('accessMode', ['', 'read', 'write', 'read_write'] as const))
  .fn(t => {
    skipIfImmediateDataNotSupported(t);
    const suffix = t.params.accessMode === '' ? '' : `, ${t.params.accessMode}`;
    const wgsl = `
${kImmediateHeader}
var<immediate${suffix}> data : u32;
@compute @workgroup_size(1)
fn main() {
  _ = data;
}`;
    t.expectCompileResult(t.params.accessMode === '', wgsl);
  });

const kEntryPointCases = {
  one_used: {
    valid: true,
    body: `
var<immediate> a : u32;
@compute @workgroup_size(1)
fn main() {
  _ = a;
}`,
  },
  two_declared_one_used: {
    valid: true,
    body: `
var<immediate> a : u32;
var<immediate> b : u32;
@compute @workgroup_size(1)
fn main() {
  _ = a;
}`,
  },
  two_entry_points_one_each: {
    valid: true,
    body: `
var<immediate> a : u32;
var<immediate> b : u32;
@compute @workgroup_size(1)
fn main_a() {
  _ = a;
}
@compute @workgroup_size(1)
fn main_b() {
  _ = b;
}`,
  },
  one_entry_point_uses_two_directly: {
    valid: false,
    body: `
var<immediate> a : u32;
var<immediate> b : u32;
@compute @workgroup_size(1)
fn main() {
  _ = a + b;
}`,
  },
  one_entry_point_uses_two_through_helper: {
    valid: false,
    body: `
var<immediate> a : u32;
var<immediate> b : u32;
fn read_b() -> u32 {
  return b;
}
@compute @workgroup_size(1)
fn main() {
  _ = a + read_b();
}`,
  },
} as const;

g.test('entry_point_interface')
  .desc('Validates one statically used immediate variable per entry point.')
  .params(u => u.combine('case', keysOf(kEntryPointCases)))
  .fn(t => {
    skipIfImmediateDataNotSupported(t);
    const testcase = kEntryPointCases[t.params.case];
    t.expectCompileResult(testcase.valid, `${kImmediateHeader}\n${testcase.body}`);
  });

const kPointerCases = {
  alias_module_scope: {
    valid: true,
    needsUnrestrictedPointerParameters: false,
    body: `
alias P = ptr<immediate, u32>;
var<immediate> data : u32;
@compute @workgroup_size(1)
fn main() {
  let p : P = &data;
  _ = *p;
}`,
  },
  let_inside_function: {
    valid: true,
    needsUnrestrictedPointerParameters: false,
    body: `
var<immediate> data : u32;
@compute @workgroup_size(1)
fn main() {
  let p : ptr<immediate, u32> = &data;
  _ = *p;
}`,
  },
  write_through_pointer: {
    valid: false,
    needsUnrestrictedPointerParameters: false,
    body: `
var<immediate> data : u32;
@compute @workgroup_size(1)
fn main() {
  let p : ptr<immediate, u32> = &data;
  *p = 1u;
}`,
  },
  pointer_parameter: {
    valid: true,
    needsUnrestrictedPointerParameters: true,
    body: `
var<immediate> data : u32;
fn read_data(p : ptr<immediate, u32>) -> u32 {
  return *p;
}
@compute @workgroup_size(1)
fn main() {
  _ = read_data(&data);
}`,
  },
  explicit_read_access: {
    valid: false,
    needsUnrestrictedPointerParameters: false,
    body: `
alias P = ptr<immediate, u32, read>;`,
  },
  explicit_write_access: {
    valid: false,
    needsUnrestrictedPointerParameters: false,
    body: `
alias P = ptr<immediate, u32, write>;`,
  },
  explicit_read_write_access: {
    valid: false,
    needsUnrestrictedPointerParameters: false,
    body: `
alias P = ptr<immediate, u32, read_write>;`,
  },
  missing_store_type: {
    valid: false,
    needsUnrestrictedPointerParameters: false,
    body: `
alias P = ptr<immediate>;`,
  },
} as const;

g.test('pointers')
  .desc('Validates ptr<immediate> type creation, use, access modes, and function parameters.')
  .params(u => u.combine('case', keysOf(kPointerCases)))
  .fn(t => {
    skipIfImmediateDataNotSupported(t);
    const testcase = kPointerCases[t.params.case];
    const unrestrictedHeader =
      testcase.needsUnrestrictedPointerParameters &&
      t.hasLanguageFeature('unrestricted_pointer_parameters')
        ? 'requires unrestricted_pointer_parameters;\n'
        : '';
    const expected =
      testcase.valid &&
      (!testcase.needsUnrestrictedPointerParameters ||
        t.hasLanguageFeature('unrestricted_pointer_parameters'));
    const wgsl = `${kImmediateHeader}\n${unrestrictedHeader}${testcase.body}`;
    t.expectCompileResult(expected, wgsl);
  });
