// META: global=window,dedicatedworker

promise_test(async t => {
  let fmt = 'RGBA';
  const rgb_plane = [
    0xBA, 0xDF, 0x00, 0xD0, 0xBA, 0xDF, 0x01, 0xD0, 0xBA, 0xDF, 0x02, 0xD0,
    0xBA, 0xDF, 0x03, 0xD0
  ];
  let data = new Uint8Array(rgb_plane);
  let unused_buffer = new ArrayBuffer(123);
  let init = {
    format: fmt,
    timestamp: 1234,
    codedWidth: 2,
    codedHeight: 2,
    visibleRect: {x: 0, y: 0, width: 2, height: 2},
    transfer: [data.buffer, unused_buffer]
  };
  assert_equals(data.length, 16, 'data.length');
  assert_equals(unused_buffer.byteLength, 123, 'unused_buffer.byteLength');

  let frame = new VideoFrame(data, init);
  assert_equals(frame.format, fmt, 'format');
  assert_equals(data.length, 0, 'data.length after detach');
  assert_equals(unused_buffer.byteLength, 0, 'unused_buffer after detach');

  const options = {
    rect: {x: 0, y: 0, width: init.codedWidth, height: init.codedHeight}
  };
  let size = frame.allocationSize(options);
  let output_data = new Uint8Array(size);
  let layout = await frame.copyTo(output_data, options);
  let expected_data = new Uint8Array(rgb_plane);
  assert_equals(expected_data.length, size, 'expected_data size');
  for (let i = 0; i < size; i++) {
    assert_equals(expected_data[i], output_data[i], `expected_data[${i}]`);
  }

  frame.close();
}, 'Test transfering ArrayBuffer to VideoFrame');


promise_test(async t => {
  let fmt = 'I420';
  const i420_planes = [0xAA, 0xBB, 0xCC];
  let data = new Uint8Array(i420_planes);
  let init = {
    format: fmt,
    timestamp: 1234,
    codedWidth: 1,
    codedHeight: 1,
    visibleRect: {x: 0, y: 0, width: 1, height: 1},
    transfer: [data.buffer]
  };

  let frame = new VideoFrame(data, init);
  assert_equals(data.length, 0, 'data.length after detach');

  const options = {
    rect: {x: 0, y: 0, width: init.codedWidth, height: init.codedHeight}
  };
  let size = frame.allocationSize(options);
  let output_data = new Uint8Array(size);
  let layout = await frame.copyTo(output_data, options);
  let expected_data = new Uint8Array(i420_planes);
  assert_equals(expected_data.length, size, 'expected_data size');
  assert_equals(layout[0].stride, 1, 'layout[0].stride');
  assert_equals(layout[0].offset, 0, 'layout[0].offset');
  assert_equals(layout[1].stride, 1, 'layout[1].stride');
  assert_equals(layout[1].offset, 1, 'layout[1].offset');
  assert_equals(layout[2].stride, 1, 'layout[2].stride');
  assert_equals(layout[2].offset, 2, 'layout[2].offset');
  assert_equals(expected_data.length, size, 'expected_data size');
  for (let i = 0; i < size; i++) {
    assert_equals(expected_data[i], output_data[i], `expected_data[${i}]`);
  }

  frame.close();
}, 'Test transfering buffers to VideoFrame with uneven samples');

promise_test(async t => {
  const rgb_plane = [
    0xBA, 0xDF, 0x00, 0xD0, 0xBA, 0xDF, 0x01, 0xD0, 0xBA, 0xDF, 0x02, 0xD0,
    0xBA, 0xDF, 0x03, 0xD0
  ];
  let data = new Uint8Array(rgb_plane);
  let detached_buffer = new ArrayBuffer(123);

  // Detach `detached_buffer`
  structuredClone({x: detached_buffer}, {transfer: [detached_buffer]});

  let init = {
    format: 'RGBA',
    timestamp: 1234,
    codedWidth: 2,
    codedHeight: 2,
    visibleRect: {x: 0, y: 0, width: 2, height: 2},
    transfer: [data.buffer, detached_buffer]
  };

  try {
    new VideoFrame(data, init);
  } catch (error) {
    assert_equals(error.name, 'DataCloneError', 'error.name');
  }
  // `data.buffer` didn't get detached
  assert_equals(data.length, 16, 'data.length');
}, 'Test transfering detached buffer to VideoFrame');


promise_test(async t => {
  const rgb_plane = [
    0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE,
    0xEE, 0xEE, 0xEE, 0xEE
  ];
  const padding_size = 6;
  let arraybuffer = new ArrayBuffer(padding_size + 16 /* pixels */);
  let data = new Uint8Array(arraybuffer, padding_size);
  data.set(rgb_plane);

  let init = {
    format: 'RGBA',
    timestamp: 1234,
    codedWidth: 2,
    codedHeight: 2,
    visibleRect: {x: 0, y: 0, width: 2, height: 2},
    transfer: [arraybuffer]
  };

  let frame = new VideoFrame(data, init);
  assert_equals(data.length, 0, 'data.length after detach');
  assert_equals(arraybuffer.byteLength, 0, 'arraybuffer after detach');

  const options = {
    rect: {x: 0, y: 0, width: init.codedWidth, height: init.codedHeight}
  };
  let size = frame.allocationSize(options);
  let output_data = new Uint8Array(size);
  let layout = await frame.copyTo(output_data, options);
  for (let i = 0; i < size; i++) {
    assert_equals(output_data[i], 0xEE, `output_data[${i}]`);
  }
}, 'Test transfering view of an ArrayBuffer to VideoFrame');

promise_test(async t => {
  const rgb_plane = [
    0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE,
    0xEE, 0xEE, 0xEE, 0xEE
  ];
  const padding_size = 6;
  let arraybuffer = new ArrayBuffer(padding_size + 16 /* pixels */);
  let data = new Uint8Array(arraybuffer, padding_size);
  data.set(rgb_plane);

  let init = {
    format: 'RGBA',
    timestamp: 1234,
    codedWidth: 2,
    codedHeight: 2,
    visibleRect: {x: 0, y: 0, width: 2, height: 2},
    transfer: [arraybuffer, arraybuffer]
  };

  try {
    new VideoFrame(data, init);
  } catch (error) {
    assert_equals(error.name, 'DataCloneError', 'error.name');
  }
  // `data.buffer` didn't get detached
  assert_equals(data.length, 16, 'data.length');
}, 'Test transfering same array buffer twice');

promise_test(async t => {
  const bytes = [ 0xBA, 0xDF, 0x00, 0xD0, 0xBA, 0xDF, 0x01, 0xD0, 0xBA, 0xDF ];
  let data = new Uint8Array(bytes);
  let unused_buffer = new ArrayBuffer(123);
  let init = {
    type: 'key',
    timestamp: 0,
    data: data,
    transfer: [data.buffer, unused_buffer]
  };

  assert_equals(data.length, 10, 'data.length');
  assert_equals(unused_buffer.byteLength, 123, 'unused_buffer.byteLength');

  let chunk = new EncodedAudioChunk(init);
  assert_equals(data.length, 0, 'data.length after detach');
  assert_equals(unused_buffer.byteLength, 0, 'unused_buffer after detach');

  let output_data = new Uint8Array(chunk.byteLength);
  chunk.copyTo(output_data);
  let expected_data = new Uint8Array(bytes);
  assert_equals(expected_data.length, chunk.byteLength, 'expected_data size');
  for (let i = 0; i < chunk.byteLength; i++) {
    assert_equals(expected_data[i], output_data[i], `expected_data[${i}]`);
  }
}, 'Test transfering ArrayBuffer to EncodedAudioChunk');

promise_test(async t => {
  const bytes = [ 0xBA, 0xDF, 0x00, 0xD0, 0xBA, 0xDF, 0x01, 0xD0, 0xBA, 0xDF ];
  let data = new Uint8Array(bytes);
  let unused_buffer = new ArrayBuffer(123);
  let init = {
    type: 'key',
    timestamp: 0,
    data: data,
    transfer: [data.buffer, unused_buffer]
  };

  assert_equals(data.length, 10, 'data.length');
  assert_equals(unused_buffer.byteLength, 123, 'unused_buffer.byteLength');

  let chunk = new EncodedVideoChunk(init);
  assert_equals(data.length, 0, 'data.length after detach');
  assert_equals(unused_buffer.byteLength, 0, 'unused_buffer after detach');

  let output_data = new Uint8Array(chunk.byteLength);
  chunk.copyTo(output_data);
  let expected_data = new Uint8Array(bytes);
  assert_equals(expected_data.length, chunk.byteLength, 'expected_data size');
  for (let i = 0; i < chunk.byteLength; i++) {
    assert_equals(expected_data[i], output_data[i], `expected_data[${i}]`);
  }
}, 'Test transfering ArrayBuffer to EncodedVideoChunk');

promise_test(async t => {
  const bytes = [0xBA, 0xDF, 0x00, 0xD0, 0xBA, 0xDF, 0x01, 0xD0, 0xBA, 0xDF];
  let data = new Uint8Array(bytes);
  let unused_buffer = new ArrayBuffer(123);
  let init = {
    type: 'key',
    timestamp: 0,
    numberOfFrames: data.length,
    numberOfChannels: 1,
    sampleRate: 10000,
    format: 'u8',
    data: data,
    transfer: [data.buffer, unused_buffer]
  };

  assert_equals(data.length, 10, 'data.length');
  assert_equals(unused_buffer.byteLength, 123, 'unused_buffer.byteLength');

  let audio_data = new AudioData(init);
  assert_equals(data.length, 0, 'data.length after detach');
  assert_equals(unused_buffer.byteLength, 0, 'unused_buffer after detach');

  let readback_data = new Uint8Array(bytes.length);
  audio_data.copyTo(readback_data, {planeIndex: 0, format: 'u8'});
  let expected_data = new Uint8Array(bytes);
  for (let i = 0; i < expected_data.length; i++) {
    assert_equals(expected_data[i], readback_data[i], `expected_data[${i}]`);
  }
}, 'Test transfering ArrayBuffer to AudioData');

promise_test(async t => {
  let sample_rate = 48000;
  let total_duration_s = 1;
  let data_count = 10;
  let chunks = [];

  let encoder_init = {
    error: t.unreached_func('Encoder error'),
    output: (chunk, metadata) => {
      chunks.push(chunk);
    }
  };
  let encoder = new AudioEncoder(encoder_init);
  let config = {
    codec: 'opus',
    sampleRate: sample_rate,
    numberOfChannels: 2,
    bitrate: 256000,  // 256kbit
  };
  encoder.configure(config);

  let timestamp_us = 0;
  const data_duration_s = total_duration_s / data_count;
  const frames = data_duration_s * config.sampleRate;
  for (let i = 0; i < data_count; i++) {
    let buffer = new Float32Array(frames * config.numberOfChannels);
    let data = new AudioData({
      timestamp: timestamp_us,
      data: buffer,
      numberOfChannels: config.numberOfChannels,
      numberOfFrames: frames,
      sampleRate: config.sampleRate,
      format: 'f32-planar',
      transfer: [buffer.buffer]
    });
    timestamp_us += data_duration_s * 1_000_000;
    assert_equals(buffer.length, 0, 'buffer.length after detach');
    encoder.encode(data);
  }
  await encoder.flush();
  encoder.close();
  assert_greater_than(chunks.length, 0);
}, 'Encoding from AudioData with transferred buffer');


promise_test(async t => {
  let unused_buffer = new ArrayBuffer(123);
  let support = await ImageDecoder.isTypeSupported('image/png');
  assert_implements_optional(
      support, 'Optional codec image/png not supported.');
  let buffer = await fetch('four-colors.png').then(response => {
    return response.arrayBuffer();
  });

  let decoder = new ImageDecoder(
      {data: buffer, type: 'image/png', transfer: [buffer, unused_buffer]});
  assert_equals(buffer.byteLength, 0, 'buffer.byteLength after detach');
  assert_equals(unused_buffer.byteLength, 0, 'unused_buffer after detach');

  let result = await decoder.decode();
  assert_equals(result.image.displayWidth, 320);
  assert_equals(result.image.displayHeight, 240);
}, 'Test transfering ArrayBuffer to ImageDecoder.');
