// META: global=window,dedicatedworker
// META: script=/webcodecs/utils.js

const invalidConfigs = [
  {
    comment: 'Missing codec',
    config: {
      sampleRate: 48000,
      numberOfChannels: 2,
    },
  },
  {
    comment: 'Empty codec',
    config: {
      codec: '',
      sampleRate: 48000,
      numberOfChannels: 2,
    },
  },
  {
    comment: 'Missing sampleRate',
    config: {
      codec: 'opus',
      sampleRate: 48000,
    },
  },
  {
    comment: 'Missing numberOfChannels',
    config: {
      codec: 'opus',
      sampleRate: 48000,
    },
  },
  {
    comment: 'Zero sampleRate',
    config: {
      codec: 'opus',
      sampleRate: 0,
      numberOfChannels: 2,
    },
  },
  {
    comment: 'Zero channels',
    config: {
      codec: 'opus',
      sampleRate: 8000,
      numberOfChannels: 0,
    },
  },
  {
    comment: 'Bit rate too big',
    config: {
      codec: 'opus',
      sampleRate: 8000,
      numberOfChannels: 2,
      bitrate: 6e9,
    },
  },
  {
    comment: 'Bit rate present but equal to zero',
    config: {
      codec: 'opus',
      sampleRate: 8000,
      numberOfChannels: 2,
      bitrate: 0,
    },
  },
  {
    comment: 'Opus complexity too big',
    config: {
      codec: 'opus',
      sampleRate: 8000,
      numberOfChannels: 2,
      opus: {
        complexity: 11,
      },
    },
  },
  {
    comment: 'Opus packetlossperc too big',
    config: {
      codec: 'opus',
      sampleRate: 8000,
      numberOfChannels: 2,
      opus: {
        packetlossperc: 101,
      },
    },
  },
  {
    comment: 'Opus frame duration too small',
    config: {
      codec: 'opus',
      sampleRate: 8000,
      numberOfChannels: 2,
      opus: {
        frameDuration: 0,
      },
    },
  },
  {
    comment: 'Opus frame duration too big',
    config: {
      codec: 'opus',
      sampleRate: 8000,
      numberOfChannels: 2,
      opus: {
        frameDuration: 120 * 1000 + 1,
      },
    },
  },
  {
    comment: 'Invalid Opus frameDuration',
    config: {
      codec: 'opus',
      sampleRate: 8000,
      numberOfChannels: 2,
      opus: {
        frameDuration: 2501,
      },
    },
  },
  {
    comment: 'Bitrate is too low for Opus',
    config: {
      codec: 'opus',
      sampleRate: 48000,
      numberOfChannels: 2,
      bitrate: 1,
    },
  },
];

invalidConfigs.forEach(entry => {
  promise_test(
      t => {
        return promise_rejects_js(
            t, TypeError, AudioEncoder.isConfigSupported(entry.config));
      },
      'Test that AudioEncoder.isConfigSupported() rejects invalid config: ' +
          entry.comment);
});

invalidConfigs.forEach(entry => {
  async_test(
      t => {
        let codec = new AudioEncoder(getDefaultCodecInit(t));
        assert_throws_js(TypeError, () => {
          codec.configure(entry.config);
        });
        t.done();
      },
      'Test that AudioEncoder.configure() rejects invalid config: ' +
          entry.comment);
});

const validButUnsupportedConfigs = [
  {
    comment: 'Unrecognized codec',
    config: {
      codec: 'bogus',
      sampleRate: 48000,
      numberOfChannels: 2,
    },
  },
  {
    comment: 'Sample rate is too small',
    config: {
      codec: 'opus',
      sampleRate: 1,
      numberOfChannels: 2,
    },
  },
  {
    comment: 'Sample rate is too large',
    config: {
      codec: 'opus',
      sampleRate: 10000000,
      numberOfChannels: 2,
    },
  },
  {
    comment: 'Way too many channels',
    config: {
      codec: 'opus',
      sampleRate: 8000,
      numberOfChannels: 1024,
      bitrate: 128000,
    },
  },
  {
    comment: 'Possible future opus codec string',
    config: {
      codec: 'opus.123',
      sampleRate: 48000,
      numberOfChannels: 2,
    }
  },
  {
    comment: 'Possible future aac codec string',
    config: {
      codec: 'mp4a.FF.9',
      sampleRate: 48000,
      numberOfChannels: 2,
    }
  },
  {
    comment: 'codec with spaces',
    config: {
      codec: '  opus  ',
      sampleRate: 48000,
      numberOfChannels: 2,
    }
  },
];

validButUnsupportedConfigs.forEach(entry => {
  promise_test(
      async t => {
        let support = await AudioEncoder.isConfigSupported(entry.config);
        assert_false(support.supported);

        let config = support.config;
        assert_equals(config.codec, entry.config.codec);
        assert_equals(config.sampleRate, entry.config.sampleRate);
        assert_equals(config.numberOfChannels, entry.config.numberOfChannels);
      },
      'Test that AudioEncoder.isConfigSupported() doesn\'t support config: ' +
          entry.comment);
});

validButUnsupportedConfigs.forEach(entry => {
  promise_test(
      t => {
        let isErrorCallbackCalled = false;
        let codec = new AudioEncoder({
          output: t.unreached_func('unexpected output'),
          error: t.step_func_done(e => {
            isErrorCallbackCalled = true;
            assert_true(e instanceof DOMException);
            assert_equals(e.name, 'NotSupportedError');
            assert_equals(codec.state, 'closed', 'state');
          })
        });
        codec.configure(entry.config);
        return codec.flush()
            .then(t.unreached_func('flush succeeded unexpectedly'))
            .catch(t.step_func(e => {
              assert_true(isErrorCallbackCalled, "isErrorCallbackCalled");
              assert_true(e instanceof DOMException);
              assert_equals(e.name, 'NotSupportedError');
              assert_equals(codec.state, 'closed', 'state');
            }));
      },
      'Test that AudioEncoder.configure() doesn\'t support config: ' +
          entry.comment);
});

const validConfigs = [
  {
    codec: 'opus',
    sampleRate: 8000,
    numberOfChannels: 1,
  },
  {
    codec: 'opus',
    sampleRate: 48000,
    numberOfChannels: 2,
  },
  {
    codec: 'opus',
    sampleRate: 48000,
    numberOfChannels: 2,
    bitrate: 128000,
    bitrateMode: "constant",
    bogus: 123
  },
  {
    codec: 'opus',
    sampleRate: 48000,
    numberOfChannels: 2,
    bitrate: 128000,
    bitrateMode: "variable",
    bogus: 123
  },
  {
    codec: 'opus',
    sampleRate: 48000,
    numberOfChannels: 2,
    opus: {
      complexity: 5,
      signal: 'music',
      application: 'audio',
      frameDuration: 20000,
      packetlossperc: 10,
      useinbandfec: true,
    },
  },
  {
    codec: 'opus',
    sampleRate: 48000,
    numberOfChannels: 2,
    opus: {
      format: 'opus',
      signal: 'voice',
      application: 'lowdelay',
      complexity: 10,
      frameDuration: 60000,
      packetlossperc: 20,  // Irrelevant without useinbandfec, but still valid.
      usedtx: true,
      bogus: 456,
    },
  },
  {
    codec: 'opus',
    sampleRate: 48000,
    numberOfChannels: 2,
    opus: {},  // Use default values.
  },
];

validConfigs.forEach(config => {
  promise_test(async t => {
    let support = await AudioEncoder.isConfigSupported(config);
    assert_true(support.supported);

    let new_config = support.config;
    assert_equals(new_config.codec, config.codec);
    assert_equals(new_config.sampleRate, config.sampleRate);
    assert_equals(new_config.numberOfChannels, config.numberOfChannels);
    if (config.bitrate)
      assert_equals(new_config.bitrate, config.bitrate);

    if (config.opus) {
      let opus_config = config.opus;
      let new_opus_config = new_config.opus;

      assert_equals(new_opus_config.format, opus_config.format ?? 'opus');
      assert_equals(
          new_opus_config.frameDuration, opus_config.frameDuration ?? 20000);
      assert_equals(
          new_opus_config.packetlossperc, opus_config.packetlossperc ?? 0);
      assert_equals(
          new_opus_config.useinbandfec, opus_config.useinbandfec ?? false);
      assert_equals(new_opus_config.usedtx, opus_config.usedtx ?? false);
      assert_false(new_opus_config.hasOwnProperty('bogus'));

      if (opus_config.complexity) {
        assert_equals(new_opus_config.complexity, opus_config.complexity);
      } else {
        // Default complexity is 5 for mobile/ARM platforms, and 9 otherwise.
        assert_true(
            new_opus_config.complexity == 5 || new_opus_config.complexity == 9);
      }

    } else {
      assert_false(new_config.hasOwnProperty('opus'));
    }

    assert_false(new_config.hasOwnProperty('bogus'));
  }, 'AudioEncoder.isConfigSupported() supports: ' + JSON.stringify(config));
});
