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

// Flags: --harmony-proxies


// Helper.

function TestWithProxies(test, x, y, z) {
  test(Proxy.create, x, y, z)
  test(function(h) {return Proxy.createFunction(h, function() {})}, x, y, z)
}



// Getting.

function TestWithGet(handler) {
  TestWithProxies(TestWithGet2, handler)
}

var c = "global"
var key = ""

function TestWithGet2(create, handler) {
  var b = "local"

  var p = create(handler)
  with (p) {
    assertEquals("onproxy", a)
    assertEquals("local", b)
    assertEquals("global", c)
  }

  var o = Object.create(p, {d: {value: "own"}})
  with (o) {
    assertEquals("onproxy", a)
    assertEquals("local", b)
    assertEquals("global", c)
    assertEquals("own", d)
  }
}

TestWithGet({
  get: function(r, k) { key = k; return k === "a" ? "onproxy" : undefined },
  getPropertyDescriptor: function(k) {
    key = k;
    return k === "a" ? {value: "onproxy", configurable: true} : undefined
  }
})

TestWithGet({
  get: function(r, k) { return this.get2(r, k) },
  get2: function(r, k) { key = k; return k === "a" ? "onproxy" : undefined },
  getPropertyDescriptor: function(k) {
    key = k;
    return k === "a" ? {value: "onproxy", configurable: true} : undefined
  }
})

TestWithGet({
  getPropertyDescriptor: function(k) {
    key = k;
    return k === "a" ? {value: "onproxy", configurable: true} : undefined
  }
})

TestWithGet({
  getPropertyDescriptor: function(k) { return this.getPropertyDescriptor2(k) },
  getPropertyDescriptor2: function(k) {
    key = k;
    return k === "a" ? {value: "onproxy", configurable: true} : undefined
  }
})

TestWithGet({
  getPropertyDescriptor: function(k) {
    key = k;
    return k === "a" ?
        {get value() { return "onproxy" }, configurable: true} : undefined
  }
})

TestWithGet({
  get: undefined,
  getPropertyDescriptor: function(k) {
    key = k;
    return k === "a" ? {value: "onproxy", configurable: true} : undefined
  }
})



// Invoking.

function TestWithGetCall(handler) {
  TestWithProxies(TestWithGetCall2, handler)
}

var receiver = null
var c = function() { return "global" }

function TestWithGetCall2(create, handler) {
  var b = function() { return "local" }

  var p = create(handler)
  with (p) {
    receiver = null
    assertEquals("onproxy", a())
    assertSame(p, receiver)
    assertEquals("local", b())
    assertEquals("global", c())
  }

  var o = Object.create(p, {d: {value: function() { return "own" }}})
  with (o) {
    receiver = null
    assertEquals("onproxy", a())
    assertSame(o, receiver)
    assertEquals("local", b())
    assertEquals("global", c())
    assertEquals("own", d())
  }
}

function onproxy() { receiver = this; return "onproxy" }

TestWithGetCall({
  get: function(r, k) { key = k; return k === "a" ? onproxy : undefined },
  getPropertyDescriptor: function(k) {
    key = k;
    return k === "a" ? {value: onproxy, configurable: true} : undefined
  }
})

TestWithGetCall({
  get: function(r, k) { return this.get2(r, k) },
  get2: function(r, k) { key = k; return k === "a" ? onproxy : undefined },
  getPropertyDescriptor: function(k) {
    key = k;
    return k === "a" ? {value: onproxy, configurable: true} : undefined
  }
})

TestWithGetCall({
  getPropertyDescriptor: function(k) {
    key = k;
    return k === "a" ? {value: onproxy, configurable: true} : undefined
  }
})

TestWithGetCall({
  getPropertyDescriptor: function(k) { return this.getPropertyDescriptor2(k) },
  getPropertyDescriptor2: function(k) {
    key = k;
    return k === "a" ? {value: onproxy, configurable: true} : undefined
  }
})

TestWithGetCall({
  getPropertyDescriptor: function(k) {
    key = k;
    return k === "a" ?
        {get value() { return onproxy }, configurable: true} : undefined
  }
})

TestWithGetCall({
  get: undefined,
  getPropertyDescriptor: function(k) {
    key = k;
    return k === "a" ? {value: onproxy, configurable: true} : undefined
  }
})


function TestWithGetCallThrow(handler) {
  TestWithProxies(TestWithGetCallThrow2, handler)
}

function TestWithGetCallThrow2(create, handler) {
  var b = function() { return "local" }

  var p = create(handler)
  with (p) {
    assertThrows(function(){ a() }, "myexn")
    assertEquals("local", b())
    assertEquals("global", c())
  }

  var o = Object.create(p, {d: {value: function() { return "own" }}})
  with (o) {
    assertThrows(function(){ a() }, "myexn")
    assertEquals("local", b())
    assertEquals("global", c())
    assertEquals("own", d())
  }
}

function onproxythrow() { throw "myexn" }

TestWithGetCallThrow({
  get: function(r, k) { key = k; return k === "a" ? onproxythrow : undefined },
  getPropertyDescriptor: function(k) {
    key = k;
    return k === "a" ? {value: onproxythrow, configurable: true} : undefined
  }
})

TestWithGetCallThrow({
  get: function(r, k) { return this.get2(r, k) },
  get2: function(r, k) { key = k; return k === "a" ? onproxythrow : undefined },
  getPropertyDescriptor: function(k) {
    key = k;
    return k === "a" ? {value: onproxythrow, configurable: true} : undefined
  }
})

TestWithGetCallThrow({
  getPropertyDescriptor: function(k) {
    key = k;
    return k === "a" ? {value: onproxythrow, configurable: true} : undefined
  }
})

TestWithGetCallThrow({
  getPropertyDescriptor: function(k) { return this.getPropertyDescriptor2(k) },
  getPropertyDescriptor2: function(k) {
    key = k;
    return k === "a" ? {value: onproxythrow, configurable: true} : undefined
  }
})

TestWithGetCallThrow({
  getPropertyDescriptor: function(k) {
    key = k;
    return k === "a" ?
        {get value() { return onproxythrow }, configurable: true} : undefined
  }
})

TestWithGetCallThrow({
  get: undefined,
  getPropertyDescriptor: function(k) {
    key = k;
    return k === "a" ? {value: onproxythrow, configurable: true} : undefined
  }
})



// Setting.

var key
var val

function TestWithSet(handler, hasSetter) {
  TestWithProxies(TestWithSet2, handler, hasSetter)
}

var c = "global"

function TestWithSet2(create, handler, hasSetter) {
  var b = "local"

  var p = create(handler)
  key = val = undefined
  with (p) {
    a = "set"
    assertEquals("a", key)
    assertEquals("set", val)
    assertEquals("local", b)
    assertEquals("global", c)
    b = "local"
    c = "global"
    assertEquals("a", key)
    assertEquals("set", val)
  }

  if (!hasSetter) return

  var o = Object.create(p, {d: {value: "own"}})
  key = val = undefined
  with (o) {
    a = "set"
    assertEquals("a", key)
    assertEquals("set", val)
    assertEquals("local", b)
    assertEquals("global", c)
    assertEquals("own", d)
    b = "local"
    c = "global"
    d = "own"
    assertEquals("a", key)
    assertEquals("set", val)
  }
}

TestWithSet({
  set: function(r, k, v) { key = k; val = v; return true },
  getPropertyDescriptor: function(k) {
    return k === "a" ? {writable: true, configurable: true} : undefined
  }
})

TestWithSet({
  set: function(r, k, v) { return this.set2(r, k, v) },
  set2: function(r, k, v) { key = k; val = v; return true },
  getPropertyDescriptor: function(k) {
    return k === "a" ? {writable: true, configurable: true} : undefined
  }
})

TestWithSet({
  getPropertyDescriptor: function(k) {
    return this.getOwnPropertyDescriptor(k)
  },
  getOwnPropertyDescriptor: function(k) {
    return k === "a" ? {writable: true, configurable: true} : undefined
  },
  defineProperty: function(k, desc) { key = k; val = desc.value }
})

TestWithSet({
  getOwnPropertyDescriptor: function(k) {
    return this.getPropertyDescriptor2(k)
  },
  getPropertyDescriptor: function(k) { return this.getPropertyDescriptor2(k) },
  getPropertyDescriptor2: function(k) {
    return k === "a" ? {writable: true, configurable: true} : undefined
  },
  defineProperty: function(k, desc) { this.defineProperty2(k, desc) },
  defineProperty2: function(k, desc) { key = k; val = desc.value }
})

TestWithSet({
  getOwnPropertyDescriptor: function(k) {
    return this.getPropertyDescriptor(k)
  },
  getPropertyDescriptor: function(k) {
    return k === "a" ?
        {get writable() { return true }, configurable: true} : undefined
  },
  defineProperty: function(k, desc) { key = k; val = desc.value }
})

TestWithSet({
  getOwnPropertyDescriptor: function(k) {
    return this.getPropertyDescriptor(k)
  },
  getPropertyDescriptor: function(k) {
    return k === "a" ?
        {set: function(v) { key = k; val = v }, configurable: true} : undefined
  }
}, true)

TestWithSet({
  getOwnPropertyDescriptor: function(k) {
    return this.getPropertyDescriptor(k)
  },
  getPropertyDescriptor: function(k) { return this.getPropertyDescriptor2(k) },
  getPropertyDescriptor2: function(k) {
    return k === "a" ?
        {set: function(v) { key = k; val = v }, configurable: true} : undefined
  }
}, true)

TestWithSet({
  getOwnPropertyDescriptor: function(k) { return null },
  getPropertyDescriptor: function(k) {
    return k === "a" ? {writable: true, configurable: true} : undefined
  },
  defineProperty: function(k, desc) { key = k; val = desc.value }
})


function TestWithSetThrow(handler, hasSetter) {
  TestWithProxies(TestWithSetThrow2, handler, hasSetter)
}

function TestWithSetThrow2(create, handler, hasSetter) {
  var p = create(handler)
  assertThrows(function(){
    with (p) {
      a = 1
    }
  }, "myexn")

  if (!hasSetter) return

  var o = Object.create(p, {})
  assertThrows(function(){
    with (o) {
      a = 1
    }
  }, "myexn")
}

TestWithSetThrow({
  set: function(r, k, v) { throw "myexn" },
  getPropertyDescriptor: function(k) {
    return k === "a" ? {writable: true, configurable: true} : undefined
  }
})

TestWithSetThrow({
  getPropertyDescriptor: function(k) { throw "myexn" },
})

TestWithSetThrow({
  getPropertyDescriptor: function(k) {
    return k === "a" ? {writable: true, configurable: true} : undefined
  },
  defineProperty: function(k, desc) { throw "myexn" }
})

TestWithSetThrow({
  getPropertyDescriptor: function(k) {
    return k === "a" ?
        {set: function() { throw "myexn" }, configurable: true} : undefined
  }
}, true)