/*
 *  Object built-ins
 */

#include "duk_internal.h"

DUK_INTERNAL duk_ret_t duk_bi_object_constructor(duk_context *ctx) {
	if (!duk_is_constructor_call(ctx) &&
	    !duk_is_null_or_undefined(ctx, 0)) {
		duk_to_object(ctx, 0);
		return 1;
	}

	if (duk_is_object(ctx, 0)) {
		return 1;
	}

	/* Pointer and buffer primitive values are treated like other
	 * primitives values which have a fully fledged object counterpart:
	 * promote to an object value.  Lightfuncs are coerced with
	 * ToObject() even they could also be returned as is.
	 */
	if (duk_check_type_mask(ctx, 0, DUK_TYPE_MASK_STRING |
	                                DUK_TYPE_MASK_BOOLEAN |
	                                DUK_TYPE_MASK_NUMBER |
	                                DUK_TYPE_MASK_POINTER |
	                                DUK_TYPE_MASK_BUFFER |
	                                DUK_TYPE_MASK_LIGHTFUNC)) {
		duk_to_object(ctx, 0);
		return 1;
	}

	duk_push_object_helper(ctx,
	                       DUK_HOBJECT_FLAG_EXTENSIBLE |
	                       DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT),
	                       DUK_BIDX_OBJECT_PROTOTYPE);
	return 1;
}

/* Shared helper to implement Object.getPrototypeOf and the ES6
 * Object.prototype.__proto__ getter.
 *
 * http://www.ecma-international.org/ecma-262/6.0/index.html#sec-get-object.prototype.__proto__
 */
DUK_INTERNAL duk_ret_t duk_bi_object_getprototype_shared(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *h;
	duk_hobject *proto;

	DUK_UNREF(thr);

	/* magic: 0=getter call, 1=Object.getPrototypeOf */
	if (duk_get_current_magic(ctx) == 0) {
		duk_push_this_coercible_to_object(ctx);
		duk_insert(ctx, 0);
	}

	h = duk_require_hobject_or_lfunc(ctx, 0);
	/* h is NULL for lightfunc */

	/* XXX: should the API call handle this directly, i.e. attempt
	 * to duk_push_hobject(ctx, null) would push a null instead?
	 * (On the other hand 'undefined' would be just as logical, but
	 * not wanted here.)
	 */

	if (h == NULL) {
		duk_push_hobject_bidx(ctx, DUK_BIDX_FUNCTION_PROTOTYPE);
	} else {
		proto = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h);
		if (proto) {
			duk_push_hobject(ctx, proto);
		} else {
			duk_push_null(ctx);
		}
	}
	return 1;
}

/* Shared helper to implement ES6 Object.setPrototypeOf and
 * Object.prototype.__proto__ setter.
 *
 * http://www.ecma-international.org/ecma-262/6.0/index.html#sec-get-object.prototype.__proto__
 * http://www.ecma-international.org/ecma-262/6.0/index.html#sec-object.setprototypeof
 */
DUK_INTERNAL duk_ret_t duk_bi_object_setprototype_shared(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *h_obj;
	duk_hobject *h_new_proto;
	duk_hobject *h_curr;
	duk_ret_t ret_success = 1;  /* retval for success path */

	/* Preliminaries for __proto__ and setPrototypeOf (E6 19.1.2.18 steps 1-4);
	 * magic: 0=setter call, 1=Object.setPrototypeOf
	 */
	if (duk_get_current_magic(ctx) == 0) {
		duk_push_this_check_object_coercible(ctx);
		duk_insert(ctx, 0);
		if (!duk_check_type_mask(ctx, 1, DUK_TYPE_MASK_NULL | DUK_TYPE_MASK_OBJECT)) {
			return 0;
		}

		/* __proto__ setter returns 'undefined' on success unlike the
		 * setPrototypeOf() call which returns the target object.
		 */
		ret_success = 0;
	} else {
		duk_require_object_coercible(ctx, 0);
		duk_require_type_mask(ctx, 1, DUK_TYPE_MASK_NULL | DUK_TYPE_MASK_OBJECT);
	}

	h_new_proto = duk_get_hobject(ctx, 1);
	/* h_new_proto may be NULL */
	if (duk_is_lightfunc(ctx, 0)) {
		if (h_new_proto == thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]) {
			goto skip;
		}
		goto fail_nonextensible;
	}
	h_obj = duk_get_hobject(ctx, 0);
	if (!h_obj) {
		goto skip;
	}
	DUK_ASSERT(h_obj != NULL);

	/* [[SetPrototypeOf]] standard behavior, E6 9.1.2 */
	/* TODO: implement Proxy object support here */

	if (h_new_proto == DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h_obj)) {
		goto skip;
	}
	if (!DUK_HOBJECT_HAS_EXTENSIBLE(h_obj)) {
		goto fail_nonextensible;
	}
	for (h_curr = h_new_proto; h_curr != NULL; h_curr = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h_curr)) {
		/* Loop prevention */
		if (h_curr == h_obj) {
			goto fail_loop;
		}
	}
	DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, h_obj, h_new_proto);
	/* fall thru */

 skip:
	duk_set_top(ctx, 1);
	return ret_success;

 fail_nonextensible:
 fail_loop:
	return DUK_RET_TYPE_ERROR;
}

DUK_INTERNAL duk_ret_t duk_bi_object_constructor_get_own_property_descriptor(duk_context *ctx) {
	/* XXX: no need for indirect call */
	return duk_hobject_object_get_own_property_descriptor(ctx);
}

DUK_INTERNAL duk_ret_t duk_bi_object_constructor_create(duk_context *ctx) {
	duk_tval *tv;
	duk_hobject *proto = NULL;

	DUK_ASSERT_TOP(ctx, 2);

	tv = duk_get_tval(ctx, 0);
	DUK_ASSERT(tv != NULL);
	if (DUK_TVAL_IS_NULL(tv)) {
		;
	} else if (DUK_TVAL_IS_OBJECT(tv)) {
		proto = DUK_TVAL_GET_OBJECT(tv);
		DUK_ASSERT(proto != NULL);
	} else {
		return DUK_RET_TYPE_ERROR;
	}

	(void) duk_push_object_helper_proto(ctx,
	                                    DUK_HOBJECT_FLAG_EXTENSIBLE |
	                                    DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT),
	                                    proto);

	if (!duk_is_undefined(ctx, 1)) {
		/* [ O Properties obj ] */

		duk_replace(ctx, 0);

		/* [ obj Properties ] */

		/* Just call the "original" Object.defineProperties() to
		 * finish up.
		 */

		return duk_bi_object_constructor_define_properties(ctx);
	}

	/* [ O Properties obj ] */

	return 1;
}

DUK_INTERNAL duk_ret_t duk_bi_object_constructor_define_property(duk_context *ctx) {
	duk_hobject *obj;
	duk_hstring *key;
	duk_hobject *get;
	duk_hobject *set;
	duk_idx_t idx_value;
	duk_uint_t defprop_flags;

	DUK_ASSERT(ctx != NULL);

	DUK_DDD(DUK_DDDPRINT("Object.defineProperty(): ctx=%p obj=%!T key=%!T desc=%!T",
	                     (void *) ctx,
	                     (duk_tval *) duk_get_tval(ctx, 0),
	                     (duk_tval *) duk_get_tval(ctx, 1),
	                     (duk_tval *) duk_get_tval(ctx, 2)));

	/* [ obj key desc ] */

	/* Lightfuncs are currently supported by coercing to a temporary
	 * Function object; changes will be allowed (the coerced value is
	 * extensible) but will be lost.
	 */
	obj = duk_require_hobject_or_lfunc_coerce(ctx, 0);
	(void) duk_to_string(ctx, 1);
	key = duk_require_hstring(ctx, 1);
	(void) duk_require_hobject(ctx, 2);

	DUK_ASSERT(obj != NULL);
	DUK_ASSERT(key != NULL);
	DUK_ASSERT(duk_get_hobject(ctx, 2) != NULL);

	/*
	 *  Validate and convert argument property descriptor (an Ecmascript
	 *  object) into a set of defprop_flags and possibly property value,
	 *  getter, and/or setter values on the value stack.
	 *
	 *  Lightfunc set/get values are coerced to full Functions.
	 */

	duk_hobject_prepare_property_descriptor(ctx,
	                                        2 /*idx_desc*/,
	                                        &defprop_flags,
	                                        &idx_value,
	                                        &get,
	                                        &set);

	/*
	 *  Use Object.defineProperty() helper for the actual operation.
	 */

	duk_hobject_define_property_helper(ctx,
	                                   defprop_flags,
	                                   obj,
	                                   key,
	                                   idx_value,
	                                   get,
	                                   set);

	/* Ignore the normalize/validate helper outputs on the value stack,
	 * they're popped automatically.
	 */

	/*
	 *  Return target object.
	 */

	duk_push_hobject(ctx, obj);
	return 1;
}

DUK_INTERNAL duk_ret_t duk_bi_object_constructor_define_properties(duk_context *ctx) {
	duk_small_uint_t pass;
	duk_uint_t defprop_flags;
	duk_hobject *obj;
	duk_idx_t idx_value;
	duk_hobject *get;
	duk_hobject *set;

	/* Lightfunc handling by ToObject() coercion. */
	obj = duk_require_hobject_or_lfunc_coerce(ctx, 0);  /* target */
	DUK_ASSERT(obj != NULL);

	duk_to_object(ctx, 1);        /* properties object */

	DUK_DDD(DUK_DDDPRINT("target=%!iT, properties=%!iT",
	                     (duk_tval *) duk_get_tval(ctx, 0),
	                     (duk_tval *) duk_get_tval(ctx, 1)));

	/*
	 *  Two pass approach to processing the property descriptors.
	 *  On first pass validate and normalize all descriptors before
	 *  any changes are made to the target object.  On second pass
	 *  make the actual modifications to the target object.
	 *
	 *  Right now we'll just use the same normalize/validate helper
	 *  on both passes, ignoring its outputs on the first pass.
	 */

	for (pass = 0; pass < 2; pass++) {
		duk_set_top(ctx, 2);  /* -> [ hobject props ] */
		duk_enum(ctx, 1, DUK_ENUM_OWN_PROPERTIES_ONLY /*enum_flags*/);

		for (;;) {
			duk_hstring *key;

			/* [ hobject props enum(props) ] */

			duk_set_top(ctx, 3);

			if (!duk_next(ctx, 2, 1 /*get_value*/)) {
				break;
			}

			DUK_DDD(DUK_DDDPRINT("-> key=%!iT, desc=%!iT",
			                     (duk_tval *) duk_get_tval(ctx, -2),
			                     (duk_tval *) duk_get_tval(ctx, -1)));

			/* [ hobject props enum(props) key desc ] */

			duk_hobject_prepare_property_descriptor(ctx,
			                                        4 /*idx_desc*/,
			                                        &defprop_flags,
			                                        &idx_value,
			                                        &get,
			                                        &set);

			/* [ hobject props enum(props) key desc value? getter? setter? ] */

			if (pass == 0) {
				continue;
			}

			key = duk_get_hstring(ctx, 3);
			DUK_ASSERT(key != NULL);

			duk_hobject_define_property_helper(ctx,
			                                   defprop_flags,
			                                   obj,
			                                   key,
			                                   idx_value,
			                                   get,
			                                   set);
		}
	}

	/*
	 *  Return target object
	 */

	duk_dup(ctx, 0);
	return 1;
}

DUK_INTERNAL duk_ret_t duk_bi_object_constructor_seal_freeze_shared(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *h;
	duk_bool_t is_freeze;

	h = duk_require_hobject_or_lfunc(ctx, 0);
	if (!h) {
		/* Lightfunc, always success. */
		return 1;
	}

	is_freeze = (duk_bool_t) duk_get_current_magic(ctx);
	duk_hobject_object_seal_freeze_helper(thr, h, is_freeze);

	/* Sealed and frozen objects cannot gain any more properties,
	 * so this is a good time to compact them.
	 */
	duk_hobject_compact_props(thr, h);

	return 1;
}

DUK_INTERNAL duk_ret_t duk_bi_object_constructor_prevent_extensions(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *h;

	h = duk_require_hobject_or_lfunc(ctx, 0);
	if (!h) {
		/* Lightfunc, always success. */
		return 1;
	}
	DUK_ASSERT(h != NULL);

	DUK_HOBJECT_CLEAR_EXTENSIBLE(h);

	/* A non-extensible object cannot gain any more properties,
	 * so this is a good time to compact.
	 */
	duk_hobject_compact_props(thr, h);

	return 1;
}

DUK_INTERNAL duk_ret_t duk_bi_object_constructor_is_sealed_frozen_shared(duk_context *ctx) {
	duk_hobject *h;
	duk_bool_t is_frozen;
	duk_bool_t rc;

	h = duk_require_hobject_or_lfunc(ctx, 0);
	if (!h) {
		duk_push_true(ctx);  /* frozen and sealed */
	} else {
		is_frozen = duk_get_current_magic(ctx);
		rc = duk_hobject_object_is_sealed_frozen_helper((duk_hthread *) ctx, h, is_frozen /*is_frozen*/);
		duk_push_boolean(ctx, rc);
	}
	return 1;
}

DUK_INTERNAL duk_ret_t duk_bi_object_constructor_is_extensible(duk_context *ctx) {
	duk_hobject *h;

	h = duk_require_hobject_or_lfunc(ctx, 0);
	if (!h) {
		duk_push_false(ctx);
	} else {
		duk_push_boolean(ctx, DUK_HOBJECT_HAS_EXTENSIBLE(h));
	}
	return 1;
}

/* Shared helper for Object.getOwnPropertyNames() and Object.keys().
 * Magic: 0=getOwnPropertyNames, 1=Object.keys.
 */
DUK_INTERNAL duk_ret_t duk_bi_object_constructor_keys_shared(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *obj;
#if defined(DUK_USE_ES6_PROXY)
	duk_hobject *h_proxy_target;
	duk_hobject *h_proxy_handler;
	duk_hobject *h_trap_result;
	duk_uarridx_t i, len, idx;
#endif
	duk_small_uint_t enum_flags;

	DUK_ASSERT_TOP(ctx, 1);
	DUK_UNREF(thr);

	obj = duk_require_hobject_or_lfunc_coerce(ctx, 0);
	DUK_ASSERT(obj != NULL);
	DUK_UNREF(obj);

#if defined(DUK_USE_ES6_PROXY)
	if (DUK_LIKELY(!duk_hobject_proxy_check(thr,
	                                        obj,
	                                        &h_proxy_target,
	                                        &h_proxy_handler))) {
		goto skip_proxy;
	}

	duk_push_hobject(ctx, h_proxy_handler);
	if (!duk_get_prop_stridx(ctx, -1, DUK_STRIDX_OWN_KEYS)) {
		/* Careful with reachability here: don't pop 'obj' before pushing
		 * proxy target.
		 */
		DUK_DDD(DUK_DDDPRINT("no ownKeys trap, get keys of target instead"));
		duk_pop_2(ctx);
		duk_push_hobject(ctx, h_proxy_target);
		duk_replace(ctx, 0);
		DUK_ASSERT_TOP(ctx, 1);
		goto skip_proxy;
	}

	/* [ obj handler trap ] */
	duk_insert(ctx, -2);
	duk_push_hobject(ctx, h_proxy_target);  /* -> [ obj trap handler target ] */
	duk_call_method(ctx, 1 /*nargs*/);      /* -> [ obj trap_result ] */
	h_trap_result = duk_require_hobject(ctx, -1);
	DUK_UNREF(h_trap_result);

	len = (duk_uarridx_t) duk_get_length(ctx, -1);
	idx = 0;
	duk_push_array(ctx);
	for (i = 0; i < len; i++) {
		/* [ obj trap_result res_arr ] */
		if (duk_get_prop_index(ctx, -2, i) && duk_is_string(ctx, -1)) {
			/* XXX: for Object.keys() we should check enumerability of key */
			/* [ obj trap_result res_arr propname ] */
			duk_put_prop_index(ctx, -2, idx);
			idx++;
		} else {
			duk_pop(ctx);
		}
	}

	/* XXX: missing trap result validation for non-configurable target keys
	 * (must be present), for non-extensible target all target keys must be
	 * present and no extra keys can be present.
	 * http://www.ecma-international.org/ecma-262/6.0/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys
	 */

	/* XXX: for Object.keys() the [[OwnPropertyKeys]] result (trap result)
	 * should be filtered so that only enumerable keys remain.  Enumerability
	 * should be checked with [[GetOwnProperty]] on the original object
	 * (i.e., the proxy in this case).  If the proxy has a getOwnPropertyDescriptor
	 * trap, it should be triggered for every property.  If the proxy doesn't have
	 * the trap, enumerability should be checked against the target object instead.
	 * We don't do any of this now, so Object.keys() and Object.getOwnPropertyNames()
	 * return the same result now for proxy traps.  We still do clean up the trap
	 * result, so that Object.keys() and Object.getOwnPropertyNames() will return a
	 * clean array of strings without gaps.
	 */
	return 1;

 skip_proxy:
#endif  /* DUK_USE_ES6_PROXY */

	DUK_ASSERT_TOP(ctx, 1);

	if (duk_get_current_magic(ctx)) {
		/* Object.keys */
		enum_flags = DUK_ENUM_OWN_PROPERTIES_ONLY |
		             DUK_ENUM_NO_PROXY_BEHAVIOR;
	} else {
		/* Object.getOwnPropertyNames */
		enum_flags = DUK_ENUM_INCLUDE_NONENUMERABLE |
		             DUK_ENUM_OWN_PROPERTIES_ONLY |
		             DUK_ENUM_NO_PROXY_BEHAVIOR;
	}

	return duk_hobject_get_enumerated_keys(ctx, enum_flags);
}

DUK_INTERNAL duk_ret_t duk_bi_object_prototype_to_string(duk_context *ctx) {
	duk_push_this(ctx);
	duk_to_object_class_string_top(ctx);
	return 1;
}

DUK_INTERNAL duk_ret_t duk_bi_object_prototype_to_locale_string(duk_context *ctx) {
	DUK_ASSERT_TOP(ctx, 0);
	(void) duk_push_this_coercible_to_object(ctx);
	duk_get_prop_stridx(ctx, 0, DUK_STRIDX_TO_STRING);
	if (!duk_is_callable(ctx, 1)) {
		return DUK_RET_TYPE_ERROR;
	}
	duk_dup(ctx, 0);  /* -> [ O toString O ] */
	duk_call_method(ctx, 0);  /* XXX: call method tail call? */
	return 1;
}

DUK_INTERNAL duk_ret_t duk_bi_object_prototype_value_of(duk_context *ctx) {
	(void) duk_push_this_coercible_to_object(ctx);
	return 1;
}

DUK_INTERNAL duk_ret_t duk_bi_object_prototype_is_prototype_of(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *h_v;
	duk_hobject *h_obj;

	DUK_ASSERT_TOP(ctx, 1);

	h_v = duk_get_hobject(ctx, 0);
	if (!h_v) {
		duk_push_false(ctx);  /* XXX: tail call: return duk_push_false(ctx) */
		return 1;
	}

	h_obj = duk_push_this_coercible_to_object(ctx);
	DUK_ASSERT(h_obj != NULL);

	/* E5.1 Section 15.2.4.6, step 3.a, lookup proto once before compare.
	 * Prototype loops should cause an error to be thrown.
	 */
	duk_push_boolean(ctx, duk_hobject_prototype_chain_contains(thr, DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h_v), h_obj, 0 /*ignore_loop*/));
	return 1;
}

DUK_INTERNAL duk_ret_t duk_bi_object_prototype_has_own_property(duk_context *ctx) {
	return duk_hobject_object_ownprop_helper(ctx, 0 /*required_desc_flags*/);
}

DUK_INTERNAL duk_ret_t duk_bi_object_prototype_property_is_enumerable(duk_context *ctx) {
	return duk_hobject_object_ownprop_helper(ctx, DUK_PROPDESC_FLAG_ENUMERABLE /*required_desc_flags*/);
}
