//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft Corporation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------

const cliArgs = WScript.Arguments || [];

if (cliArgs.indexOf("--help") !== -1) {
  print("usage: ch memory.js -args [-verbose] -endargs");
  WScript.quit(0);
}

// Parse arguments
let verbose = 0;
while (true) {
  const iVerbose = cliArgs.indexOf("-verbose");
  if (iVerbose === -1) {
    break;
  }
  cliArgs.splice(iVerbose, 1);
  ++verbose;
}

function testDetach(buffer) {
  try {
    ArrayBuffer.detach(buffer);
    print("Failed. Expected an error when trying to transfer ");
  } catch (e) {
    if (verbose > 1) {
      print(`Passed. Expected error: ${e.message}`);
    }
  }
}

function test({init, max, checkOOM} = {}) {
  if (verbose) {
    print(`Testing memory {init: ${init|0}, max: ${max}}`);
  }
  const moduleTxt = `
  (module
    (memory (export "mem") ${init|0} ${max !== undefined ? max|0 : ""})
    (func (export "grow") (param i32) (result i32) (memory.grow (get_local 0)))
    (func (export "current") (result i32) (memory.size))
    (func (export "load") (param i32) (result i32) (i32.load (get_local 0)))
    (func (export "store") (param i32 i32) (i32.store (get_local 0) (get_local 1)))
  )`;
  if (verbose > 1) {
    print(moduleTxt);
  }
  const module = new WebAssembly.Module(WebAssembly.wabt.convertWast2Wasm(moduleTxt));
  let instance;
  try {
    instance = new WebAssembly.Instance(module);
  } catch (e) {
    if (!checkOOM || !e.message.includes("Failed to create WebAssembly.Memory")) {
      print(`FAILED. failed to instanciate module with error: ${e}`);
    } else if (verbose) {
      print(e.message);
    }
    return;
  }
  const {exports: {grow, current, load, store, mem}} = instance;
  function testReadWrite(index, value, currentSize) {
    const shouldTrap = index < 0 || (index + 4) > currentSize;
    const commonMsg = op => `trap on ${op}(${index}, ${value})`;
    try {
      store(index, value);
      if (shouldTrap) {
        print(`Failed. Expected ${commonMsg("store")}`);
        return;
      }
    } catch (e) {
      if (shouldTrap) {
        if (verbose) {
          print(`Passed. Expected ${commonMsg("store")}`);
        }
      } else {
        print(`Failed. Unexpected ${commonMsg("store")}`);
      }
    }

    try {
      const loadedValue = load(index);
      if (shouldTrap) {
        print(`Failed. Expected ${commonMsg("load")}`);
        return;
      }
      if ((loadedValue|0) !== (value|0)) {
        print(`Failed. Expected value ${value|0} after load. Got ${loadedValue|0}`);
      }
    } catch (e) {
      if (shouldTrap) {
        if (verbose) {
          print(`Passed. Expected ${commonMsg("load")}`);
        }
      } else {
        print(`Failed. Unexpected ${commonMsg("load")}`);
      }
    }
  }
  function run(delta) {
    testDetach(mem.buffer);
    const beforePages = current();
    const growRes = grow(delta);
    if (growRes !== -1 && growRes !== beforePages) {
      print(`FAILED. Expected grow(${delta}) to return ${beforePages}`);
    }
    const afterPages = current();
    if (growRes !== -1 && beforePages + delta !== afterPages) {
      print(`FAILED. Expected to have ${beforePages + delta} pages. Got ${afterPages} pages`);
    }
    if (verbose) {
      print(`current: ${beforePages}, grow(${delta}): ${growRes}, after: ${afterPages}`);
    }
    const currentSize = afterPages * 0x10000;
    testReadWrite(-4, -4, currentSize);
    testReadWrite(-3, -3, currentSize);
    testReadWrite(-2, -2, currentSize);
    testReadWrite(-1, -1, currentSize);
    testReadWrite(0, 6, currentSize);
    testDetach(mem.buffer);
    testReadWrite(1, 7, currentSize);
    testDetach(mem.buffer);
    testReadWrite(1, 7, currentSize);
    testReadWrite(currentSize - 4, 457, currentSize);
    testReadWrite(currentSize - 3, -98745, currentSize);
    testReadWrite(currentSize - 2, 786452, currentSize);
    testReadWrite(currentSize - 1, -1324, currentSize);
    testReadWrite(currentSize, 123, currentSize);
    testDetach(mem.buffer);
  }
  run(0);
  run(3);
  run(5);
  run(1 << 13);
}

test({init: 0});
test({init: 0, max: 5});
test({init: 0, max: 10});
test({init: 5});
test({init: 5, max: 10});
// test({init: 1 << 14, checkOOM: true}); // ArrayBuffer will throw OOM instead of returning a null buffer
try {
  test({init: 1 << 15});
  print("Failed. Expected an error when allocating WebAssembly.Memory too big");
} catch (e) {
  if (verbose) {
    print(`Passed. Expected error: ${e.message}`);
  }
}
print("PASSED");
