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

"use strict";

// This file relies on the fact that the following declaration has been made
// in runtime.js:
// var $Object = global.Object;

var $Proxy = new $Object();

// -------------------------------------------------------------------

function ProxyCreate(handler, proto) {
  if (!IS_SPEC_OBJECT(handler))
    throw MakeTypeError("handler_non_object", ["create"])
  if (IS_UNDEFINED(proto))
    proto = null
  else if (!(IS_SPEC_OBJECT(proto) || IS_NULL(proto)))
    throw MakeTypeError("proto_non_object", ["create"])
  return %CreateJSProxy(handler, proto)
}

function ProxyCreateFunction(handler, callTrap, constructTrap) {
  if (!IS_SPEC_OBJECT(handler))
    throw MakeTypeError("handler_non_object", ["create"])
  if (!IS_SPEC_FUNCTION(callTrap))
    throw MakeTypeError("trap_function_expected", ["createFunction", "call"])
  if (IS_UNDEFINED(constructTrap)) {
    constructTrap = DerivedConstructTrap(callTrap)
  } else if (IS_SPEC_FUNCTION(constructTrap)) {
    // Make sure the trap receives 'undefined' as this.
    var construct = constructTrap
    constructTrap = function() {
      return %Apply(construct, UNDEFINED, arguments, 0, %_ArgumentsLength());
    }
  } else {
    throw MakeTypeError("trap_function_expected",
                        ["createFunction", "construct"])
  }
  return %CreateJSFunctionProxy(
    handler, callTrap, constructTrap, $Function.prototype)
}


// -------------------------------------------------------------------

function SetUpProxy() {
  %CheckIsBootstrapping()

  global.Proxy = $Proxy;

  // Set up non-enumerable properties of the Proxy object.
  InstallFunctions($Proxy, DONT_ENUM, [
    "create", ProxyCreate,
    "createFunction", ProxyCreateFunction
  ])
}

SetUpProxy();


// -------------------------------------------------------------------
// Proxy Builtins

function DerivedConstructTrap(callTrap) {
  return function() {
    var proto = this.prototype
    if (!IS_SPEC_OBJECT(proto)) proto = $Object.prototype
    var obj = { __proto__: proto };
    var result = %Apply(callTrap, obj, arguments, 0, %_ArgumentsLength());
    return IS_SPEC_OBJECT(result) ? result : obj
  }
}

function DelegateCallAndConstruct(callTrap, constructTrap) {
  return function() {
    return %Apply(%_IsConstructCall() ? constructTrap : callTrap,
                  this, arguments, 0, %_ArgumentsLength())
  }
}

function DerivedGetTrap(receiver, name) {
  var desc = this.getPropertyDescriptor(name)
  if (IS_UNDEFINED(desc)) { return desc }
  if ('value' in desc) {
    return desc.value
  } else {
    if (IS_UNDEFINED(desc.get)) { return desc.get }
    // The proposal says: desc.get.call(receiver)
    return %_CallFunction(receiver, desc.get)
  }
}

function DerivedSetTrap(receiver, name, val) {
  var desc = this.getOwnPropertyDescriptor(name)
  if (desc) {
    if ('writable' in desc) {
      if (desc.writable) {
        desc.value = val
        this.defineProperty(name, desc)
        return true
      } else {
        return false
      }
    } else { // accessor
      if (desc.set) {
        // The proposal says: desc.set.call(receiver, val)
        %_CallFunction(receiver, val, desc.set)
        return true
      } else {
        return false
      }
    }
  }
  desc = this.getPropertyDescriptor(name)
  if (desc) {
    if ('writable' in desc) {
      if (desc.writable) {
        // fall through
      } else {
        return false
      }
    } else { // accessor
      if (desc.set) {
        // The proposal says: desc.set.call(receiver, val)
        %_CallFunction(receiver, val, desc.set)
        return true
      } else {
        return false
      }
    }
  }
  this.defineProperty(name, {
    value: val,
    writable: true,
    enumerable: true,
    configurable: true});
  return true;
}

function DerivedHasTrap(name) {
  return !!this.getPropertyDescriptor(name)
}

function DerivedHasOwnTrap(name) {
  return !!this.getOwnPropertyDescriptor(name)
}

function DerivedKeysTrap() {
  var names = this.getOwnPropertyNames()
  var enumerableNames = []
  for (var i = 0, count = 0; i < names.length; ++i) {
    var name = names[i]
    if (IS_SYMBOL(name)) continue
    var desc = this.getOwnPropertyDescriptor(TO_STRING_INLINE(name))
    if (!IS_UNDEFINED(desc) && desc.enumerable) {
      enumerableNames[count++] = names[i]
    }
  }
  return enumerableNames
}

function DerivedEnumerateTrap() {
  var names = this.getPropertyNames()
  var enumerableNames = []
  for (var i = 0, count = 0; i < names.length; ++i) {
    var name = names[i]
    if (IS_SYMBOL(name)) continue
    var desc = this.getPropertyDescriptor(TO_STRING_INLINE(name))
    if (!IS_UNDEFINED(desc)) {
      if (!desc.configurable) {
        throw MakeTypeError("proxy_prop_not_configurable",
            [this, "getPropertyDescriptor", name])
      }
      if (desc.enumerable) enumerableNames[count++] = names[i]
    }
  }
  return enumerableNames
}

function ProxyEnumerate(proxy) {
  var handler = %GetHandler(proxy)
  if (IS_UNDEFINED(handler.enumerate)) {
    return %Apply(DerivedEnumerateTrap, handler, [], 0, 0)
  } else {
    return ToNameArray(handler.enumerate(), "enumerate", false)
  }
}