// Copyright 2014 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.
//
// Flags: --harmony-classes


(function TestSuperNamedLoads() {
  function Base() { }
  function Derived() {
    this.derivedDataProperty = "xxx";
  }
  Derived.prototype = Object.create(Base.prototype);

  function fBase() { return "Base " + this.toString(); }

  Base.prototype.f = fBase.toMethod(Base.prototype);

  function fDerived() {
     assertEquals("Base this is Derived", super.f());
     var a = super.x;
     assertEquals(15, a);
     assertEquals(15, super.x);
     assertEquals(27, this.x);

     return "Derived"
  }

  Base.prototype.x = 15;
  Base.prototype.toString = function() { return "this is Base"; };
  Derived.prototype.toString = function() { return "this is Derived"; };
  Derived.prototype.x = 27;
  Derived.prototype.f = fDerived.toMethod(Derived.prototype);

  assertEquals("Base this is Base", new Base().f());
  assertEquals("Derived", new Derived().f());
}());


(function TestSuperKeywordNonMethod() {
  function f() {
    super.unknown();
  }

  assertThrows(f, ReferenceError);
}());


(function TestGetter() {
  function Base() {}
  var derived;
  Base.prototype = {
    constructor: Base,
    get x() {
      assertSame(this, derived);
      return this._x;
    },
    _x: 'base'
  };

  function Derived() {}
  Derived.__proto__ = Base;
  Derived.prototype = {
    __proto__: Base.prototype,
    constructor: Derived,
    _x: 'derived'
  };
  Derived.prototype.testGetter = function() {
    return super.x;
  }.toMethod(Derived.prototype);
  Derived.prototype.testGetterStrict = function() {
    'use strict';
    return super.x;
  }.toMethod(Derived.prototype);
  derived = new Derived();
  assertEquals('derived', derived.testGetter());
  derived = new Derived();
  assertEquals('derived', derived.testGetterStrict());
}());


(function TestSetter() {
  function Base() {}
  Base.prototype = {
    constructor: Base,
    get x() {
      return this._x;
    },
    set x(v) {
      this._x = v;
    },
    _x: 'base'
  };

  function Derived() {}
  Derived.__proto__ = Base;
  Derived.prototype = {
    __proto__: Base.prototype,
    constructor: Derived,
    _x: 'derived'
  };
  Derived.prototype.testSetter = function() {
    assertEquals('foobar', super.x = 'foobar');
    assertEquals('foobarabc', super.x += 'abc');
  }.toMethod(Derived.prototype);
  var d = new Derived();
  d.testSetter();
  assertEquals('base', Base.prototype._x);
  assertEquals('foobarabc', d._x);
  d._x = '';
  Derived.prototype.testSetterStrict = function() {
    'use strict';
    assertEquals('foobar', super.x = 'foobar');
    assertEquals('foobarabc', super.x += 'abc');
  }.toMethod(Derived.prototype);
  d.testSetterStrict();
  assertEquals('base', Base.prototype._x);
  assertEquals('foobarabc', d._x);
}());


(function TestSetterDataProperties() {
  function Base() {}
  Base.prototype = {
    constructor: Base,
    x: 'x from Base'
  };

  function Derived() {}
  Derived.prototype = {
    __proto__: Base.prototype,
    constructor: Derived,
  };

  Derived.prototype.testSetter = function() {
    assertEquals('x from Base', super.x);
    super.x = 'data property';
    assertEquals('x from Base', super.x);
    assertEquals('data property', this.x);
  }.toMethod(Derived.prototype);

  new Derived().testSetter();
}());


(function TestAccessorsOnPrimitives() {
  var getCalled = 0;
  var setCalled = 0;
  function Base() {}
  Base.prototype = {
    constructor: Base,
    get x() {
      getCalled++;
      return 1;
    },
    set x(v) {
      setCalled++;
      return v;
    },
  };

  function Derived() {}
  Derived.prototype = {
    __proto__: Base.prototype,
    constructor: Derived,
  };
  Derived.prototype.testSetter = function() {
    setCalled = 0;
    getCalled = 0;
    assertEquals('object', typeof this);
    assertTrue(this instanceof Number)
    assertEquals(42, this.valueOf());
    assertEquals(1, super.x);
    assertEquals(1, getCalled);
    assertEquals(0, setCalled);

    assertEquals(5, super.x = 5);
    assertEquals(1, getCalled);
    assertEquals(1, setCalled);

    assertEquals(6, super.x += 5);
    assertEquals(2, getCalled);
    assertEquals(2, setCalled);

    super.newProperty = 15;
    assertEquals(15, this.newProperty);
    assertEquals(undefined, super.newProperty);
  }.toMethod(Derived.prototype);

  Derived.prototype.testSetterStrict = function() {
    'use strict';
    getCalled = 0;
    setCalled = 0;
    assertTrue(42 === this);

    assertEquals(1, super.x);
    assertEquals(1, getCalled);
    assertEquals(0, setCalled);

    assertEquals(5, super.x = 5);
    assertEquals(1, getCalled);
    assertEquals(1, setCalled);

    assertEquals(6, super.x += 5);
    assertEquals(2, getCalled);
    assertEquals(2, setCalled);

    var ex;
    try {
      super.newProperty = 15;
    } catch (e) { ex = e; }
    assertTrue(ex instanceof TypeError);
  }.toMethod(Derived.prototype);

  Derived.prototype.testSetter.call(42);
  Derived.prototype.testSetterStrict.call(42);

  function DerivedFromString() {}
  DerivedFromString.prototype = Object.create(String.prototype);

  function f() {
    'use strict';
    assertTrue(42 === this);
    assertEquals(String.prototype.toString, super.toString);
    var ex;
    try {
      super.toString();
    } catch(e) { ex = e; }

    assertTrue(ex instanceof TypeError);
  }
  f.toMethod(DerivedFromString.prototype).call(42);
}());


(function TestSetterUndefinedProperties() {
  function Base() {}
  function Derived() {}
  Derived.prototype = { __proto__ : Base.prototype };
  Derived.prototype.mSloppy = function () {
    assertEquals(undefined, super.x);
    assertEquals(undefined, this.x);
    super.x = 10;
    assertEquals(10, this.x);
    assertEquals(undefined, super.x);
  }.toMethod(Derived.prototype);

  Derived.prototype.mStrict = function () {
    'use strict';
    assertEquals(undefined, super.x);
    assertEquals(undefined, this.x);
    super.x = 10;
    assertEquals(10, this.x);
    assertEquals(undefined, super.x);
  }.toMethod(Derived.prototype);
  var d = new Derived();
  d.mSloppy();
  assertEquals(10, d.x);
  var d1 = new Derived();
  d1.mStrict();
  assertEquals(10, d.x);
}());


(function TestSetterCreatingOwnProperties() {
  function Base() {}
  function Derived() {}
  Derived.prototype = { __proto__ : Base.prototype };
  var setterCalled;

  Derived.prototype.mSloppy = function() {
    assertEquals(42, this.ownReadOnly);
    super.ownReadOnly = 55;
    assertEquals(42, this.ownReadOnly);

    assertEquals(15, this.ownReadonlyAccessor);
    super.ownReadonlyAccessor = 55;
    assertEquals(15, this.ownReadonlyAccessor);

    setterCalled = 0;
    super.ownSetter = 42;
    assertEquals(1, setterCalled);
  }.toMethod(Derived.prototype);

  Derived.prototype.mStrict = function() {
    'use strict';
    assertEquals(42, this.ownReadOnly);
    var ex;
    try {
      super.ownReadOnly = 55;
    } catch(e) { ex = e; }
    assertTrue(ex instanceof TypeError);
    assertEquals(42, this.ownReadOnly);

    assertEquals(15, this.ownReadonlyAccessor);
    ex = null;
    try {
      super.ownReadonlyAccessor = 55;
    } catch(e) { ex = e; }
    assertTrue(ex instanceof TypeError);
    assertEquals(15, this.ownReadonlyAccessor);

    setterCalled = 0;
    super.ownSetter = 42;
    assertEquals(1, setterCalled);
  }.toMethod(Derived.prototype);

  var d = new Derived();
  Object.defineProperty(d, 'ownReadOnly', { value : 42, writable : false });
  Object.defineProperty(d, 'ownSetter',
      { set : function() { setterCalled++; } });
  Object.defineProperty(d, 'ownReadonlyAccessor',
      { get : function() { return 15; }});
  d.mSloppy();
  d.mStrict();
}());


(function TestSetterNoProtoWalk() {
  function Base() {}
  function Derived() {}
  var getCalled;
  var setCalled;
  Derived.prototype = {
    __proto__ : Base.prototype,
    get x() { getCalled++; return 42; },
    set x(v) { setCalled++; }
  };

  Derived.prototype.mSloppy = function() {
    setCalled = 0;
    getCalled = 0;
    assertEquals(42, this.x);
    assertEquals(1, getCalled);
    assertEquals(0, setCalled);

    getCalled = 0;
    setCalled = 0;
    this.x = 43;
    assertEquals(0, getCalled);
    assertEquals(1, setCalled);

    getCalled = 0;
    setCalled = 0;
    super.x = 15;
    assertEquals(0, setCalled);
    assertEquals(0, getCalled);

    assertEquals(15, this.x);
    assertEquals(0, getCalled);
    assertEquals(0, setCalled);

  }.toMethod(Derived.prototype);

  Derived.prototype.mStrict = function() {
    'use strict';
    setCalled = 0;
    getCalled = 0;
    assertEquals(42, this.x);
    assertEquals(1, getCalled);
    assertEquals(0, setCalled);

    getCalled = 0;
    setCalled = 0;
    this.x = 43;
    assertEquals(0, getCalled);
    assertEquals(1, setCalled);

    getCalled = 0;
    setCalled = 0;
    super.x = 15;
    assertEquals(0, setCalled);
    assertEquals(0, getCalled);

    assertEquals(15, this.x);
    assertEquals(0, getCalled);
    assertEquals(0, setCalled);

  }.toMethod(Derived.prototype);

  new Derived().mSloppy();
  new Derived().mStrict();
}());


(function TestSetterDoesNotReconfigure() {
  function Base() {}
  function Derived() {}

  Derived.prototype.mStrict = function (){
    'use strict';
    super.nonEnumConfig = 5;
    var d1 = Object.getOwnPropertyDescriptor(this, 'nonEnumConfig');
    assertEquals(5, d1.value);
    assertTrue(d1.configurable);
    assertFalse(d1.enumerable);

    super.nonEnumNonConfig = 5;
    var d1 = Object.getOwnPropertyDescriptor(this, 'nonEnumNonConfig');
    assertEquals(5, d1.value);
    assertFalse(d1.configurable);
    assertFalse(d1.enumerable);
  }.toMethod(Derived.prototype);

  Derived.prototype.mSloppy = function (){
    super.nonEnumConfig = 42;
    var d1 = Object.getOwnPropertyDescriptor(this, 'nonEnumConfig');
    assertEquals(42, d1.value);
    assertTrue(d1.configurable);
    assertFalse(d1.enumerable);

    super.nonEnumNonConfig = 42;
    var d1 = Object.getOwnPropertyDescriptor(this, 'nonEnumNonConfig');
    assertEquals(42, d1.value);
    assertFalse(d1.configurable);
    assertFalse(d1.enumerable);
  }.toMethod(Derived.prototype);

  var d = new Derived();
  Object.defineProperty(d, 'nonEnumConfig',
      { value : 0, enumerable : false, configurable : true, writable : true });
  Object.defineProperty(d, 'nonEnumNonConfig',
      { value : 0, enumerable : false, configurable : false, writable : true });
  d.mStrict();
  d.mSloppy();
}());


(function TestCountOperations() {
  function Base() {}
  Base.prototype = {
    constructor: Base,
    get x() {
      return this._x;
    },
    set x(v) {
      this._x = v;
    },
    _x: 1
  };

  function Derived() {}
  Derived.__proto__ = Base;
  Derived.prototype = {
    __proto__: Base.prototype,
    constructor: Derived,
    _x: 2
  };

  Derived.prototype.testCounts = function() {
    assertEquals(2, this._x);
    assertEquals(2, super.x);
    super.x++;
    assertEquals(3, super.x);
    ++super.x;
    assertEquals(4, super.x);
    assertEquals(4, super.x++);
    assertEquals(5, super.x);
    assertEquals(6, ++super.x);
    assertEquals(6, super.x);
    assertEquals(6, this._x);

    super.x--;
    assertEquals(5, super.x);
    --super.x;
    assertEquals(4, super.x);
    assertEquals(4, super.x--);
    assertEquals(3, super.x);
    assertEquals(2, --super.x);
    assertEquals(2, super.x);
    assertEquals(2, this._x);
  }.toMethod(Derived.prototype);
  new Derived().testCounts();
}());


(function TestSuperCall() {
  function Subclass(base, constructor) {
    var homeObject = { __proto__ : base.prototype };
    var result = constructor.toMethod(homeObject);
    homeObject.constructor = result;
    result.prototype = homeObject;
    return result;
  }

  var baseCalled = 0;
  var derivedCalled = 0;
  var derivedDerivedCalled = 0;

  function Base() {
    baseCalled++;
  }

  var Derived = Subclass(Base, function () {
    super();
    derivedCalled++;
  });

  assertEquals(Base, Base.prototype.constructor);
  assertEquals(Base.prototype, Derived.prototype.__proto__);

  baseCalled = 0;
  derivedCalled = 0;
  new Derived();
  assertEquals(1, baseCalled);
  assertEquals(1, derivedCalled);

  var DerivedDerived = Subclass(Derived, function () {
    super();
    derivedDerivedCalled++;
  });

  baseCalled = 0;
  derivedCalled = 0;
  derivedDerivedCalled = 0;
  new DerivedDerived();
  assertEquals(1, baseCalled);
  assertEquals(1, derivedCalled);
  assertEquals(1, derivedDerivedCalled);

  function Base2(v) {
    this.fromBase = v;
  }
  var Derived2 = Subclass(Base2, function (v1, v2) {
    super(v1);
    this.fromDerived = v2;
  });

  var d = new Derived2("base", "derived");
  assertEquals("base", d.fromBase);
  assertEquals("derived", d.fromDerived);
}());


(function TestUnsupportedCases() {
  function f1(x) { return super[x]; }
  function f2(x) { super[x] = 5; }
  var o = {};
  assertThrows(function(){f1.toMethod(o)(x);}, ReferenceError);
  assertThrows(function(){f2.toMethod(o)(x);}, ReferenceError);
}());
