export const description = `Validation for phony assignment statements`;

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

export const g = makeTestGroup(ShaderValidationTest);

interface Case {
  value: string;
  pass: boolean;
  usesF16?: boolean;
  gdecl?: string;
}

const kConstructibleTypes = [
  'bool',
  'i32',
  'u32',
  'f32',
  'f16',
  'vec2f',
  'vec3h',
  'vec4u',
  'vec3b',
  'mat2x3f',
  'mat4x2h',
  'abstractInt',
  'abstractFloat',
] as const;

const kConstructibleCases = {
  ...kConstructibleTypes.reduce(
    (acc, t) => ({
      ...acc,
      [t]: {
        value: Type[t].create(1).wgsl(),
        pass: t === 'i32' || t === 'u32' || t === 'abstractInt',
        usesF16: scalarTypeOf(Type[t]).kind === 'f16',
      },
    }),
    {}
  ),
  array: { value: 'array(1,2,3)', pass: false },
  struct: { value: 'S(1,2)', pass: false, gdecl: 'struct S{ a:u32, b:u32}' },
  atomic_u32: { value: 'xu', pass: false, gdecl: 'var<workgroup> xu: atomic<u32>;' },
  atomic_i32: { value: 'xi', pass: false, gdecl: 'var<workgroup> xi: atomic<i32>;' },
} as const;

g.test('var_init_type')
  .desc(`Test increment and decrement of a variable of various types`)
  .params(u => u.combine('type', keysOf(kConstructibleCases)).combine('direction', ['up', 'down']))
  .fn(t => {
    const { value, pass, usesF16, gdecl } = kConstructibleCases[t.params.type] as Case;
    const operator = t.params.direction === 'up' ? '++' : '--';
    const code = `
${usesF16 ? 'enable f16;' : ''}
${gdecl ?? ''}
fn f() {
  var a = ${value};
  a${operator};
}`;
    t.expectCompileResult(pass, code);
  });

interface ComponentCase {
  type: string;
  wgsl: string;
  pass: boolean;
  usesF16?: boolean;
  gdecl?: string;
}

const kComponentCases = {
  v2u_x: { type: 'vec2u', wgsl: 'a.x', pass: true },
  v2u_y: { type: 'vec2u', wgsl: 'a.y', pass: true },
  v3u_x: { type: 'vec3u', wgsl: 'a.x', pass: true },
  v3u_y: { type: 'vec3u', wgsl: 'a.y', pass: true },
  v3u_z: { type: 'vec3u', wgsl: 'a.z', pass: true },
  v4u_x: { type: 'vec4u', wgsl: 'a.x', pass: true },
  v4u_y: { type: 'vec4u', wgsl: 'a.y', pass: true },
  v4u_z: { type: 'vec4u', wgsl: 'a.z', pass: true },
  v4u_w: { type: 'vec4u', wgsl: 'a.w', pass: true },
  v2i_x: { type: 'vec2i', wgsl: 'a.x', pass: true },
  v2i_y: { type: 'vec2i', wgsl: 'a.y', pass: true },
  v3i_x: { type: 'vec3i', wgsl: 'a.x', pass: true },
  v3i_y: { type: 'vec3i', wgsl: 'a.y', pass: true },
  v3i_z: { type: 'vec3i', wgsl: 'a.z', pass: true },
  v4i_x: { type: 'vec4i', wgsl: 'a.x', pass: true },
  v4i_y: { type: 'vec4i', wgsl: 'a.y', pass: true },
  v4i_z: { type: 'vec4i', wgsl: 'a.z', pass: true },
  v4i_w: { type: 'vec4i', wgsl: 'a.w', pass: true },
  v2u_xx: { type: 'vec2u', wgsl: 'a.xx', pass: false },
  v2u_indexed: { type: 'vec2u', wgsl: 'a[0]', pass: true },
  v2f_x: { type: 'vec2f', wgsl: 'a.x', pass: false },
  v2h_x: { type: 'vec2h', wgsl: 'a.x', pass: false, usesF16: true },
  mat2x2f: { type: 'mat2x2f', wgsl: 'a[0][0]', pass: false },
  mat2x2h: { type: 'mat2x2h', wgsl: 'a[0][0]', pass: false, usesF16: true },
  array: { type: 'array<i32,2>', wgsl: 'a', pass: false },
  array_i: { type: 'array<i32,2>', wgsl: 'a[0]', pass: true },
  array_f: { type: 'array<f32,2>', wgsl: 'a[0]', pass: false },
  struct: { type: 'S', wgsl: 'S', pass: false, gdecl: 'struct S{field:i32}' },
  struct_var: { type: 'S', wgsl: 'a', pass: false, gdecl: 'struct S{field:i32}' },
  struct_field: { type: 'S', wgsl: 'a.field', pass: true, gdecl: 'struct S{field:i32}' },
} as const;

g.test('component')
  .desc('Test increment and decrement of component of various types')
  .params(u => u.combine('type', keysOf(kComponentCases)).combine('direction', ['up', 'down']))
  .fn(t => {
    const { type, wgsl, pass, usesF16, gdecl } = kComponentCases[t.params.type] as ComponentCase;
    const operator = t.params.direction === 'up' ? '++' : '--';
    const code = `
${usesF16 ? 'enable f16;' : ''}
${gdecl ?? ''}
fn f() {
  var a: ${type};
  ${wgsl}${operator};
}`;
    t.expectCompileResult(pass, code);
  });

interface ParseCase {
  // Statement in a function to execute.
  wgsl: string;
  // True if the test should pass
  pass: boolean;
  // Module-scope program fragment, if any. Usually a declaration
  gdecl?: string;
}

const kTests = {
  var: { wgsl: 'a++;', pass: true },
  vector: { wgsl: 'v++;', pass: false },
  paren_var_paren: { wgsl: '(a)++;', pass: true },
  star_and_var: { wgsl: '*&a++;', pass: true },
  paren_star_and_var_paren: { wgsl: '(*&a)++;', pass: true },
  many_star_and_var: { wgsl: '*&*&*&a++;', pass: true },

  space: { wgsl: 'a ++;', pass: true },
  tab: { wgsl: 'a\t++;', pass: true },
  newline: { wgsl: 'a\n++;', pass: true },
  cr: { wgsl: 'a\r++;', pass: true },
  space_space: { wgsl: 'a ++ ;', pass: true },
  plus_space_plus: { wgsl: 'a+ +;', pass: false },
  minux_space_minus: { wgsl: 'a- -;', pass: false },

  no_var: { wgsl: '++;', pass: false },
  no_semi: { wgsl: 'a++', pass: false },
  prefix: { wgsl: '++a;', pass: false },

  postfix_x: { wgsl: 'v++.x;', pass: false },
  postfix_r: { wgsl: 'v++.r;', pass: false },
  postfix_index: { wgsl: 'v++[0];', pass: false },
  postfix_field: { wgsl: 'a++.foo;', pass: false },

  literal_i32: { wgsl: '12i++;', pass: false },
  literal_u32: { wgsl: '12u++;', pass: false },
  literal_abstract_int: { wgsl: '12++;', pass: false },
  literal_abstract_float: { wgsl: '12.0++;', pass: false },
  literal_f32: { wgsl: '12.0f++;', pass: false },

  assign_to: { wgsl: 'a++ = 1;', pass: false },

  at_global: { wgsl: '', pass: false, gdecl: 'var<private> g:i32; g++;' },
  private: { wgsl: 'g++;', pass: true, gdecl: 'var<private> g:i32;' },
  workgroup: { wgsl: 'g++;', pass: true, gdecl: 'var<workgroup> g:i32;' },
  storage_rw: {
    wgsl: 'g++;',
    pass: true,
    gdecl: '@group(0) @binding(0) var<storage,read_write> g: i32;',
  },
  storage_r: {
    wgsl: 'g++;',
    pass: false,
    gdecl: '@group(0) @binding(0) var<storage,read> g: i32;',
  },
  storage: { wgsl: 'g++;', pass: false, gdecl: '@group(0) @binding(0) var<storage,read> g: i32;' },
  uniform: { wgsl: 'g++;', pass: false, gdecl: '@group(0) @binding(0) var<uniform> g: i32;' },
  texture: { wgsl: 'g++;', pass: false, gdecl: '@group(0) @binding(0) var g: texture_2d<u32>;' },
  texture_x: {
    wgsl: 'g.x++;',
    pass: false,
    gdecl: '@group(0) @binding(0) var g: texture_2d<u32>;',
  },
  texture_storage: {
    wgsl: 'g++;',
    pass: false,
    gdecl: '@group(0) @binding(0) var g: texture_storage_2d<r32uint>;',
  },
  texture_storage_x: {
    wgsl: 'g.x++;',
    pass: false,
    gdecl: '@group(0) @binding(0) var g: texture_storage_2d<r32uint>;',
  },
  sampler: { wgsl: 'g++;', pass: false, gdecl: '@group(0) @binding(0) var g: sampler;' },
  sampler_comparison: {
    wgsl: 'g++;',
    pass: false,
    gdecl: '@group(0) @binding(0) var g: sampler_comparison;',
  },
  override: { wgsl: 'g++;', pass: false, gdecl: 'override g:i32;' },
  global_const: { wgsl: 'g++;', pass: false, gdecl: 'const g:i32 = 0;' },
  workgroup_atomic: { wgsl: 'g++;', pass: false, gdecl: 'var<workgroup> g:atomic<i32>;' },
  storage_atomic: {
    wgsl: 'g++;',
    pass: false,
    gdecl: '@group(0) @binding(0) var<storage,read_write> g:atomic<u32>;',
  },

  subexpr: { wgsl: 'a = b++;', pass: false },
  expr_paren: { wgsl: '(a++);', pass: false },
  expr_add: { wgsl: '0 + a++;', pass: false },
  expr_negate: { wgsl: '-a++;', pass: false },
  inc_inc: { wgsl: 'a++++;', pass: false },
  inc_space_inc: { wgsl: 'a++ ++;', pass: false },
  inc_dec: { wgsl: 'a++--;', pass: false },
  inc_space_dec: { wgsl: 'a++ --;', pass: false },
  paren_inc: { wgsl: '(a++)++;', pass: false },
  paren_dec: { wgsl: '(a++)--;', pass: false },

  in_block: { wgsl: '{ a++; }', pass: true },
  in_for_init: { wgsl: 'for (a++;false;) {}', pass: true },
  in_for_cond: { wgsl: 'for (;a++;) {}', pass: false },
  in_for_update: { wgsl: 'for (;false;a++) {}', pass: true },
  in_for_update_semi: { wgsl: 'for (;false;a++;) {}', pass: false },
  in_continuing: { wgsl: 'loop { continuing { a++; break if true;}}', pass: true },

  let: { wgsl: 'let c = a; c++;', pass: false },
  const: { wgsl: 'const c = 1; c++;', pass: false },
  builtin: { wgsl: 'max++', pass: false },
  enum: { wgsl: 'r32uint++', pass: false },
  param: { wgsl: '', pass: false, gdecl: 'fn bump(p: i32) { p++;}' },
};

g.test('parse')
  .desc(`Test that increment and decrement statements are parsed correctly.`)
  .params(u => u.combine('test', keysOf(kTests)).combine('direction', ['up', 'down']))
  .fn(t => {
    const c = kTests[t.params.test] as ParseCase;
    let { wgsl, gdecl } = c;
    gdecl = gdecl ?? '';
    if (t.params.direction === 'down') {
      wgsl = wgsl.replace('++', '--');
      gdecl = gdecl.replace('++', '--');
    }

    const code = `
${gdecl}
fn f() {
  var a: u32;
  var b: u32;
  var v: vec4u;
  ${wgsl}
}`;
    t.expectCompileResult(c.pass, code);
  });
