// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Flags: --harmony-rab-gsab --harmony-rab-gsab-transfer --allow-natives-syntax

function TestTransfer(method) {
  assertEquals(0, ArrayBuffer.prototype[method].length);
  assertEquals(method, ArrayBuffer.prototype[method].name);

  function AssertDetached(ab) {
    assertTrue(ab.detached);
    assertEquals(0, ab.byteLength);
    assertThrows(() => (new Uint8Array(ab)).sort(), TypeError);
  }

  function AssertResizable(originalResizable, newResizable) {
    if (method === 'transfer') {
      assertEquals(originalResizable, newResizable);
    } else {
      assertFalse(newResizable);
    }
  }

  const IndicesAndValuesForTesting = [[1, 4], [4, 18], [17, 255], [518, 48]];
  function WriteTestData(ab) {
    let u8 = new Uint8Array(ab);
    for (let [idx, val] of IndicesAndValuesForTesting) {
      u8[idx] = val;
    }
  }
  function AssertBufferContainsTestData(ab) {
    let u8 = new Uint8Array(ab);
    for (let [idx, val] of IndicesAndValuesForTesting) {
      if (idx < u8.length) {
        assertEquals(val, u8[idx]);
      }
    }
  }

  function TestSameLength(len, opts) {
    let ab = new ArrayBuffer(len, opts);
    WriteTestData(ab);
    const resizable = ab.resizable;
    const xfer = ab[method]();
    assertEquals(len, xfer.byteLength);
    AssertResizable(resizable, xfer.resizable);
    AssertBufferContainsTestData(xfer);
    AssertDetached(ab);
  }
  TestSameLength(1024);
  TestSameLength(1024, {maxByteLength: 2048});

  function TestGrow(len, opts) {
    let ab = new ArrayBuffer(len, opts);
    const resizable = ab.resizable;
    WriteTestData(ab);
    const newLen = len * 2 + 128;  // +128 to ensure newLen is never 0
    const xfer = ab[method](newLen);
    assertEquals(newLen, xfer.byteLength);
    AssertResizable(resizable, xfer.resizable);
    if (len > 0) AssertBufferContainsTestData(xfer);
    AssertDetached(ab);

    // The new memory should be zeroed.
    let u8 = new Uint8Array(xfer);
    for (let i = len; i < newLen; i++) {
      assertEquals(0, u8[i]);
    }
  }
  TestGrow(1024);
  TestGrow(0);
  if (method === 'transfer') {
    // Cannot transfer to a new byte length > max byte length.
    assertThrows(() => TestGrow(1024, {maxByteLength: 2048}), RangeError);
  } else {
    TestGrow(1024, {maxByteLength: 2048});
  }
  TestGrow(0, {maxByteLength: 2048});

  function TestNonGrow(len, opts) {
    for (let newLen
             of [len / 2,  // shrink
                 0         // 0 special case
    ]) {
      let ab = new ArrayBuffer(len, opts);
      WriteTestData(ab);
      const resizable = ab.resizable;
      const xfer = ab[method](newLen);
      assertEquals(newLen, xfer.byteLength);
      AssertResizable(resizable, xfer.resizable);
      if (len > 0) AssertBufferContainsTestData(xfer);
      AssertDetached(ab);
    }
  }
  TestNonGrow(1024);
  TestNonGrow(1024, {maxByteLength: 2048});
  TestNonGrow(0);
  TestNonGrow(0, {maxByteLength: 2048});

  (function TestParameterConversion() {
    const len = 1024;
    {
      let ab = new ArrayBuffer(len);
      const detach = {
        valueOf() {
          %ArrayBufferDetach(ab);
          return len;
        }
      };
      assertThrows(() => ab[method](detach), TypeError);
    }

    {
      let ab = new ArrayBuffer(len, {maxByteLength: len * 4});
      const resizable = ab.resizable;
      const shrink = {
        valueOf() {
          ab.resize(len / 2);
          return len;
        }
      };
      const xfer = ab[method](shrink);
      AssertResizable(resizable, xfer.resizable);
      assertEquals(len, xfer.byteLength);
    }

    {
      let ab = new ArrayBuffer(len, {maxByteLength: len * 4});
      const resizable = ab.resizable;
      const grow = {
        valueOf() {
          ab.resize(len * 2);
          return len;
        }
      };
      const xfer = ab[method](grow);
      AssertResizable(resizable, xfer.resizable);
      assertEquals(len, xfer.byteLength);
    }
  })();

  (function TestCannotBeSAB() {
    const len = 1024;
    let sab = new SharedArrayBuffer(1024);
    let gsab = new SharedArrayBuffer(len, {maxByteLength: len * 4});

    assertThrows(() => ArrayBuffer.prototype[method].call(sab), TypeError);
    assertThrows(() => ArrayBuffer.prototype[method].call(gsab), TypeError);
  })();

  (function TestInvalidLength() {
    for (let newLen of [-1024, Number.MAX_SAFE_INTEGER + 1]) {
      let ab = new ArrayBuffer(1024);
      assertThrows(() => ab[method](newLen), RangeError);
    }
  })();

  (function TestEmptySourceStore() {
    let ab = new ArrayBuffer();
    let xfer = ab[method]()[method](1024);
  })();

  if (typeof WebAssembly !== 'undefined') {
    // WebAssembly buffers cannot be detached.
    const memory = new WebAssembly.Memory({initial: 1});
    assertThrows(() => memory.buffer[method](), TypeError);
  }
}

TestTransfer('transfer');
TestTransfer('transferToFixedLength');
