// Copyright 2015 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.



////////////////////////////////////////////////////////////////////////////////
// (Auxiliaries)


"use strict";

var global = this;

var sym = Symbol("gaga");

var objects = [
  {},
  [],
  function() {},
  function() {
    return arguments;
  }(),
  function() {
    'use strict';
    return arguments;
  }(),
  Object(1),
  Object(true),
  Object('bla'),
  new Date,
  new RegExp,
  new Set,
  new Map,
  new WeakMap,
  new WeakSet,
  new ArrayBuffer(10),
  new Int32Array(5),
  Object,
  Function,
  Date,
  RegExp,
  global
];

function prepare(target) {
  target["bla"] = true;
  target[4] = 42;
  target[sym] = "foo";
  target["noconf"] = 43;
  Object.defineProperty(target, "noconf",
      { configurable: false });
  Object.defineProperty(target, "nowrite",
      { writable: false, configurable: true, value: 44 });
  Object.defineProperty(target, "getter",
      { get: function () {return this.bla}, configurable: true });
  Object.defineProperty(target, "setter",
      { set: function (x) {this.gaga = x}, configurable: true });
  Object.defineProperty(target, "setter2",
      { set: function (x) {}, configurable: true });
}



////////////////////////////////////////////////////////////////////////////////
// Reflect.get


(function testReflectGetArity() {
  assertEquals(2, Reflect.get.length);
})();


(function testReflectGetOnNonObject() {
  assertThrows(function() { Reflect.get(); }, TypeError);
  assertThrows(function() { Reflect.get(42, "bla"); }, TypeError);
  assertThrows(function() { Reflect.get(null, "bla"); }, TypeError);
})();


(function testReflectGetKeyConversion() {
  var target = {bla: 42};
  var a = { [Symbol.toPrimitive]: function() { return "bla" } };
  var b = { [Symbol.toPrimitive]: function() { throw "gaga" } };
  assertEquals(42, Reflect.get(target, a));
  assertThrowsEquals(function() { Reflect.get(target, b); }, "gaga");
})();


(function testReflectGetOnObject() {
  var receiver = {bla: false};
  for (let target of objects) {
    prepare(target);
    assertEquals(true, Reflect.get(target, "bla"));
    assertEquals(true, Reflect.get(target, "bla", target));
    assertEquals(true, Reflect.get(target, "bla", receiver));
    assertEquals(42, Reflect.get(target, 4));
    assertEquals(42, Reflect.get(target, 4, target));
    assertEquals(42, Reflect.get(target, 4, receiver));
    assertEquals(42, Reflect.get(target, "4"));
    assertEquals(42, Reflect.get(target, "4", target));
    assertEquals(42, Reflect.get(target, "4", receiver));
    assertEquals("foo", Reflect.get(target, sym));
    assertEquals("foo", Reflect.get(target, sym, target));
    assertEquals("foo", Reflect.get(target, sym, receiver));
    assertEquals(43, Reflect.get(target, "noconf"));
    assertEquals(43, Reflect.get(target, "noconf", target));
    assertEquals(43, Reflect.get(target, "noconf", receiver));
    assertEquals(true, Reflect.get(target, "getter"));
    assertEquals(true, Reflect.get(target, "getter", target));
    assertEquals(false, Reflect.get(target, "getter", receiver));
    assertEquals(undefined, Reflect.get(target, "setter"));
    assertEquals(undefined, Reflect.get(target, "setter", target));
    assertEquals(undefined, Reflect.get(target, "setter", receiver));
    assertEquals(undefined, Reflect.get(target, "foo"));
    assertEquals(undefined, Reflect.get(target, "foo", target));
    assertEquals(undefined, Reflect.get(target, "foo", receiver));
    assertEquals(undefined, Reflect.get(target, 666));
    assertEquals(undefined, Reflect.get(target, 666, target));
    assertEquals(undefined, Reflect.get(target, 666, receiver));

    let proto = target.__proto__;
    target.__proto__ = { get foo() {return this.bla} };
    assertEquals(true, Reflect.get(target, "foo"));
    assertEquals(true, Reflect.get(target, "foo", target));
    assertEquals(false, Reflect.get(target, "foo", receiver));
    target.__proto__ = proto;
  }
})();



////////////////////////////////////////////////////////////////////////////////
// Reflect.set


(function testReflectSetArity() {
  assertEquals(3, Reflect.set.length);
})();


(function testReflectSetOnNonObject() {
  assertThrows(function() { Reflect.set(); }, TypeError);
  assertThrows(function() { Reflect.set(42, "bla"); }, TypeError);
  assertThrows(function() { Reflect.set(null, "bla"); }, TypeError);
})();


(function testReflectSetKeyConversion() {
  var target = {};
  var a = { [Symbol.toPrimitive]: function() { return "bla" } };
  var b = { [Symbol.toPrimitive]: function() { throw "gaga" } };
  assertTrue(Reflect.set(target, a, 42));
  assertEquals(42, target.bla);
  assertThrowsEquals(function() { Reflect.set(target, b, 42); }, "gaga");
})();


(function testReflectSetOnObject() {
  var receiver = {bla: false};
  var value = 34234;
  for (let target of objects) {
    prepare(target);
    assertTrue(Reflect.set(target, "bla", value));
    assertEquals(value, target.bla);

    prepare(target);
    assertTrue(Reflect.set(target, "bla", value, target));
    assertEquals(value, target.bla);

    prepare(target);
    assertTrue(Reflect.set(target, "bla", value, receiver));
    assertEquals(true, target.bla);
    assertEquals(value, receiver.bla);
    receiver.bla = false;

    prepare(target);
    assertTrue(Reflect.set(target, 4, value));
    assertEquals(value, target[4]);

    prepare(target);
    assertTrue(Reflect.set(target, 4, value, target));
    assertEquals(value, target[4]);

    prepare(target);
    assertTrue(Reflect.set(target, 4, value, receiver));
    assertEquals(42, target[4]);
    assertEquals(value, receiver[4]);
    delete receiver[4];

    prepare(target);
    assertTrue(Reflect.set(target, sym, value));
    assertEquals(value, target[sym]);

    prepare(target);
    assertTrue(Reflect.set(target, sym, value, target));
    assertEquals(value, target[sym]);

    prepare(target);
    assertTrue(Reflect.set(target, sym, value, receiver));
    assertEquals("foo", target[sym]);
    assertEquals(value, receiver[sym]);
    delete receiver[sym];

    prepare(target);
    assertTrue(Reflect.set(target, "noconf", value));
    assertEquals(value, target.noconf);

    prepare(target);
    assertTrue(Reflect.set(target, "noconf", value, target));
    assertEquals(value, target.noconf);

    prepare(target);
    assertTrue(Reflect.set(target, "noconf", value, receiver));
    assertEquals(43, target.noconf);
    assertEquals(value, receiver.noconf);
    delete receiver.noconf;

    assertTrue(Reflect.set(target, "setter", value));
    assertEquals(value, target.gaga)
    delete target.gaga;

    assertTrue(Reflect.set(target, "setter", value, target));
    assertEquals(value, target.gaga)
    delete target.gaga;

    assertTrue(Reflect.set(target, "setter", value, receiver));
    assertFalse("gaga" in target);
    assertEquals(value, receiver.gaga);
    delete receiver.gaga;

    assertFalse(Reflect.set(target, "nowrite", value));
    assertEquals(44, target.nowrite);

    assertFalse(Reflect.set(target, "nowrite", value, target));
    assertEquals(44, target.nowrite);

    assertFalse(Reflect.set(target, "nowrite", value, receiver));
    assertEquals(44, target.nowrite);
    assertFalse("nowrite" in receiver);

    // Data vs Non-Writable
    assertFalse(Reflect.set({}, "nowrite", value, target));

    // Data vs Accessor
    assertFalse(Reflect.set({}, "unknown", 0, {set unknown(x) {}}));
    assertFalse(Reflect.set(target, "unknown", value, {set unknown(x) {}}));
    assertFalse(Reflect.set(target, "bla", value, {set bla(x) {}}));
    assertFalse(Reflect.set(target, "bla", value, {get bla() {}}));

    // Accessor vs Data
    assertTrue(Reflect.set({set bla(x) {}}), "bla", value, target);
    assertFalse(Reflect.set({get bla() {}}, "bla", value, target));

    // Data vs Non-Object
    assertFalse(Reflect.set({}, "bla", value, null));
    assertFalse(Reflect.set({bla: 42}, "bla", value, null));

    // Accessor vs Non-Object
    assertTrue(Reflect.set(target, "setter2", value, null));
    assertFalse(Reflect.set(target, "getter", value, null));

    let receiver2 = {};
    Object.defineProperty(receiver2, "bla",
        {configurable: false, writable: true, value: true});
    Object.defineProperty(receiver2, "not_in_target",
        {configurable: false, writable: true, value: true});
    assertTrue(Reflect.set(target, "bla", value, receiver2));
    assertTrue(Reflect.set(target, "not_in_target", value, receiver2));
  }
})();


(function testReflectSetArrayLength() {
  var y = [];
  Object.defineProperty(y, 0, {value: 42, configurable: false});
  assertFalse(Reflect.set(y, 'length', 0));
  assertTrue(Reflect.set(y, 'length', 2));
})();



////////////////////////////////////////////////////////////////////////////////
// Reflect.has


(function testReflectHasArity() {
  assertEquals(2, Reflect.has.length);
})();


(function testReflectHasOnNonObject() {
  assertThrows(function() { Reflect.has(); }, TypeError);
  assertThrows(function() { Reflect.has(42, "bla"); }, TypeError);
  assertThrows(function() { Reflect.has(null, "bla"); }, TypeError);
})();


(function testReflectHasKeyConversion() {
  var target = {bla: 42};
  var a = { [Symbol.toPrimitive]: function() { return "bla" } };
  var b = { [Symbol.toPrimitive]: function() { throw "gaga" } };
  assertTrue(Reflect.has(target, a));
  assertThrowsEquals(function() { Reflect.has(target, b); }, "gaga");
})();


(function testReflectHasOnObject() {
  for (let target of objects) {
    prepare(target);
    assertTrue(Reflect.has(target, "bla"));
    assertTrue(Reflect.has(target, 4));
    assertTrue(Reflect.has(target, "4"));
    assertTrue(Reflect.has(target, sym));
    assertTrue(Reflect.has(target, "noconf"));
    assertTrue(Reflect.has(target, "getter"));
    assertTrue(Reflect.has(target, "setter"));
    assertFalse(Reflect.has(target, "foo"));
    assertFalse(Reflect.has(target, 666));

    let proto = target.__proto__;
    target.__proto__ = { get foo() {return this.bla} };
    assertEquals(true, Reflect.has(target, "foo"));
    target.__proto__ = proto;
  }
})();



////////////////////////////////////////////////////////////////////////////////
// Reflect.defineProperty


(function testReflectDefinePropertyArity() {
  assertEquals(3, Reflect.defineProperty.length);
})();


(function testReflectDefinePropertyOnNonObject() {
  assertThrows(function() { Reflect.defineProperty(); }, TypeError);
  assertThrows(function() { Reflect.defineProperty(42, "bla"); }, TypeError);
  assertThrows(function() { Reflect.defineProperty(null, "bla"); }, TypeError);
  assertThrows(function() { Reflect.defineProperty({}, "bla"); }, TypeError);
  assertThrows(function() { Reflect.defineProperty({}, "bla", 42); },
      TypeError);
  assertThrows(function() { Reflect.defineProperty({}, "bla", null); },
      TypeError);
})();


(function testReflectDefinePropertyKeyConversion() {
  var target = {};
  var a = { [Symbol.toPrimitive]: function() { return "bla" } };
  var b = { [Symbol.toPrimitive]: function() { throw "gaga" } };
  assertTrue(Reflect.defineProperty(target, a, {value: 42}));
  assertEquals(target.bla, 42);
  assertThrowsEquals(function() { Reflect.defineProperty(target, b); }, "gaga");
})();


(function testReflectDefinePropertyArrayLength() {
  var y = [];
  Object.defineProperty(y, 0, {value: 42, configurable: false});
  assertFalse(Reflect.defineProperty(y, 'length', {value: 0}));
  assertTrue(Reflect.defineProperty(y, 'length', {value: 2}));
})();


// See reflect-define-property.js for further tests.



////////////////////////////////////////////////////////////////////////////////
// Reflect.deleteProperty


(function testReflectDeletePropertyArity() {
  assertEquals(2, Reflect.deleteProperty.length);
})();


(function testReflectDeletePropertyOnNonObject() {
  assertThrows(function() { Reflect.deleteProperty(); }, TypeError);
  assertThrows(function() { Reflect.deleteProperty(42, "bla"); }, TypeError);
  assertThrows(function() { Reflect.deleteProperty(null, "bla"); }, TypeError);
})();


(function testReflectDeletePropertyKeyConversion() {
  var target = {bla: 42};
  var a = { [Symbol.toPrimitive]: function() { return "bla" } };
  var b = { [Symbol.toPrimitive]: function() { throw "gaga" } };
  assertTrue(Reflect.deleteProperty(target, a));
  assertThrowsEquals(function() { Reflect.deleteProperty(target, b); }, "gaga");
})();


(function testReflectDeletePropertyOnObject() {
  for (let target of objects) {
    prepare(target);
    assertTrue(Reflect.deleteProperty(target, "bla"));
    assertEquals(undefined, Object.getOwnPropertyDescriptor(target, "bla"));
    if (target instanceof Int32Array) {
      assertFalse(Reflect.deleteProperty(target, 4));
    } else {
      assertTrue(Reflect.deleteProperty(target, 4));
      assertEquals(undefined, Object.getOwnPropertyDescriptor(target, 4));
    }
    assertTrue(Reflect.deleteProperty(target, sym));
    assertEquals(undefined, Object.getOwnPropertyDescriptor(target, sym));
    assertFalse(Reflect.deleteProperty(target, "noconf"));
    assertEquals(43, target.noconf);
    assertTrue(Reflect.deleteProperty(target, "getter"));
    assertTrue(Reflect.deleteProperty(target, "setter"));
    assertTrue(Reflect.deleteProperty(target, "foo"));
    assertTrue(Reflect.deleteProperty(target, 666));

    let proto = target.__proto__;
    target.__proto__ = { get foo() {return this.bla} };
    assertEquals(true, Reflect.deleteProperty(target, "foo"));
    target.__proto__ = proto;
  }
})();



////////////////////////////////////////////////////////////////////////////////
// Reflect.getPrototypeOf


(function testReflectGetPrototypeOfArity() {
  assertEquals(1, Reflect.getPrototypeOf.length);
})();


(function testReflectGetPrototypeOnNonObject() {
  assertThrows(function() { Reflect.getPrototypeOf(); }, TypeError);
  assertThrows(function() { Reflect.getPrototypeOf(42); }, TypeError);
  assertThrows(function() { Reflect.getPrototypeOf(null); }, TypeError);
})();


// See reflect-get-prototype-of.js for further tests.



////////////////////////////////////////////////////////////////////////////////
// Reflect.setPrototypeOf


(function testReflectSetPrototypeOfArity() {
  assertEquals(2, Reflect.setPrototypeOf.length);
})();


(function testReflectSetPrototypeOfOnNonObject() {
  assertThrows(function() { Reflect.setPrototypeOf(undefined, {}); },
      TypeError);
  assertThrows(function() { Reflect.setPrototypeOf(42, {}); }, TypeError);
  assertThrows(function() { Reflect.setPrototypeOf(null, {}); }, TypeError);

  assertThrows(function() { Reflect.setPrototypeOf({}, undefined); },
      TypeError);
  assertThrows(function() { Reflect.setPrototypeOf({}, 42); }, TypeError);
  assertTrue(Reflect.setPrototypeOf({}, null));
})();


// See reflect-set-prototype-of.js for further tests.



////////////////////////////////////////////////////////////////////////////////
// Reflect.isExtensible


(function testReflectIsExtensibleArity() {
  assertEquals(1, Reflect.isExtensible.length);
})();


(function testReflectIsExtensibleOnNonObject() {
  assertThrows(function() { Reflect.isExtensible(); }, TypeError);
  assertThrows(function() { Reflect.isExtensible(42); }, TypeError);
  assertThrows(function() { Reflect.isExtensible(null); }, TypeError);
})();


(function testReflectIsExtensibleOnObject() {
  // This should be the last test on [objects] as it modifies them irreversibly.
  for (let target of objects) {
    prepare(target);
    if (target instanceof Int32Array) continue;  // issue v8:4460
    assertTrue(Reflect.isExtensible(target));
    Object.preventExtensions(target);
    assertFalse(Reflect.isExtensible(target));
  }
})();


////////////////////////////////////////////////////////////////////////////////
// Reflect.getOwnPropertyDescriptor


(function testReflectGetOwnPropertyDescriptorArity() {
  assertEquals(2, Reflect.getOwnPropertyDescriptor.length);
})();


(function testReflectGetOwnPropertyDescriptorOnNonObject() {
  assertThrows(function() { Reflect.getOwnPropertyDescriptor(); }, TypeError);
  assertThrows(function() { Reflect.getOwnPropertyDescriptor(42); },
      TypeError);
  assertThrows(function() { Reflect.getOwnPropertyDescriptor(null); },
      TypeError);
})();


(function testReflectGetOwnPropertyDescriptorKeyConversion() {
  var target = {bla: 42};
  var a = { [Symbol.toPrimitive]: function() { return "bla" } };
  var b = { [Symbol.toPrimitive]: function() { throw "gaga" } };
  assertEquals(42, Reflect.getOwnPropertyDescriptor(target, a).value);
  assertThrowsEquals(() => Reflect.getOwnPropertyDescriptor(target, b), "gaga");
})();


// See reflect-get-own-property-descriptor.js for further tests.



////////////////////////////////////////////////////////////////////////////////
// Reflect.ownKeys


(function testReflectOwnKeysArity() {
  assertEquals(1, Reflect.ownKeys.length);
})();


(function testReflectOwnKeysOnNonObject() {
  assertThrows(function() { Reflect.ownKeys(); }, TypeError);
  assertThrows(function() { Reflect.ownKeys(42); }, TypeError);
  assertThrows(function() { Reflect.ownKeys(null); }, TypeError);
})();


(function testReflectOwnKeysOnObject(){
  assertEquals(["z", "y", "x"], Reflect.ownKeys({z: 3, y: 2, x: 1}));
  assertEquals(["length"], Reflect.ownKeys([]));

  var s1 = Symbol("foo");
  var s2 = Symbol("bar");
  var obj = { [s1]: 0, "bla": 0, 42: 0, "0": 0,
      [s2]: 0, "-1": 0, "88": 0, "aaa": 0 };
  assertEquals(["0", "42", "88", "bla", "-1", "aaa", s1, s2],
      Reflect.ownKeys(obj));
  // Force dict-mode elements.
  delete obj[0];
  assertEquals(["42", "88", "bla", "-1", "aaa", s1, s2],
      Reflect.ownKeys(obj));
  // Force dict-mode properties.
  delete obj["bla"];
  assertEquals(["42", "88", "-1", "aaa", s1, s2], Reflect.ownKeys(obj));
})();


// See reflect-own-keys.js for further tests.



////////////////////////////////////////////////////////////////////////////////
// Reflect.preventExtensions


(function testReflectPreventExtensionsArity() {
  assertEquals(1, Reflect.preventExtensions.length);
})();


(function testReflectPreventExtensionsOnNonObject() {
  assertThrows(function() { Reflect.preventExtensions(); }, TypeError);
  assertThrows(function() { Reflect.preventExtensions(42); }, TypeError);
  assertThrows(function() { Reflect.preventExtensions(null); }, TypeError);
})();


// See reflect-prevent-extensions.js for further tests.

// TODO(neis): Need proxies to test the situation where
// [[preventExtensions]] returns false.
