load(libdir + 'asserts.js');

function typedArrayMatchesString(ta, str, uptoStringLength = false) {
  if (ta.length != str.length && !uptoStringLength)
    return false;
  for (let i = 0; i < str.length; i++) {
    if (ta[i] != str.charCodeAt(i))
      return false;
  }
  return true;
}

function test() {

  // Check that {signed,unsigned} -> {Int*Array, Uint*Array} for string types.

  const shortU8 = ctypes.unsigned_char.array(10)("abc\0\0\0\0\0\0\0").readTypedArray();
  assertEq(shortU8 instanceof Uint8Array, true);
  assertEq(typedArrayMatchesString(shortU8, "abc\0\0\0\0\0\0\0"), true);

  const shortI8 = ctypes.signed_char.array(10)("abc\0\0\0\0\0\0\0").readTypedArray();
  assertEq(shortI8 instanceof Int8Array, true);
  assertEq(typedArrayMatchesString(shortI8, "abc\0\0\0\0\0\0\0"), true);

  const shortU16 = ctypes.char16_t.array(10)("千").readTypedArray();
  assertEq(shortU16 instanceof Uint16Array, true);
  assertEq(typedArrayMatchesString(shortU16, "千", 'ignore zero-padding, please'), true);

  // ...and for (other) numeric types.

  const I16 = ctypes.int16_t.array(10)().readTypedArray();
  assertEq(I16 instanceof Int16Array, true);

  const U32 = ctypes.uint32_t.array(10)().readTypedArray();
  assertEq(U32 instanceof Uint32Array, true);

  const I32 = ctypes.int32_t.array(10)().readTypedArray();
  assertEq(I32 instanceof Int32Array, true);

  // Check that pointers without associated length get truncated to strlen().

  const shortU8cs = ctypes.unsigned_char.array(10)("abc\0\0\0").addressOfElement(0).readTypedArray();
  assertEq(shortU8cs instanceof Uint8Array, true);
  assertEq(shortU8cs.length, 3);
  assertEq(typedArrayMatchesString(shortU8cs, "abc", 'stop at NUL, please'), true);

  const shortI8cs = ctypes.signed_char.array(10)("abc\0\0\0").addressOfElement(0).readTypedArray();
  assertEq(shortI8cs instanceof Int8Array, true);
  assertEq(shortI8cs.length, 3);
  assertEq(typedArrayMatchesString(shortI8cs, "abc", 'stop at NUL, please'), true);

  const shortU16cs = ctypes.char16_t.array(10)("千\0").addressOfElement(0).readTypedArray();
  assertEq(shortU16cs instanceof Uint16Array, true);
  assertEq(shortU16cs.length, 1);
  assertEq(typedArrayMatchesString(shortU16cs, "千", 'ignore zero-padding, please'), true);

  // Other types should just fail if the length is not known.

  assertTypeErrorMessage(() => { ctypes.int32_t.array(3)().addressOfElement(0).readTypedArray(); },
                         /base type .* is not an 8-bit or 16-bit integer or character type/);
  assertTypeErrorMessage(() => { ctypes.float.array(3)().addressOfElement(0).readTypedArray(); },
                         /base type .* is not an 8-bit or 16-bit integer or character type/);
  assertTypeErrorMessage(() => { ctypes.double.array(3)().addressOfElement(0).readTypedArray(); },
                         /base type .* is not an 8-bit or 16-bit integer or character type/);
  // char16_t is unsigned, so this is not a string type.
  assertTypeErrorMessage(() => { ctypes.int16_t.array(3)().addressOfElement(0).readTypedArray(); },
                         /base type .* is not an 8-bit or 16-bit integer or character type/);

  // Wide string -> bytes -> UTF-8 encoded string in typed array

  const input2 = "千千千千千千千千千千千千千千千千千千千千千千千千千";
  const encoded = ctypes.char.array(input2.length * 3)(input2).readTypedArray();
  // Each 千 character is encoded as 3 bytes.
  assertEq(encoded.length, input2.length * 3);

  // Wide string -> char16_t -> 16-bit encoded string in typed array

  const encoded16 = ctypes.char16_t.array(input2.length)(input2).readTypedArray();
  assertEq(encoded16.length, input2.length);

  // Floats

  const floats = ctypes.float.array(3)([10, 20, 30]).readTypedArray();
  assertEq(floats instanceof Float32Array, true);
  assertEq(floats.toString(), "10,20,30");

  const doubles = ctypes.double.array(3)([10, 20, 30]).readTypedArray();
  assertEq(doubles instanceof Float64Array, true);
  assertEq(doubles.toString(), "10,20,30");

  // Invalid

  assertTypeErrorMessage(() => { ctypes.int64_t.array(3)([10, 20, 30]).readTypedArray() },
                         /base type ctypes.int64_t.array.*is not compatible with a typed array element type/);

}

if (typeof ctypes === "object")
  test();
