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

'use strict';

const kPoolingOperators = ['averagePool2d', 'l2Pool2d', 'maxPool2d'];

kPoolingOperators.forEach((operatorName) => {
  validateInputFromAnotherBuilder(
      operatorName, {dataType: 'float32', shape: [2, 2, 2, 2]});
});

const label = 'pool_2d_xxx';
const tests = [
  {
    name: 'Test pool2d with default options.',
    input: {dataType: 'float32', shape: [1, 3, 4, 4]},
    output: {dataType: 'float32', shape: [1, 3, 1, 1]}
  },
  {
    name: 'Test pool2d with windowDimensions',
    input: {dataType: 'float16', shape: [1, 3, 4, 4]},
    options: {
      windowDimensions: [3, 3],
    },
    output: {dataType: 'float16', shape: [1, 3, 2, 2]}
  },
  {
    name: 'Test pool2d with padding.',
    input: {dataType: 'float32', shape: [1, 3, 5, 5]},
    options: {
      windowDimensions: [5, 5],
      padding: [2, 2, 2, 2],
    },
    output: {dataType: 'float32', shape: [1, 3, 5, 5]}
  },
  {
    name: 'Test pool2d with strides.',
    input: {dataType: 'float16', shape: [1, 3, 5, 5]},
    options: {
      windowDimensions: [2, 2],
      strides: [2, 2],
    },
    output: {dataType: 'float16', shape: [1, 3, 2, 2]}
  },
  {
    name: 'Test pool2d with strides and padding.',
    input: {dataType: 'float32', shape: [1, 3, 5, 5]},
    options: {
      windowDimensions: [3, 3],
      padding: [1, 1, 1, 1],
      strides: [2, 2],
    },
    output: {dataType: 'float32', shape: [1, 3, 3, 3]}
  },
  {
    name: 'Test pool2d with strides and asymmetric padding.',
    input: {dataType: 'float32', shape: [1, 3, 7, 7]},
    options: {
      windowDimensions: [4, 4],
      padding: [2, 1, 2, 1],
      strides: [2, 2],
    },
    output: {dataType: 'float32', shape: [1, 3, 4, 4]}
  },
  {
    name: 'Test pool2d with strides, padding and roundingType="floor".',
    input: {dataType: 'float32', shape: [1, 3, 7, 7]},
    options: {
      windowDimensions: [4, 4],
      padding: [1, 1, 1, 1],
      strides: [2, 2],
      roundingType: 'floor',
    },
    output: {dataType: 'float32', shape: [1, 3, 3, 3]}
  },
  {
    name: 'Test pool2d with strides, padding and roundingType="ceil".',
    input: {dataType: 'float16', shape: [1, 3, 7, 7]},
    options: {
      windowDimensions: [4, 4],
      padding: [1, 1, 1, 1],
      strides: [2, 2],
      roundingType: 'ceil',
    },
    output: {dataType: 'float16', shape: [1, 3, 4, 4]}
  },
  {
    name: 'Test pool2d with explicit outputSizes ignored roundingType',
    input: {dataType: 'float32', shape: [1, 3, 7, 7]},
    options: {
      windowDimensions: [4, 4],
      padding: [1, 1, 1, 1],
      strides: [2, 2],
      roundingType: 'ceil',
      outputSizes: [3, 3],
    },
    output: {dataType: 'float32', shape: [1, 3, 3, 3]}
  },
  {
    name: 'Test pool2d with strides, padding and outputSizes=[3, 3].',
    input: {dataType: 'float32', shape: [1, 3, 7, 7]},
    options: {
      windowDimensions: [4, 4],
      padding: [1, 1, 1, 1],
      strides: [2, 2],
      outputSizes: [3, 3],
    },
    output: {dataType: 'float32', shape: [1, 3, 3, 3]}
  },
  {
    name: 'Test pool2d with strides, padding and outputSizes=[4, 4].',
    input: {dataType: 'float32', shape: [1, 3, 7, 7]},
    options: {
      windowDimensions: [4, 4],
      padding: [1, 1, 1, 1],
      strides: [2, 2],
      outputSizes: [4, 4],
    },
    output: {dataType: 'float32', shape: [1, 3, 4, 4]}
  },
  {
    name: 'Test pool2d with layout="nchw".',
    input: {dataType: 'float32', shape: [1, 2, 5, 5]},
    options: {
      windowDimensions: [3, 3],
      layout: 'nchw',
    },
    output: {dataType: 'float32', shape: [1, 2, 3, 3]}
  },
  {
    name: 'Test pool2d with layout="nhwc".',
    input: {dataType: 'float16', shape: [1, 5, 5, 2]},
    options: {
      windowDimensions: [3, 3],
      layout: 'nhwc',
    },
    output: {dataType: 'float16', shape: [1, 3, 3, 2]}
  },
  {
    name: 'Throw if the input is not a 4-D tensor.',
    input: {dataType: 'float32', shape: [1, 5, 5]},
    options: {label},
  },
  {
    name: 'Throw if the output sizes is incorrect.',
    input: {dataType: 'float32', shape: [1, 2, 5, 5]},
    options: {
      windowDimensions: [2, 2],
      padding: [2, 2, 2, 2],
      strides: [2, 2],
      outputSizes: [3, 3],
      label: label,
    },
  },
  {
    name: 'Throw if the length of output sizes is not 2.',
    input: {dataType: 'float32', shape: [1, 2, 5, 5]},
    options: {
      windowDimensions: [2, 2],
      padding: [2, 2, 2, 2],
      strides: [2, 2],
      outputSizes: [1, 2, 4, 4],
      label: label,
    },
  },
  {
    name: 'Throw if outputSizes[0] is not greater than 0.',
    input: {dataType: 'float32', shape: [1, 2, 5, 5]},
    options: {
      windowDimensions: [2, 2],
      padding: [2, 2, 2, 2],
      strides: [2, 2],
      outputSizes: [0, 4],
      label: label,
    },
  },
  {
    name: 'Throw if outputSizes[1] is not greater than 0.',
    input: {dataType: 'float32', shape: [1, 2, 5, 5]},
    options: {
      windowDimensions: [2, 2],
      padding: [2, 2, 2, 2],
      strides: [2, 2],
      outputSizes: [4, 0],
      label: label,
    },
  },
  {
    name: 'Throw if the length of window dimensions is not 2.',
    input: {dataType: 'float32', shape: [1, 2, 5, 5]},
    options: {
      windowDimensions: [1, 1, 1, 1],
      label: label,
    },
  },
  {
    name: 'Throw if any window dimension is lesser than 1.',
    input: {dataType: 'float32', shape: [1, 2, 5, 5]},
    options: {
      windowDimensions: [0, 2],
      label: label,
    },
  },
  {
    name:
        'Throw if the input height is too small to fill the pool window height.',
    input: {dataType: 'float32', shape: [1, 2, 5, 5]},
    options: {
      windowDimensions: [8, 2],
      label: label,
    },
  },
  {
    name:
        'Throw if the input width is too small to fill the pool window width.',
    input: {dataType: 'float32', shape: [1, 2, 5, 5]},
    options: {
      windowDimensions: [2, 8],
      label: label,
    },
  },
  {
    name: 'Throw if the calculated output height is equal to 0.',
    input: {dataType: 'float32', shape: [1, 2, 5, 5]},
    options: {
      windowDimensions: [6, 3],
      label: label,
    },
  },
  {
    name: 'Throw if the calculated output width is equal to 0.',
    input: {dataType: 'float32', shape: [1, 2, 5, 5]},
    options: {
      windowDimensions: [3, 6],
      label: label,
    },
  },
  {
    name: 'Throw if the length of padding is not 4.',
    input: {dataType: 'float32', shape: [1, 2, 5, 5]},
    options: {
      padding: [2, 2],
      label: label,
    },
  },
  {
    name: 'Throw if the length of strides is not 2.',
    input: {dataType: 'float32', shape: [1, 2, 5, 5]},
    options: {
      strides: [2],
      label: label,
    },
  },
  {
    name: 'Throw if one stride value is smaller than 1.',
    input: {dataType: 'float32', shape: [1, 2, 5, 5]},
    options: {
      strides: [0, 2],
      label: label,
    },
  },
  {
    name: 'Throw if the length of dilations is not 2.',
    input: {dataType: 'float32', shape: [1, 2, 5, 5]},
    options: {
      dilations: [1, 1, 2],
      label: label,
    },
  },
  {
    name: 'Throw if one dilation value is smaller than 1.',
    input: {dataType: 'float32', shape: [1, 2, 5, 5]},
    options: {
      dilations: [1, 0],
      label: label,
    },
  },
  {
    name: 'Throw if the padding height value is too large',
    input: {dataType: 'float32', shape: [1, 3, 5, 5]},
    options: {
      padding: [kMaxUnsignedLong, kMaxUnsignedLong, 0, 0],
      label: label,
    },
  },
  {
    name: 'Throw if the padding width value is too large',
    input: {dataType: 'float32', shape: [1, 3, 5, 5]},
    options: {
      padding: [0, 0, kMaxUnsignedLong, kMaxUnsignedLong],
      label: label,
    },
  },
];

tests.forEach(
    test => promise_test(async t => {
      const builder = new MLGraphBuilder(context);
      const input = builder.input('input', test.input);
      kPoolingOperators.forEach((operatorName) => {
        if (test.output) {
          const output = builder[operatorName](input, test.options);
          assert_equals(output.dataType, test.output.dataType);
          assert_array_equals(output.shape, test.output.shape);
        } else {
          const regrexp = new RegExp('\\[' + label + '\\]');
          assert_throws_with_label(
              () => builder[operatorName](input, test.options), regrexp);
        }
      });
    }, test.name));

['int32', 'uint32', 'int8', 'uint8'].forEach(
    dataType => promise_test(async t => {
      const builder = new MLGraphBuilder(context);
      const input = builder.input('input', {dataType, shape: [1, 3, 4, 4]});
      const output = builder.maxPool2d(input);
      assert_equals(output.dataType, dataType);
      assert_array_equals(output.shape, [1, 3, 1, 1]);
    }, `[maxPool2d] Test maxPool2d with data type ${dataType}`));

promise_test(async t => {
  const builder = new MLGraphBuilder(context);
  const input =
      builder.input('input', {dataType: 'int64', shape: [1, 2, 3, 3]});
  assert_throws_js(TypeError, () => builder.averagePool2d(input));
}, '[averagePool2d] Throw if the input data type is not floating point');

promise_test(async t => {
  const builder = new MLGraphBuilder(context);
  const input =
      builder.input('input', {dataType: 'uint8', shape: [1, 2, 4, 4]});
  assert_throws_js(TypeError, () => builder.l2Pool2d(input));
}, '[l2Pool2d] Throw if the input data type is not floating point');
