export const description = `
Validation tests for let declarations
`;

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

export const g = makeTestGroup(ShaderValidationTest);

interface Case {
  code: string;
  valid: boolean | 'texture_and_sampler_let' | 'buffer_view';
  decls?: string;
}

const kTypeCases: Record<string, Case> = {
  bool: {
    code: `let x : bool = true;`,
    valid: true,
  },
  i32: {
    code: `let x : i32 = 1i;`,
    valid: true,
  },
  u32: {
    code: `let x : u32 = 1u;`,
    valid: true,
  },
  f32: {
    code: `let x : f32 = 1f;`,
    valid: true,
  },
  f16: {
    code: `let x : f16 = 1h;`,
    valid: true,
  },
  vec2i: {
    code: `let x : vec2i = vec2i();`,
    valid: true,
  },
  vec3u: {
    code: `let x : vec3u = vec3u();`,
    valid: true,
  },
  vec4f: {
    code: `let x : vec4f = vec4f();`,
    valid: true,
  },
  mat2x2: {
    code: `let x : mat2x2f = mat2x2f();`,
    valid: true,
  },
  mat4x3f: {
    code: `let x : mat4x3<f32> = mat4x3<f32>();`,
    valid: true,
  },
  array_sized: {
    code: `let x : array<u32, 4> = array(1,2,3,4);`,
    valid: true,
  },
  array_runtime: {
    code: `let x : array<u32> = array(1,2,3);`,
    valid: false,
  },
  struct: {
    code: `let x : S = S(0);`,
    valid: true,
    decls: `struct S { x : u32 }`,
  },
  atomic: {
    code: `let x : atomic<u32> = 0;`,
    valid: false,
  },
  ptr_function: {
    code: `
      var x : i32;
      let y : ptr<function, i32> = &x;`,
    valid: true,
  },
  ptr_storage: {
    code: `let y : ptr<storage, i32> = &x[0];`,
    valid: true,
    decls: `@group(0) @binding(0) var<storage> x : array<i32, 4>;`,
  },
  load_rule: {
    code: `
      var x : i32 = 1;
      let y : i32 = x;`,
    valid: true,
  },
  texture_2d: {
    code: `let x = tex2d;`,
    valid: 'texture_and_sampler_let',
    decls: `@group(0) @binding(0) var tex2d : texture_2d<f32>;`,
  },
  texture_storage_1d: {
    code: `let x : texture_storage_1d<rgba32float, write> = tex1d;`,
    valid: 'texture_and_sampler_let',
    decls: `@group(0) @binding(0) var tex1d : texture_storage_1d<rgba32float, write>;`,
  },
  sampler: {
    code: `let s = samp;`,
    valid: 'texture_and_sampler_let',
    decls: `@group(0) @binding(0) var samp : sampler;`,
  },
  sampler_comparison: {
    code: `let s : sampler_comparison = samp_comp;`,
    valid: 'texture_and_sampler_let',
    decls: `@group(0) @binding(0) var samp_comp : sampler_comparison;`,
  },
  buffer: {
    code: `let x : buffer = b;`,
    valid: false,
    decls: `@group(0) @binding(0) var<storage> b : buffer;`,
  },
  buffer_sized: {
    code: `let x : buffer<128> = b;`,
    valid: false,
    decls: `@group(0) @binding(0) var<uniform> b : buffer<128>;`,
  },
  buffer_override_sized: {
    code: `let x : buffer<o> = b;`,
    valid: false,
    decls: `override o = 16u; var<workgroup> b : buffer<o>;`,
  },
  ptr_buffer: {
    code: `let x : ptr<storage, buffer> = &b;`,
    valid: 'buffer_view',
    decls: `@group(0) @binding(0) var<storage> b : buffer;`,
  },
  ptr_buffer_sized: {
    code: `let x : ptr<uniform, buffer<128>> = &b;`,
    valid: 'buffer_view',
    decls: `@group(0) @binding(0) var<uniform> b : buffer<128>;`,
  },
  ptr_buffer_override_sized: {
    code: `let x : ptr<workgroup, buffer<o>> = &b;`,
    valid: 'buffer_view',
    decls: `override o = 16u; var<workgroup> b : buffer<o>;`,
  },
};

g.test('type')
  .desc('Test let types')
  .params(u => u.combine('case', keysOf(kTypeCases)))
  .fn(t => {
    if (t.params.case === 'f16') {
      t.skipIfDeviceDoesNotHaveFeature('shader-f16');
    }
    const testcase = kTypeCases[t.params.case];

    const code = `
${t.params.case === 'f16' ? 'enable f16;' : ''}
${testcase.decls ?? ''}
fn foo() {
  ${testcase.code}
}`;
    let expect: boolean = testcase.valid === true;
    if (testcase.valid === 'texture_and_sampler_let') {
      expect = t.hasLanguageFeature('texture_and_sampler_let');
    } else if (testcase.valid === 'buffer_view') {
      expect = t.hasLanguageFeature('buffer_view');
    }
    t.expectCompileResult(expect, code);
  });

const kInitCases: Record<string, Case> = {
  no_init: {
    code: `let x : u32;`,
    valid: false,
  },
  no_type: {
    code: `let x = 1;`,
    valid: true,
  },
  init_matching_type: {
    code: `let x : u32 = 1u;`,
    valid: true,
  },
  init_mismatch_type: {
    code: `let x : u32 = 1i;`,
    valid: false,
  },
  ptr_type_mismatch: {
    code: `var x : i32;\nlet y : ptr<function, u32> = &x;`,
    valid: false,
  },
  ptr_access_mismatch: {
    code: `let y : ptr<storage, u32, read> = &x;`,
    valid: false,
    decls: `@group(0) @binding(0) var<storage, read_write> x : u32;`,
  },
  ptr_addrspace_mismatch: {
    code: `let y = ptr<storage, u32> = &x;`,
    valid: false,
    decls: `@group(0) @binding(0) var<uniform> x : u32;`,
  },
  init_const_expr: {
    code: `let y = x * 2;`,
    valid: true,
    decls: `const x = 1;`,
  },
  init_override_expr: {
    code: `let y = x + 1;`,
    valid: true,
    decls: `override x = 1;`,
  },
  init_runtime_expr: {
    code: `var x = 1;\nlet y = x << 1;`,
    valid: true,
  },
  buffer_mismatch: {
    code: `let x : ptr<uniform, buffer> = &b;`,
    valid: false,
    decls: `@group(0) @binding(0) var<uniform> b : buffer<128>;`,
  },
  buffer_smaller_size: {
    code: `let x : ptr<uniform, buffer<64>> = &b;`,
    valid: false,
    decls: `@group(0) @binding(0) var<uniform> b : buffer<128>;`,
  },
  buffer_mismatched_overrides: {
    code: `let p : ptr<workgroup, buffer<o1>> = &b;`,
    valid: false,
    decls: `override o1 : u32;\noverride o2 : u32;\nvar<workgroup> b : buffer<o2>;`,
  },
};

g.test('initializer')
  .desc('Test let initializers')
  .params(u => u.combine('case', keysOf(kInitCases)))
  .fn(t => {
    const testcase = kInitCases[t.params.case];
    const code = `
${testcase.decls ?? ''}
fn foo() {
  ${testcase.code}
}`;
    const expect = testcase.valid;
    t.expectCompileResult(expect === true, code);
  });

g.test('module_scope')
  .desc('Test that let declarations are disallowed module scope')
  .fn(t => {
    const code = `let x = 0;`;
    t.expectCompileResult(false, code);
  });

const kTestTypes = [
  'f32',
  'i32',
  'u32',
  'bool',
  'vec2<f32>',
  'vec2<i32>',
  'vec2<u32>',
  'vec2<bool>',
  'vec3<f32>',
  'vec3<i32>',
  'vec3<u32>',
  'vec3<bool>',
  'vec4<f32>',
  'vec4<i32>',
  'vec4<u32>',
  'vec4<bool>',
  'mat2x2<f32>',
  'mat2x3<f32>',
  'mat2x4<f32>',
  'mat3x2<f32>',
  'mat3x3<f32>',
  'mat3x4<f32>',
  'mat4x2<f32>',
  'mat4x3<f32>',
  'mat4x4<f32>',
  // [1]: 12 is a random number here. find a solution to replace it.
  'array<f32, 12>',
  'array<i32, 12>',
  'array<u32, 12>',
  'array<bool, 12>',
] as const;

g.test('initializer_type')
  .desc(
    `
  If present, the initializer's type must match the store type of the variable.
  Testing scalars, vectors, and matrices of every dimension and type.
  TODO: add test for: structs - arrays of vectors and matrices - arrays of different length
`
  )
  .params(u => u.beginSubcases().combine('lhsType', kTestTypes).combine('rhsType', kTestTypes))
  .fn(t => {
    const { lhsType, rhsType } = t.params;

    const code = `
      @fragment
      fn main() {
        let a : ${lhsType} = ${rhsType}();
      }
    `;

    const expectation = lhsType === rhsType;
    t.expectCompileResult(expectation, code);
  });
