//
//  typedArraysunitTest.js
//  examples
//
//  Created by Clément Brisset on 7/7/14
//  Copyright 2014 High Fidelity, Inc.
//
//  Distributed under the Apache License, Version 2.0.
//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//

Script.include("Test.js");

// e.g. extractbits([0xff, 0x80, 0x00, 0x00], 23, 30); inclusive
function extractbits(bytes, lo, hi) {
  var out = 0;
  bytes = bytes.slice(); // make a copy
  var lsb = bytes.pop(), sc = 0, sh = 0;

  for (; lo > 0;  lo--, hi--) {
    lsb >>= 1;
    if (++sc === 8) { sc = 0; lsb = bytes.pop(); }
  }

  for (; hi >= 0;  hi--) {
    out = out | (lsb & 0x01) << sh++;
    lsb >>= 1;
    if (++sc === 8) { sc = 0; lsb = bytes.pop(); }
  }

  return out;
}

test('ArrayBuffer', function(finished) {
     this.assertEquals(new ArrayBuffer(0).byteLength, 0, 'no length');
     
     this.assertEquals(typeof(new ArrayBuffer(0)), 'object', 'creation');
     this.assertEquals(typeof(new ArrayBuffer(1)), 'object', 'creation');
     this.assertEquals(typeof(new ArrayBuffer(123)), 'object', 'creation');
     
     this.assertEquals(new ArrayBuffer(123).byteLength, 123, 'length');

  	this.raises(function () { return new ArrayBuffer(-1); }, 'negative length');
  	this.raises(function () { return new ArrayBuffer(0x80000000); }, 'absurd length');
});

test('DataView constructors', function (finished) {
  var d = new DataView(new ArrayBuffer(8));

  d.setUint32(0, 0x12345678);
  this.assertEquals(d.getUint32(0), 0x12345678, 'big endian/big endian');

  d.setUint32(0, 0x12345678, true);
  this.assertEquals(d.getUint32(0, true), 0x12345678, 'little endian/little endian');

  d.setUint32(0, 0x12345678, true);
  this.assertEquals(d.getUint32(0), 0x78563412, 'little endian/big endian');

  d.setUint32(0, 0x12345678);
  this.assertEquals(d.getUint32(0, true), 0x78563412, 'big endian/little endian');

  this.raises(function () { return new DataView({}); }, 'non-ArrayBuffer argument');
  this.raises(function () { return new DataView("bogus"); }, 'non-ArrayBuffer argument');
});


test('ArrayBufferView', function () {
  var ab = new ArrayBuffer(48);
  var i32 = new Int32Array(ab, 16);
  i32.set([1, 2, 3, 4, 5, 6, 7, 8]);

  this.assertEquals(i32.buffer, ab, 'ArrayBuffers equal');
  this.assertEquals(i32.byteOffset, 16, 'byteOffset');
  this.assertEquals(i32.byteLength, 32, 'byteLength');

  var da = new DataView(i32.buffer, 8);
  this.assertEquals(da.buffer, ab, 'DataView: ArrayBuffers equal');
  this.assertEquals(da.byteOffset, 8, 'DataView: byteOffset');
  this.assertEquals(da.byteLength, 40, 'DataView: byteLength');
});

test('TypedArrays', function () {
  var a;

  this.assertEquals(Int8Array.BYTES_PER_ELEMENT, 1, 'Int8Array.BYTES_PER_ELEMENT');
  a = new Int8Array([1, 2, 3, 4, 5, 6, 7, 8]);
  this.assertEquals(a.BYTES_PER_ELEMENT, 1, 'int8Array.BYTES_PER_ELEMENT');
  this.assertEquals(a.byteOffset, 0, 'int8Array.byteOffset');
  this.assertEquals(a.byteLength, 8, 'int8Array.byteLength');

  this.assertEquals(Uint8Array.BYTES_PER_ELEMENT, 1, 'Uint8Array.BYTES_PER_ELEMENT');
  a = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
  this.assertEquals(a.BYTES_PER_ELEMENT, 1, 'uint8Array.BYTES_PER_ELEMENT');
  this.assertEquals(a.byteOffset, 0, 'uint8Array.byteOffset');
  this.assertEquals(a.byteLength, 8, 'uint8Array.byteLength');

  this.assertEquals(Int16Array.BYTES_PER_ELEMENT, 2, 'Int16Array.BYTES_PER_ELEMENT');
  a = new Int16Array([1, 2, 3, 4, 5, 6, 7, 8]);
  this.assertEquals(a.BYTES_PER_ELEMENT, 2, 'int16Array.BYTES_PER_ELEMENT');
  this.assertEquals(a.byteOffset, 0, 'int16Array.byteOffset');
  this.assertEquals(a.byteLength, 16, 'int16Array.byteLength');

  this.assertEquals(Uint16Array.BYTES_PER_ELEMENT, 2, 'Uint16Array.BYTES_PER_ELEMENT');
  a = new Uint16Array([1, 2, 3, 4, 5, 6, 7, 8]);
  this.assertEquals(a.BYTES_PER_ELEMENT, 2, 'uint16Array.BYTES_PER_ELEMENT');
  this.assertEquals(a.byteOffset, 0, 'uint16Array.byteOffset');
  this.assertEquals(a.byteLength, 16, 'uint16Array.byteLength');

  this.assertEquals(Int32Array.BYTES_PER_ELEMENT, 4, 'Int32Array.BYTES_PER_ELEMENT');
  a = new Int32Array([1, 2, 3, 4, 5, 6, 7, 8]);
  this.assertEquals(a.BYTES_PER_ELEMENT, 4, 'int32Array.BYTES_PER_ELEMENT');
  this.assertEquals(a.byteOffset, 0, 'int32Array.byteOffset');
  this.assertEquals(a.byteLength, 32, 'int32Array.byteLength');

  this.assertEquals(Uint32Array.BYTES_PER_ELEMENT, 4, 'Uint32Array.BYTES_PER_ELEMENT');
  a = new Uint32Array([1, 2, 3, 4, 5, 6, 7, 8]);
  this.assertEquals(a.BYTES_PER_ELEMENT, 4, 'uint32Array.BYTES_PER_ELEMENT');
  this.assertEquals(a.byteOffset, 0, 'uint32Array.byteOffset');
  this.assertEquals(a.byteLength, 32, 'uint32Array.byteLength');

  this.assertEquals(Float32Array.BYTES_PER_ELEMENT, 4, 'Float32Array.BYTES_PER_ELEMENT');
  a = new Float32Array([1, 2, 3, 4, 5, 6, 7, 8]);
  this.assertEquals(a.BYTES_PER_ELEMENT, 4, 'float32Array.BYTES_PER_ELEMENT');
  this.assertEquals(a.byteOffset, 0, 'float32Array.byteOffset');
  this.assertEquals(a.byteLength, 32, 'float32Array.byteLength');

  this.assertEquals(Float64Array.BYTES_PER_ELEMENT, 8, 'Float64Array.BYTES_PER_ELEMENT');
  a = new Float64Array([1, 2, 3, 4, 5, 6, 7, 8]);
  this.assertEquals(a.BYTES_PER_ELEMENT, 8, 'float64Array.BYTES_PER_ELEMENT');
  this.assertEquals(a.byteOffset, 0, 'float64Array.byteOffset');
  this.assertEquals(a.byteLength, 64, 'float64Array.byteLength');
});


test('typed array constructors', function () {
  this.arrayEqual(new Int8Array({ length: 3 }), [0, 0, 0], 'array equal -1');
  var rawbuf = (new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7])).buffer;

  var int8 = new Int8Array();
  this.assertEquals(int8.length, 0, 'no args 0');
  this.raises(function () { return new Int8Array(-1); }, 'bogus length');
  this.raises(function () { return new Int8Array(0x80000000); }, 'bogus length');

  int8 = new Int8Array(4);
  this.assertEquals(int8.BYTES_PER_ELEMENT, 1);
  this.assertEquals(int8.length, 4, 'length 1');
  this.assertEquals(int8.byteLength, 4, 'length 2');
  this.assertEquals(int8.byteOffset, 0, 'length 3');
  this.assertEquals(int8.get(-1), undefined, 'length, out of bounds 4');
  this.assertEquals(int8.get(4), undefined, 'length, out of bounds 5');

  int8 = new Int8Array([1, 2, 3, 4, 5, 6]);
  this.assertEquals(int8.length, 6, 'array 6');
  this.assertEquals(int8.byteLength, 6, 'array 7');
  this.assertEquals(int8.byteOffset, 0, 'array 8');
  this.assertEquals(int8.get(3), 4, 'array 9');
  this.assertEquals(int8.get(-1), undefined, 'array, out of bounds  10');
  this.assertEquals(int8.get(6), undefined, 'array, out of bounds 11');

  int8 = new Int8Array(rawbuf);
  this.assertEquals(int8.length, 8, 'buffer 12');
  this.assertEquals(int8.byteLength, 8, 'buffer 13');
  this.assertEquals(int8.byteOffset, 0, 'buffer 14');
  this.assertEquals(int8.get(7), 7, 'buffer 15');
  int8.set([111]);
  this.assertEquals(int8.get(0), 111, 'buffer 16');
  this.assertEquals(int8.get(-1), undefined, 'buffer, out of bounds 17');
  this.assertEquals(int8.get(8), undefined, 'buffer, out of bounds 18');

  int8 = new Int8Array(rawbuf, 2);
  this.assertEquals(int8.length, 6, 'buffer, byteOffset 19');
  this.assertEquals(int8.byteLength, 6, 'buffer, byteOffset 20');
  this.assertEquals(int8.byteOffset, 2, 'buffer, byteOffset 21');
  this.assertEquals(int8.get(5), 7, 'buffer, byteOffset 22');
  int8.set([112]);
  this.assertEquals(int8.get(0), 112, 'buffer 23');
  this.assertEquals(int8.get(-1), undefined, 'buffer, byteOffset, out of bounds 24');
  this.assertEquals(int8.get(6), undefined, 'buffer, byteOffset, out of bounds 25');
  
  int8 = new Int8Array(rawbuf, 8);
  this.assertEquals(int8.length, 0, 'buffer, byteOffset 26');

  this.raises(function () { return new Int8Array(rawbuf, -1); }, 'invalid byteOffset 27');
  this.raises(function () { return new Int8Array(rawbuf, 9); }, 'invalid byteOffset 28');
  this.raises(function () { return new Int32Array(rawbuf, -1); }, 'invalid byteOffset 29');
  this.raises(function () { return new Int32Array(rawbuf, 5); }, 'invalid byteOffset 30');

  int8 = new Int8Array(rawbuf, 2, 4);
  this.assertEquals(int8.length, 4, 'buffer, byteOffset, length 31');
  this.assertEquals(int8.byteLength, 4, 'buffer, byteOffset, length 32');
  this.assertEquals(int8.byteOffset, 2, 'buffer, byteOffset, length 33');
  this.assertEquals(int8.get(3), 5, 'buffer, byteOffset, length 34');
  int8.set([113]);
  this.assertEquals(int8.get(0), 113, 'buffer, byteOffset, length 35');
  this.assertEquals(int8.get(-1), undefined, 'buffer, byteOffset, length, out of bounds 36');
  this.assertEquals(int8.get(4), undefined, 'buffer, byteOffset, length, out of bounds 37');

  this.raises(function () { return new Int8Array(rawbuf, 0, 9); }, 'invalid byteOffset+length');
  this.raises(function () { return new Int8Array(rawbuf, 8, 1); }, 'invalid byteOffset+length');
  this.raises(function () { return new Int8Array(rawbuf, 9, -1); }, 'invalid byteOffset+length');
});

test('TypedArray clone constructor', function () {
  var src = new Int32Array([1, 2, 3, 4, 5, 6, 7, 8]);
  var dst = new Int32Array(src);
  this.arrayEqual(dst, [1, 2, 3, 4, 5, 6, 7, 8], '1');
  src.set([99]);
  this.arrayEqual(src, [99, 2, 3, 4, 5, 6, 7, 8], '2');
  this.arrayEqual(dst, [1, 2, 3, 4, 5, 6, 7, 8], '3');
});


test('conversions', function () {
  var uint8 = new Uint8Array([1, 2, 3, 4]),
      uint16 = new Uint16Array(uint8.buffer),
      uint32 = new Uint32Array(uint8.buffer);

  // Note: can't probe individual bytes without endianness awareness
  this.arrayEqual(uint8, [1, 2, 3, 4]);
  uint16.set([0xffff]);
  this.arrayEqual(uint8, [0xff, 0xff, 3, 4]);
  uint16.set([0xeeee], 1);
  this.arrayEqual(uint8, [0xff, 0xff, 0xee, 0xee]);
  uint32.set([0x11111111]);
  this.assertEquals(uint16.get(0), 0x1111);
  this.assertEquals(uint16.get(1), 0x1111);
  this.arrayEqual(uint8, [0x11, 0x11, 0x11, 0x11]);
});


test('signed/unsigned conversions', function () {

  var int8 = new Int8Array(1), uint8 = new Uint8Array(int8.buffer);
  uint8.set([123]);
  this.assertEquals(int8.get(0), 123, 'int8/uint8');
  uint8.set([161]);
  this.assertEquals(int8.get(0), -95, 'int8/uint8');
  int8.set([-120]);
  this.assertEquals(uint8.get(0), 136, 'uint8/int8');
  int8.set([-1]);
  this.assertEquals(uint8.get(0), 0xff, 'uint8/int8');

  var int16 = new Int16Array(1), uint16 = new Uint16Array(int16.buffer);
  uint16.set([3210]);
  this.assertEquals(int16.get(0), 3210, 'int16/uint16');
  uint16.set([49232]);
  this.assertEquals(int16.get(0), -16304, 'int16/uint16');
  int16.set([-16384]);
  this.assertEquals(uint16.get(0), 49152, 'uint16/int16');
  int16.set([-1]);
  this.assertEquals(uint16.get(0), 0xffff, 'uint16/int16');

  var int32 = new Int32Array(1), uint32 = new Uint32Array(int32.buffer);
  uint32.set([0x80706050]);
  this.assertEquals(int32.get(0), -2140118960, 'int32/uint32');
  int32.set([-2023406815]);
  this.assertEquals(uint32.get(0), 0x87654321, 'uint32/int32');
  int32.set([-1]);
  this.assertEquals(uint32.get(0), 0xffffffff, 'uint32/int32');
});


test('IEEE754 single precision unpacking', function () {
  function fromBytes(bytes) {
    var uint8 = new Uint8Array(bytes),
        dv = new DataView(uint8.buffer);
    return dv.getFloat32(0);
  }

  this.assertEquals(isNaN(fromBytes([0xff, 0xff, 0xff, 0xff])), true, 'Q-NaN');
  this.assertEquals(isNaN(fromBytes([0xff, 0xc0, 0x00, 0x01])), true, 'Q-NaN');

  this.assertEquals(isNaN(fromBytes([0xff, 0xc0, 0x00, 0x00])), true, 'Indeterminate');

  this.assertEquals(isNaN(fromBytes([0xff, 0xbf, 0xff, 0xff])), true, 'S-NaN');
  this.assertEquals(isNaN(fromBytes([0xff, 0x80, 0x00, 0x01])), true, 'S-NaN');

  this.assertEquals(fromBytes([0xff, 0x80, 0x00, 0x00]), -Infinity, '-Infinity');

  this.assertEquals(fromBytes([0xff, 0x7f, 0xff, 0xff]), -3.4028234663852886E+38, '-Normalized');
  this.assertEquals(fromBytes([0x80, 0x80, 0x00, 0x00]), -1.1754943508222875E-38, '-Normalized');
  this.assertEquals(fromBytes([0xff, 0x7f, 0xff, 0xff]), -3.4028234663852886E+38, '-Normalized');
  this.assertEquals(fromBytes([0x80, 0x80, 0x00, 0x00]), -1.1754943508222875E-38, '-Normalized');

  // TODO: Denormalized values fail on Safari on iOS/ARM
  this.assertEquals(fromBytes([0x80, 0x7f, 0xff, 0xff]), -1.1754942106924411E-38, '-Denormalized');
  this.assertEquals(fromBytes([0x80, 0x00, 0x00, 0x01]), -1.4012984643248170E-45, '-Denormalized');

  this.assertEquals(fromBytes([0x80, 0x00, 0x00, 0x00]), -0, '-0');
  this.assertEquals(fromBytes([0x00, 0x00, 0x00, 0x00]), +0, '+0');

  // TODO: Denormalized values fail on Safari on iOS/ARM
  this.assertEquals(fromBytes([0x00, 0x00, 0x00, 0x01]), 1.4012984643248170E-45, '+Denormalized');
  this.assertEquals(fromBytes([0x00, 0x7f, 0xff, 0xff]), 1.1754942106924411E-38, '+Denormalized');

  this.assertEquals(fromBytes([0x00, 0x80, 0x00, 0x00]), 1.1754943508222875E-38, '+Normalized');
  this.assertEquals(fromBytes([0x7f, 0x7f, 0xff, 0xff]), 3.4028234663852886E+38, '+Normalized');

  this.assertEquals(fromBytes([0x7f, 0x80, 0x00, 0x00]), +Infinity, '+Infinity');

  this.assertEquals(isNaN(fromBytes([0x7f, 0x80, 0x00, 0x01])), true, 'S+NaN');
  this.assertEquals(isNaN(fromBytes([0x7f, 0xbf, 0xff, 0xff])), true, 'S+NaN');

  this.assertEquals(isNaN(fromBytes([0x7f, 0xc0, 0x00, 0x00])), true, 'Q+NaN');
  this.assertEquals(isNaN(fromBytes([0x7f, 0xff, 0xff, 0xff])), true, 'Q+NaN');
});

test('IEEE754 single precision packing', function () {

  function toBytes(v) {
    var uint8 = new Uint8Array(4), dv = new DataView(uint8.buffer);
    dv.setFloat32(0, v);
    var bytes = [];
    for (var i = 0; i < 4; i += 1) {
      bytes.push(uint8.get(i));
    }
    return bytes;
  }

  this.arrayEqual(toBytes(-Infinity), [0xff, 0x80, 0x00, 0x00], '-Infinity');

  this.arrayEqual(toBytes(-3.4028235677973366e+38), [0xff, 0x80, 0x00, 0x00], '-Overflow');
  this.arrayEqual(toBytes(-3.402824E+38), [0xff, 0x80, 0x00, 0x00], '-Overflow');

  this.arrayEqual(toBytes(-3.4028234663852886E+38), [0xff, 0x7f, 0xff, 0xff], '-Normalized');
  this.arrayEqual(toBytes(-1.1754943508222875E-38), [0x80, 0x80, 0x00, 0x00], '-Normalized');

  // TODO: Denormalized values fail on Safari iOS/ARM
  this.arrayEqual(toBytes(-1.1754942106924411E-38), [0x80, 0x7f, 0xff, 0xff], '-Denormalized');
  this.arrayEqual(toBytes(-1.4012984643248170E-45), [0x80, 0x00, 0x00, 0x01], '-Denormalized');

  this.arrayEqual(toBytes(-7.006492321624085e-46), [0x80, 0x00, 0x00, 0x00], '-Underflow');

  this.arrayEqual(toBytes(-0), [0x80, 0x00, 0x00, 0x00], '-0');
  this.arrayEqual(toBytes(0), [0x00, 0x00, 0x00, 0x00], '+0');

  this.arrayEqual(toBytes(7.006492321624085e-46), [0x00, 0x00, 0x00, 0x00], '+Underflow');

  // TODO: Denormalized values fail on Safari iOS/ARM
  this.arrayEqual(toBytes(1.4012984643248170E-45), [0x00, 0x00, 0x00, 0x01], '+Denormalized');
  this.arrayEqual(toBytes(1.1754942106924411E-38), [0x00, 0x7f, 0xff, 0xff], '+Denormalized');

  this.arrayEqual(toBytes(1.1754943508222875E-38), [0x00, 0x80, 0x00, 0x00], '+Normalized');
  this.arrayEqual(toBytes(3.4028234663852886E+38), [0x7f, 0x7f, 0xff, 0xff], '+Normalized');

  this.arrayEqual(toBytes(+3.402824E+38), [0x7f, 0x80, 0x00, 0x00], '+Overflow');
  this.arrayEqual(toBytes(+3.402824E+38), [0x7f, 0x80, 0x00, 0x00], '+Overflow');
  this.arrayEqual(toBytes(+Infinity), [0x7f, 0x80, 0x00, 0x00], '+Infinity');

  // Allow any NaN pattern (exponent all 1's, fraction non-zero)
  var nanbytes = toBytes(NaN),
      sign = extractbits(nanbytes, 31, 31),
      exponent = extractbits(nanbytes, 23, 30),
      fraction = extractbits(nanbytes, 0, 22);
  this.assertEquals(exponent === 255 && fraction !== 0, true, 'NaN');
});


test('IEEE754 double precision unpacking', function () {

  function fromBytes(bytes) {
    var uint8 = new Uint8Array(bytes),
        dv = new DataView(uint8.buffer);
    return dv.getFloat64(0);
  }

  this.assertEquals(isNaN(fromBytes([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff])), true, 'Q-NaN');
  this.assertEquals(isNaN(fromBytes([0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01])), true, 'Q-NaN');

  this.assertEquals(isNaN(fromBytes([0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])), true, 'Indeterminate');

  this.assertEquals(isNaN(fromBytes([0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff])), true, 'S-NaN');
  this.assertEquals(isNaN(fromBytes([0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01])), true, 'S-NaN');

  this.assertEquals(fromBytes([0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), -Infinity, '-Infinity');

  this.assertEquals(fromBytes([0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), -1.7976931348623157E+308, '-Normalized');
  this.assertEquals(fromBytes([0x80, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), -2.2250738585072014E-308, '-Normalized');

  // TODO: Denormalized values fail on Safari iOS/ARM
  this.assertEquals(fromBytes([0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), -2.2250738585072010E-308, '-Denormalized');
  this.assertEquals(fromBytes([0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]), -4.9406564584124654E-324, '-Denormalized');

  this.assertEquals(fromBytes([0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), -0, '-0');
  this.assertEquals(fromBytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), +0, '+0');

  // TODO: Denormalized values fail on Safari iOS/ARM
  this.assertEquals(fromBytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]), 4.9406564584124654E-324, '+Denormalized');
  this.assertEquals(fromBytes([0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), 2.2250738585072010E-308, '+Denormalized');

  this.assertEquals(fromBytes([0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), 2.2250738585072014E-308, '+Normalized');
  this.assertEquals(fromBytes([0x7f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), 1.7976931348623157E+308, '+Normalized');

  this.assertEquals(fromBytes([0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), +Infinity, '+Infinity');

  this.assertEquals(isNaN(fromBytes([0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01])), true, 'S+NaN');
  this.assertEquals(isNaN(fromBytes([0x7f, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff])), true, 'S+NaN');

  this.assertEquals(isNaN(fromBytes([0x7f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])), true, 'Q+NaN');
  this.assertEquals(isNaN(fromBytes([0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff])), true, 'Q+NaN');
});


test('IEEE754 double precision packing', function () {

  function toBytes(v) {
    var uint8 = new Uint8Array(8),
        dv = new DataView(uint8.buffer);
    dv.setFloat64(0, v);
    var bytes = [];
    for (var i = 0; i < 8; i += 1) {
      bytes.push(uint8.get(i));
    }
    return bytes;
  }

  this.arrayEqual(toBytes(-Infinity), [0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], '-Infinity');

  this.arrayEqual(toBytes(-1.7976931348623157E+308), [0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], '-Normalized');
  this.arrayEqual(toBytes(-2.2250738585072014E-308), [0x80, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], '-Normalized');

  // TODO: Denormalized values fail on Safari iOS/ARM
  this.arrayEqual(toBytes(-2.2250738585072010E-308), [0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], '-Denormalized');
  this.arrayEqual(toBytes(-4.9406564584124654E-324), [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], '-Denormalized');

  this.arrayEqual(toBytes(-0), [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], '-0');
  this.arrayEqual(toBytes(0), [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], '+0');

  // TODO: Denormalized values fail on Safari iOS/ARM
  this.arrayEqual(toBytes(4.9406564584124654E-324), [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], '+Denormalized');
  this.arrayEqual(toBytes(2.2250738585072010E-308), [0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], '+Denormalized');

  this.arrayEqual(toBytes(2.2250738585072014E-308), [0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], '+Normalized');
  this.arrayEqual(toBytes(1.7976931348623157E+308), [0x7f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], '+Normalized');

  this.arrayEqual(toBytes(+Infinity), [0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], '+Infinity');

  // Allow any NaN pattern (exponent all 1's, fraction non-zero)
  var nanbytes = toBytes(NaN),
      sign = extractbits(nanbytes, 63, 63),
      exponent = extractbits(nanbytes, 52, 62),
      fraction = extractbits(nanbytes, 0, 51);
  this.assertEquals(exponent === 2047 && fraction !== 0, true, 'NaN');
});

test('Int32Array round trips', function () {
  var i32 = new Int32Array([0]);
  var data = [
    0,
    1,
      -1,
    123,
      -456,
    0x80000000 >> 0,
    0x7fffffff >> 0,
    0x12345678 >> 0,
    0x87654321 >> 0
  ];

  for (var i = 0; i < data.length; i += 1) {
    var datum = data[i];
    i32.set([datum]);
    this.assertEquals(datum, i32.get(0), String(datum));
  }
});


test('Int16Array round trips', function () {
  var i16 = new Int16Array([0]);
  var data = [
    0,
    1,
      -1,
    123,
      -456,
    0xffff8000 >> 0,
    0x00007fff >> 0,
    0x00001234 >> 0,
    0xffff8765 >> 0
  ];

  for (var i = 0; i < data.length; i += 1) {
    var datum = data[i];
    i16.set([datum]);
    this.assertEquals(datum, i16.get(0), String(datum));
  }
});


test('Int8Array round trips', function () {
  var i8 = new Int8Array([0]);
  var data = [
    0,
    1,
      -1,
    123,
      -45,
    0xffffff80 >> 0,
    0x0000007f >> 0,
    0x00000012 >> 0,
    0xffffff87 >> 0
  ];

  for (var i = 0; i < data.length; i += 1) {
    var datum = data[i];
    i8.set([datum]);
    this.assertEquals(datum, i8.get(0), String(datum));
  }
});

test('TypedArray setting', function () {

  var a = new Int32Array([1, 2, 3, 4, 5]);
  var b = new Int32Array(5);
  b.set(a);
  this.arrayEqual(b, [1, 2, 3, 4, 5], '1');
  this.raises(function () { b.set(a, 1); });

  b.set(new Int32Array([99, 98]), 2);
  this.arrayEqual(b, [1, 2, 99, 98, 5], '2');

  b.set(new Int32Array([99, 98, 97]), 2);
  this.arrayEqual(b, [1, 2, 99, 98, 97], '3');

  this.raises(function () { b.set(new Int32Array([99, 98, 97, 96]), 2); });
  this.raises(function () { b.set([101, 102, 103, 104], 4); });

  //  ab = [ 0, 1, 2, 3, 4, 5, 6, 7 ]
  //  a1 = [ ^, ^, ^, ^, ^, ^, ^, ^ ]
  //  a2 =             [ ^, ^, ^, ^ ]
  var ab = new ArrayBuffer(8);
  var a1 = new Uint8Array(ab);
  for (var i = 0; i < a1.length; i += 1) { a1.set([i], i); }
  var a2 = new Uint8Array(ab, 4);
  a1.set(a2, 2);
  this.arrayEqual(a1, [0, 1, 4, 5, 6, 7, 6, 7]);
  this.arrayEqual(a2, [6, 7, 6, 7]);
});


test('TypedArray.subarray', function () {

  var a = new Int32Array([1, 2, 3, 4, 5]);
  this.arrayEqual(a.subarray(3), [4, 5]);
  this.arrayEqual(a.subarray(1, 3), [2, 3]);
  this.arrayEqual(a.subarray(-3), [3, 4, 5]);
  this.arrayEqual(a.subarray(-3, -1), [3, 4]);
  this.arrayEqual(a.subarray(3, 2), []);
  this.arrayEqual(a.subarray(-2, -3), []);
  this.arrayEqual(a.subarray(4, 1), []);
  this.arrayEqual(a.subarray(-1, -4), []);
  this.arrayEqual(a.subarray(1).subarray(1), [3, 4, 5]);
  this.arrayEqual(a.subarray(1, 4).subarray(1, 2), [3]);
});


test('DataView constructors', function () {

  var d = new DataView(new ArrayBuffer(8));

  d.setUint32(0, 0x12345678);
  this.assertEquals(d.getUint32(0), 0x12345678, 'big endian/big endian');

  d.setUint32(0, 0x12345678, true);
  this.assertEquals(d.getUint32(0, true), 0x12345678, 'little endian/little endian');

  d.setUint32(0, 0x12345678, true);
  this.assertEquals(d.getUint32(0), 0x78563412, 'little endian/big endian');

  d.setUint32(0, 0x12345678);
  this.assertEquals(d.getUint32(0, true), 0x78563412, 'big endian/little endian');

  // Chrome allows no arguments, throws if non-ArrayBuffer
  //stricterEqual(new DataView().buffer.byteLength, 0, 'no arguments');

  // Safari (iOS 5) does not
  //raises(function () { return new DataView(); }, TypeError, 'no arguments');

  // Chrome raises TypeError, Safari iOS5 raises isDOMException(INDEX_SIZE_ERR)
  this.raises(function () { return new DataView({}); }, 'non-ArrayBuffer argument');

  this.raises(function () { return new DataView("bogus"); }, TypeError, 'non-ArrayBuffer argument');
});



test('DataView accessors', function () {
  var u = new Uint8Array(8), d = new DataView(u.buffer);
  this.arrayEqual(u, [0, 0, 0, 0, 0, 0, 0, 0], '1');

  d.setUint8(0, 255);
  this.arrayEqual(u, [0xff, 0, 0, 0, 0, 0, 0, 0], '2');

  d.setInt8(1, -1);
  this.arrayEqual(u, [0xff, 0xff, 0, 0, 0, 0, 0, 0], '3');

  d.setUint16(2, 0x1234);
  this.arrayEqual(u, [0xff, 0xff, 0x12, 0x34, 0, 0, 0, 0]), '4';

  d.setInt16(4, -1);
  this.arrayEqual(u, [0xff, 0xff, 0x12, 0x34, 0xff, 0xff, 0, 0], '5');

  d.setUint32(1, 0x12345678);
  this.arrayEqual(u, [0xff, 0x12, 0x34, 0x56, 0x78, 0xff, 0, 0], '6');

  d.setInt32(4, -2023406815);
  this.arrayEqual(u, [0xff, 0x12, 0x34, 0x56, 0x87, 0x65, 0x43, 0x21], '7');
  
  d.setFloat32(2, 1.2E+38);
  this.arrayEqual(u, [0xff, 0x12, 0x7e, 0xb4, 0x8e, 0x52, 0x43, 0x21], '8');

  d.setFloat64(0, -1.2345678E+301);
  this.arrayEqual(u, [0xfe, 0x72, 0x6f, 0x51, 0x5f, 0x61, 0x77, 0xe5], '9');

  u.set([0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87]);
  this.assertEquals(d.getUint8(0), 128, '10');
  this.assertEquals(d.getInt8(1), -127, '11');
  this.assertEquals(d.getUint16(2), 33411, '12');
  this.assertEquals(d.getInt16(3), -31868, '13');
  this.assertEquals(d.getUint32(4), 2223343239, '14');
  this.assertEquals(d.getInt32(2), -2105310075, '15');
  this.assertEquals(d.getFloat32(2), -1.932478247535851e-37, '16');
  this.assertEquals(d.getFloat64(0), -3.116851295377095e-306, '17');

});


test('DataView endian', function () {
  var rawbuf = (new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7])).buffer;
  var d;

  d = new DataView(rawbuf);
  this.assertEquals(d.byteLength, 8, 'buffer');
  this.assertEquals(d.byteOffset, 0, 'buffer');
  this.raises(function () { d.getUint8(-2); }); // Chrome bug for index -, DOMException, 'bounds for buffer'?
  this.raises(function () { d.getUint8(8); }, 'bounds for buffer');
  this.raises(function () { d.setUint8(-2, 0); }, 'bounds for buffer');
  this.raises(function () { d.setUint8(8, 0); }, 'bounds for buffer');

  d = new DataView(rawbuf, 2);
  this.assertEquals(d.byteLength, 6, 'buffer, byteOffset');
  this.assertEquals(d.byteOffset, 2, 'buffer, byteOffset');
  this.assertEquals(d.getUint8(5), 7, 'buffer, byteOffset');
  this.raises(function () { d.getUint8(-2); }, 'bounds for buffer, byteOffset');
  this.raises(function () { d.getUint8(6); }, 'bounds for buffer, byteOffset');
  this.raises(function () { d.setUint8(-2, 0); }, 'bounds for buffer, byteOffset');
  this.raises(function () { d.setUint8(6, 0); }, 'bounds for buffer, byteOffset');

  d = new DataView(rawbuf, 8);
  this.assertEquals(d.byteLength, 0, 'buffer, byteOffset');

  this.raises(function () { return new DataView(rawbuf, -1); }, 'invalid byteOffset');
  this.raises(function () { return new DataView(rawbuf, 9); }, 'invalid byteOffset');
  this.raises(function () { return new DataView(rawbuf, -1); }, 'invalid byteOffset');

  d = new DataView(rawbuf, 2, 4);
  this.assertEquals(d.byteLength, 4, 'buffer, byteOffset, length');
  this.assertEquals(d.byteOffset, 2, 'buffer, byteOffset, length');
  this.assertEquals(d.getUint8(3), 5, 'buffer, byteOffset, length');
  this.raises(function () { return d.getUint8(-2); }, 'bounds for buffer, byteOffset, length');
  this.raises(function () { d.getUint8(4); }, 'bounds for buffer, byteOffset, length');
  this.raises(function () { d.setUint8(-2, 0); }, 'bounds for buffer, byteOffset, length');
  this.raises(function () { d.setUint8(4, 0); }, 'bounds for buffer, byteOffset, length');

  this.raises(function () { return new DataView(rawbuf, 0, 9); }, 'invalid byteOffset+length');
  this.raises(function () { return new DataView(rawbuf, 8, 1); }, 'invalid byteOffset+length');
  this.raises(function () { return new DataView(rawbuf, 9, -1); }, 'invalid byteOffset+length');
});


test('Typed Array getters/setters', function () {
  // Only supported if Object.defineProperty() is fully supported on non-DOM objects.
  try {
    var o = {};
    Object.defineProperty(o, 'x', { get: function() { return 1; } });
    if (o.x !== 1) throw Error();
  } catch (_) {
    ok(true);
    return;
  }

  var bytes = new Uint8Array([1, 2, 3, 4]),
      uint32s = new Uint32Array(bytes.buffer);

  this.assertEquals(bytes[1], 2);
  uint32s[0] = 0xffffffff;
  this.assertEquals(bytes[1], 0xff);
});


test('Uint8ClampedArray', function () {
  this.assertEquals(Uint8ClampedArray.BYTES_PER_ELEMENT, 1, 'Uint8ClampedArray.BYTES_PER_ELEMENT');
  var a = new Uint8ClampedArray([-Infinity, -Number.MAX_VALUE, -1, -Number.MIN_VALUE, -0,
                                 0, Number.MIN_VALUE, 1, 1.1, 1.9, 255, 255.1, 255.9, 256, Number.MAX_VALUE, Infinity,
                                 NaN]);
  this.assertEquals(a.BYTES_PER_ELEMENT, 1);
  this.assertEquals(a.byteOffset, 0);
  this.assertEquals(a.byteLength, 17);
  this.arrayEqual(a, [0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0], "array test");
});

test('Regression Tests', function() {
  // Bug: https://github.com/inexorabletash/polyfill/issues/16
  var minFloat32 = 1.401298464324817e-45;
  var truncated = new Float32Array([-minFloat32 / 2 - Math.pow(2, -202)]).get(0);
  this.assertEquals(truncated, -minFloat32, 'smallest 32 bit float should not truncate to zero');
});