export const description = `
Tests for GPUAdapterInfo.
`;

import { Fixture } from '../../../../common/framework/fixture.js';
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { keysOf } from '../../../../common/util/data_tables.js';
import { getGPU } from '../../../../common/util/navigator_gpu.js';
import { assert, hasFeature, objectEquals } from '../../../../common/util/util.js';
import { isPowerOfTwo } from '../../../util/math.js';

export const g = makeTestGroup(Fixture);

const normalizedIdentifierRegex = /^$|^[a-z0-9]+(-[a-z0-9]+)*$/;

g.test('adapter_info')
  .desc(
    `
  Test that every member in the GPUAdapter.info except description is properly formatted`
  )
  .fn(async t => {
    const gpu = getGPU(t.rec);
    const adapter = await gpu.requestAdapter();
    assert(adapter !== null);

    const adapterInfo = adapter.info;
    assert(adapterInfo instanceof GPUAdapterInfo);

    t.expect(
      normalizedIdentifierRegex.test(adapterInfo.vendor),
      `adapterInfo.vendor should be a normalized identifier. But it's '${adapterInfo.vendor}'`
    );

    t.expect(
      normalizedIdentifierRegex.test(adapterInfo.architecture),
      `adapterInfo.architecture should be a normalized identifier. But it's '${adapterInfo.architecture}'`
    );

    t.expect(
      normalizedIdentifierRegex.test(adapterInfo.device),
      `adapterInfo.device should be a normalized identifier. But it's '${adapterInfo.device}'`
    );
  });

g.test('same_object')
  .desc(
    `
GPUAdapter.info and GPUDevice.adapterInfo provide the same object each time they're accessed,
but different objects from one another.`
  )
  .fn(async t => {
    const gpu = getGPU(t.rec);
    const adapter = await gpu.requestAdapter();
    assert(adapter !== null);
    assert(adapter.info instanceof GPUAdapterInfo);

    const adapterInfo1 = adapter.info;
    const adapterInfo2 = adapter.info;
    t.expect(adapterInfo1 === adapterInfo2, 'adapter.info should obey [SameObject]');

    const device = await t.requestDeviceTracked(adapter);
    assert(device !== null);
    assert(device.adapterInfo instanceof GPUAdapterInfo);

    const deviceAdapterInfo1 = device.adapterInfo;
    const deviceAdapterInfo2 = device.adapterInfo;
    t.expect(
      deviceAdapterInfo1 === deviceAdapterInfo2,
      'device.adapterInfo should obey [SameObject]'
    );

    t.expect(
      adapter.info !== device.adapterInfo,
      'adapter.info and device.adapterInfo should NOT return the same object'
    );
  });

g.test('device_matches_adapter')
  .desc(
    `
Test that GPUDevice.adapterInfo matches GPUAdapter.info. Cases access the members in
different orders to make sure that they are consistent regardless of the access order.`
  )
  .paramsSubcasesOnly(u =>
    u.combine('testDeviceFirst', [true, false]).combine('testMembersFirst', [true, false])
  )
  .fn(async t => {
    const { testDeviceFirst, testMembersFirst } = t.params;

    const gpu = getGPU(t.rec);
    const adapter = await gpu.requestAdapter();
    assert(adapter !== null);

    const device = await t.requestDeviceTracked(adapter);
    assert(device !== null);

    const deviceInfo: unknown[] = [];
    const adapterInfo: unknown[] = [];

    const kGPUAdapterInfoKeys = keysOf(GPUAdapterInfo.prototype);
    if (testMembersFirst) {
      if (testDeviceFirst) {
        assert(device.adapterInfo instanceof GPUAdapterInfo);
        for (const k of kGPUAdapterInfoKeys) {
          deviceInfo.push(device.adapterInfo[k]);
        }
        assert(adapter.info instanceof GPUAdapterInfo);
        for (const k of kGPUAdapterInfoKeys) {
          adapterInfo.push(adapter.info[k]);
        }
      } else {
        assert(adapter.info instanceof GPUAdapterInfo);
        for (const k of kGPUAdapterInfoKeys) {
          adapterInfo.push(adapter.info[k]);
        }
        assert(device.adapterInfo instanceof GPUAdapterInfo);
        for (const k of kGPUAdapterInfoKeys) {
          deviceInfo.push(device.adapterInfo[k]);
        }
      }
    } else {
      if (testDeviceFirst) {
        assert(device.adapterInfo instanceof GPUAdapterInfo);
        assert(adapter.info instanceof GPUAdapterInfo);
        for (const k of kGPUAdapterInfoKeys) {
          deviceInfo.push(device.adapterInfo[k]);
          adapterInfo.push(adapter.info[k]);
        }
      } else {
        assert(adapter.info instanceof GPUAdapterInfo);
        assert(device.adapterInfo instanceof GPUAdapterInfo);
        for (const k of kGPUAdapterInfoKeys) {
          adapterInfo.push(adapter.info[k]);
          deviceInfo.push(device.adapterInfo[k]);
        }
      }
      t.expect(objectEquals(deviceInfo, adapterInfo));
    }
  });

const kSubgroupMinSizeBound = 4;
const kSubgroupMaxSizeBound = 128;

g.test('subgroup_sizes')
  .desc(
    `
Verify GPUAdapterInfo.subgroupMinSize, GPUAdapterInfo.subgroupMaxSize.
If the subgroups feature is supported, they must both exist.
If they exist, they must both exist and be powers of two, and
4 <= subgroupMinSize <= subgroupMaxSize <= 128.
`
  )
  .fn(async t => {
    const gpu = getGPU(t.rec);
    const adapter = await gpu.requestAdapter();
    assert(adapter !== null);
    const { subgroupMinSize, subgroupMaxSize } = adapter.info;
    // Once 'subgroups' lands, the properties should be defined with default values 4 and 128
    // when adapter does not support the feature.
    // https://github.com/gpuweb/gpuweb/pull/4963
    if (hasFeature(adapter.features, 'subgroups')) {
      t.expect(
        subgroupMinSize !== undefined,
        'GPUAdapterInfo.subgroupMinSize must exist when subgroups supported'
      );
      t.expect(
        subgroupMaxSize !== undefined,
        'GPUAdapterInfo.subgroupMaxSize must exist when subgroups supported'
      );
    }
    t.expect(
      (subgroupMinSize === undefined) === (subgroupMinSize === undefined),
      'GPUAdapterInfo.subgropuMinSize and GPUAdapterInfo.subgroupMaxSize must both be defined, or neither should be'
    );
    if (subgroupMinSize !== undefined && subgroupMaxSize !== undefined) {
      t.expect(isPowerOfTwo(subgroupMinSize));
      t.expect(isPowerOfTwo(subgroupMaxSize));
      t.expect(kSubgroupMinSizeBound <= subgroupMinSize);
      t.expect(subgroupMinSize <= subgroupMaxSize);
      t.expect(subgroupMaxSize <= kSubgroupMaxSizeBound);
    }
  });
