// Copyright 2014 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//     * Neither the name of Google Inc. nor the names of its
//       contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


function getObjects() {
  function func() {}
  return [
    func,
    new func(),
    {x: 5},
    /regexp/,
    ['array'],
    // new Error(),
    new Date(),
    new Number(1),
    new Boolean(true),
    new String('str'),
    Object(Symbol())
  ];
}


var coercibleValues = [
  1,
  true,
  'string',
  Symbol()
];


var nonCoercibleValues = [
  undefined,
  null
];


var valuesWithoutNull = coercibleValues.concat(undefined);


function TestSetPrototypeOfCoercibleValues() {
  for (var i = 0; i < coercibleValues.length; i++) {
    var value = coercibleValues[i];
    var proto = Object.getPrototypeOf(value);
    assertEquals(Object.setPrototypeOf(value, {}), value);
    assertSame(proto, Object.getPrototypeOf(value));
  }
}
TestSetPrototypeOfCoercibleValues();


function TestSetPrototypeOfNonCoercibleValues() {
  for (var i = 0; i < nonCoercibleValues.length; i++) {
    var value = nonCoercibleValues[i];
    assertThrows(function() {
      Object.setPrototypeOf(value, {});
    }, TypeError);
  }
}
TestSetPrototypeOfNonCoercibleValues();


function TestSetPrototypeToNonObject(proto) {
  var objects = getObjects();
  for (var i = 0; i < objects.length; i++) {
    var object = objects[i];
    for (var j = 0; j < valuesWithoutNull.length; j++) {
      var proto = valuesWithoutNull[j];
      assertThrows(function() {
        Object.setPrototypeOf(object, proto);
      }, TypeError);
    }
  }
}
TestSetPrototypeToNonObject();


function TestSetPrototypeOf(object, proto) {
  assertEquals(Object.setPrototypeOf(object, proto), object);
  assertEquals(Object.getPrototypeOf(object), proto);
}


function TestSetPrototypeOfForObjects() {
  var objects1 = getObjects();
  var objects2 = getObjects();
  for (var i = 0; i < objects1.length; i++) {
    for (var j = 0; j < objects2.length; j++) {
      TestSetPrototypeOf(objects1[i], objects2[j]);
    }
  }
}
TestSetPrototypeOfForObjects();


function TestSetPrototypeToNull() {
  var objects = getObjects();
  for (var i = 0; i < objects.length; i++) {
    TestSetPrototypeOf(objects[i], null);
  }
}
TestSetPrototypeToNull();


function TestSetPrototypeOfNonExtensibleObject() {
  var objects = getObjects();
  var proto = {};
  for (var i = 0; i < objects.length; i++) {
    var object = objects[i];
    Object.preventExtensions(object);
    // Setting the current prototype must succeed.
    Object.setPrototypeOf(object, Object.getPrototypeOf(object));
    // Setting any other must fail.
    assertThrows(function() {
      Object.setPrototypeOf(object, proto);
    }, TypeError);
  }
}
TestSetPrototypeOfNonExtensibleObject();


function TestSetPrototypeCyclic() {
  var objects = [
    Object.prototype, {},
    Array.prototype, [],
    Error.prototype, new TypeError,
    // etc ...
  ];
  for (var i = 0; i < objects.length; i += 2) {
    var object = objects[i];
    var value = objects[i + 1];
    assertThrows(function() {
      Object.setPrototypeOf(object, value);
    }, TypeError);
  }
}
TestSetPrototypeCyclic();


function TestLookup() {
  var object = {};
  assertFalse('x' in object);
  assertFalse('y' in object);

  var oldProto = {
    x: 'old x',
    y: 'old y'
  };
  Object.setPrototypeOf(object, oldProto);
  assertEquals(object.x, 'old x');
  assertEquals(object.y, 'old y');

  var newProto = {
    x: 'new x'
  };
  Object.setPrototypeOf(object, newProto);
  assertEquals(object.x, 'new x');
  assertFalse('y' in object);
}
TestLookup();
