// META: title=validation tests for WebNN API prelu operation
// META: global=window,dedicatedworker
// META: variant=?cpu
// META: variant=?gpu
// META: variant=?npu
// META: script=../resources/utils_validation.js

'use strict';

const label = 'dequantize_linear_123';
const regrexp = new RegExp('\\[' + label + '\\]');
const tests = [
  {
    name:
        '[dequantizeLinear] Test scale\'s shape = [3, 2, 5] and zeroPoint\'s shape = [3, 2, 5] which is the same as input\'s shape.',
    input: {dataType: 'int8', shape: [3, 2, 5]},
    scale: {dataType: 'float32', shape: [3, 2, 5]},
    zeroPoint: {dataType: 'int8', shape: [3, 2, 5]},
    output: {dataType: 'float32', shape: [3, 2, 5]},
  },
  {
    name:
        '[dequantizeLinear] Test scale\'s shape = [5] and zeroPoint\'s shape = [5] which is unidirectionally broadcastable to input\'s shape.',
    input: {dataType: 'int8', shape: [3, 2, 5]},
    scale: {dataType: 'float32', shape: [5]},
    zeroPoint: {dataType: 'int8', shape: [5]},
    output: {dataType: 'float32', shape: [3, 2, 5]},
  },
  {
    name:
        '[dequantizeLinear] Test scale\'s shape = [] and zeroPoint\'s shape = [] which is unidirectionally broadcastable to input\'s shape.',
    input: {dataType: 'uint8', shape: [3, 2, 5]},
    scale: {dataType: 'float32', shape: []},
    zeroPoint: {dataType: 'uint8', shape: []},
    output: {dataType: 'float32', shape: [3, 2, 5]},
  },
  {
    name:
        '[dequantizeLinear] Test block-wise quantization with block_size = [2, 2, 5].',
    input: {dataType: 'uint8', shape: [6, 4, 5]},
    scale: {dataType: 'float32', shape: [3, 2, 1]},
    zeroPoint: {dataType: 'uint8', shape: [3, 2, 1]},
    output: {dataType: 'float32', shape: [6, 4, 5]},
  },
  {
    name:
        '[dequantizeLinear] Throw if the scale size is not a factor of input size.',
    input: {dataType: 'uint8', shape: [3, 2, 5]},
    scale: {dataType: 'float32', shape: [2]},
    zeroPoint: {dataType: 'uint8', shape: [2]},
  },
  {
    name:
        '[dequantizeLinear] Throw if the shape of zero_point is not the same as the shape of input.',
    input: {dataType: 'uint8', shape: [3, 2, 5]},
    scale: {dataType: 'float32', shape: [5]},
    zeroPoint: {dataType: 'uint8', shape: [2]},
  },
  {
    name:
        '[dequantizeLinear] Throw if the data type of zeroPoint is not the same as the data type of input.',
    input: {dataType: 'int8', shape: [3, 2, 5]},
    scale: {dataType: 'float32', shape: [5]},
    zeroPoint: {dataType: 'uint8', shape: [5]},
  },
  {
    name:
        '[dequantizeLinear] Throw if the data type of input is not one of {int4, uint4, int8, uint8}.',
    input: {dataType: 'float16', shape: [3, 2, 5]},
    scale: {dataType: 'float32', shape: [5]},
    zeroPoint: {dataType: 'int8', shape: [5]},
  },
  {
    name:
        '[dequantizeLinear] Throw if the data type of zero_point is not one of {int4, uint4, int8, uint8}.',
    input: {dataType: 'int8', shape: [3, 2, 5]},
    scale: {dataType: 'float32', shape: [5]},
    zeroPoint: {dataType: 'int32', shape: [5]},
  },
  {
    name: '[dequantizeLinear] Throw if the data type of scale is float32.',
    input: {dataType: 'uint8', shape: [3, 2, 5]},
    scale: {dataType: 'int32', shape: [5]},
    zeroPoint: {dataType: 'uint8', shape: [5]},
  },
];

tests.forEach(
    test => promise_test(async t => {
      const builder = new MLGraphBuilder(context);
      const input = builder.input('input', test.input);
      const scale = builder.input('scale', test.scale);
      const zeroPoint = builder.input('zeroPoint', test.zeroPoint);
      if (test.output) {
        const output = builder.dequantizeLinear(input, scale, zeroPoint);
        assert_equals(output.dataType, test.output.dataType);
        assert_array_equals(output.shape, test.output.shape);
      } else {
        const options = {label};
        assert_throws_with_label(
            () => builder.dequantizeLinear(input, scale, zeroPoint, options),
            regrexp);
      }
    }, test.name));

const kExampleInputDescriptor = {
  dataType: 'int8',
  shape: [2, 4]
};
const kExampleScaleDescriptor = {
  dataType: 'float32',
  shape: [2, 4]
};
multi_builder_test(async (t, builder, otherBuilder) => {
  const inputFromOtherBuilder =
      otherBuilder.input('input', kExampleInputDescriptor);

  const scale = builder.input('scale', kExampleScaleDescriptor);
  const zeroPoint = builder.input('zeroPoint', kExampleInputDescriptor);
  assert_throws_js(
      TypeError,
      () => builder.dequantizeLinear(inputFromOtherBuilder, scale, zeroPoint));
}, '[dequantizeLinear] throw if input is from another builder');

multi_builder_test(async (t, builder, otherBuilder) => {
  const scaleFromOtherBuilder =
      otherBuilder.input('scale', kExampleScaleDescriptor);

  const input = builder.input('input', kExampleInputDescriptor);
  const zeroPoint = builder.input('zeroPoint', kExampleInputDescriptor);
  assert_throws_js(
      TypeError,
      () => builder.dequantizeLinear(input, scaleFromOtherBuilder, zeroPoint));
}, '[dequantizeLinear] throw if scale is from another builder');

multi_builder_test(async (t, builder, otherBuilder) => {
  const zeroPointFromOtherBuilder =
      otherBuilder.input('zeroPoint', kExampleInputDescriptor);

  const input = builder.input('input', kExampleInputDescriptor);
  const scale = builder.input('scale', kExampleScaleDescriptor);
  assert_throws_js(
      TypeError,
      () => builder.dequantizeLinear(input, scale, zeroPointFromOtherBuilder));
}, '[dequantizeLinear] throw if zeroPoint is from another builder');

promise_test(async t => {
  const builder = new MLGraphBuilder(context);

  const input = builder.input('input', {
      dataType: 'int8',
      shape: [context.opSupportLimits().maxTensorByteLength / 5, 5]});
  const scale = builder.input('scale', {dataType: 'float32', shape: [5]});
  const zeroPoint = builder.input('zeroPoint', {dataType: 'int8', shape: [5]});

  const options = {label};
  assert_throws_with_label(
      () => builder.dequantizeLinear(input, scale, zeroPoint, options), regrexp);
}, '[dequantizeLinear] throw if the output tensor byte length exceeds limit');
