/*
 *  Function built-ins
 */

#include "duk_internal.h"

/* Needed even when Function built-in is disabled. */
DUK_INTERNAL duk_ret_t duk_bi_function_prototype(duk_hthread *thr) {
	/* ignore arguments, return undefined (E5 Section 15.3.4) */
	DUK_UNREF(thr);
	return 0;
}

#if defined(DUK_USE_FUNCTION_BUILTIN)
DUK_INTERNAL duk_ret_t duk_bi_function_constructor(duk_hthread *thr) {
	duk_hstring *h_sourcecode;
	duk_idx_t nargs;
	duk_idx_t i;
	duk_small_uint_t comp_flags;
	duk_hcompfunc *func;
	duk_hobject *outer_lex_env;
	duk_hobject *outer_var_env;

	/* normal and constructor calls have identical semantics */

	nargs = duk_get_top(thr);
	for (i = 0; i < nargs; i++) {
		duk_to_string(thr, i);  /* Rejects Symbols during coercion. */
	}

	if (nargs == 0) {
		duk_push_hstring_empty(thr);
		duk_push_hstring_empty(thr);
	} else if (nargs == 1) {
		/* XXX: cover this with the generic >1 case? */
		duk_push_hstring_empty(thr);
	} else {
		duk_insert(thr, 0);   /* [ arg1 ... argN-1 body] -> [body arg1 ... argN-1] */
		duk_push_literal(thr, ",");
		duk_insert(thr, 1);
		duk_join(thr, nargs - 1);
	}

	/* [ body formals ], formals is comma separated list that needs to be parsed */

	DUK_ASSERT_TOP(thr, 2);

	/* XXX: this placeholder is not always correct, but use for now.
	 * It will fail in corner cases; see test-dev-func-cons-args.js.
	 */
	duk_push_literal(thr, "function(");
	duk_dup_1(thr);
	duk_push_literal(thr, "){");
	duk_dup_0(thr);
	duk_push_literal(thr, "\n}");  /* Newline is important to handle trailing // comment. */
	duk_concat(thr, 5);

	/* [ body formals source ] */

	DUK_ASSERT_TOP(thr, 3);

	/* strictness is not inherited, intentional */
	comp_flags = DUK_COMPILE_FUNCEXPR;

	duk_push_hstring_stridx(thr, DUK_STRIDX_COMPILE);  /* XXX: copy from caller? */  /* XXX: ignored now */
	h_sourcecode = duk_require_hstring(thr, -2);  /* no symbol check needed; -2 is concat'd code */
	duk_js_compile(thr,
	               (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_sourcecode),
	               (duk_size_t) DUK_HSTRING_GET_BYTELEN(h_sourcecode),
	               comp_flags);

	/* Force .name to 'anonymous' (ES2015). */
	duk_push_literal(thr, "anonymous");
	duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_C);

	func = (duk_hcompfunc *) duk_known_hobject(thr, -1);
	DUK_ASSERT(DUK_HOBJECT_IS_COMPFUNC((duk_hobject *) func));
	DUK_ASSERT(DUK_HOBJECT_HAS_CONSTRUCTABLE((duk_hobject *) func));

	/* [ body formals source template ] */

	/* only outer_lex_env matters, as functions always get a new
	 * variable declaration environment.
	 */

	outer_lex_env = thr->builtins[DUK_BIDX_GLOBAL_ENV];
	outer_var_env = thr->builtins[DUK_BIDX_GLOBAL_ENV];

	duk_js_push_closure(thr, func, outer_var_env, outer_lex_env, 1 /*add_auto_proto*/);

	/* [ body formals source template closure ] */

	return 1;
}
#endif  /* DUK_USE_FUNCTION_BUILTIN */

#if defined(DUK_USE_FUNCTION_BUILTIN)
DUK_INTERNAL duk_ret_t duk_bi_function_prototype_to_string(duk_hthread *thr) {
	duk_tval *tv;

	/*
	 *  E5 Section 15.3.4.2 places few requirements on the output of
	 *  this function: the result is implementation dependent, must
	 *  follow FunctionDeclaration syntax (in particular, must have a
	 *  name even for anonymous functions or functions with empty name).
	 *  The output does NOT need to compile into anything useful.
	 *
	 *  E6 Section 19.2.3.5 changes the requirements completely: the
	 *  result must either eval() to a functionally equivalent object
	 *  OR eval() to a SyntaxError.
	 *
	 *  We opt for the SyntaxError approach for now, with a syntax that
	 *  mimics V8's native function syntax:
	 *
	 *      'function cos() { [native code] }'
	 *
	 *  but extended with [ecmascript code], [bound code], and
	 *  [lightfunc code].
	 */

	duk_push_this(thr);
	tv = DUK_GET_TVAL_NEGIDX(thr, -1);
	DUK_ASSERT(tv != NULL);

	if (DUK_TVAL_IS_OBJECT(tv)) {
		duk_hobject *obj = DUK_TVAL_GET_OBJECT(tv);
		const char *func_name;

		/* Function name: missing/undefined is mapped to empty string,
		 * otherwise coerce to string.  No handling for invalid identifier
		 * characters or e.g. '{' in the function name.  This doesn't
		 * really matter as long as a SyntaxError results.  Technically
		 * if the name contained a suitable prefix followed by '//' it
		 * might cause the result to parse without error.
		 */
		duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_NAME);
		if (duk_is_undefined(thr, -1)) {
			func_name = "";
		} else {
			func_name = duk_to_string(thr, -1);
			DUK_ASSERT(func_name != NULL);
		}

		if (DUK_HOBJECT_IS_COMPFUNC(obj)) {
			duk_push_sprintf(thr, "function %s() { [ecmascript code] }", (const char *) func_name);
		} else if (DUK_HOBJECT_IS_NATFUNC(obj)) {
			duk_push_sprintf(thr, "function %s() { [native code] }", (const char *) func_name);
		} else if (DUK_HOBJECT_IS_BOUNDFUNC(obj)) {
			duk_push_sprintf(thr, "function %s() { [bound code] }", (const char *) func_name);
		} else {
			goto type_error;
		}
	} else if (DUK_TVAL_IS_LIGHTFUNC(tv)) {
		duk_push_lightfunc_tostring(thr, tv);
	} else {
		goto type_error;
	}

	return 1;

 type_error:
	DUK_DCERROR_TYPE_INVALID_ARGS(thr);
}
#endif

/* Always present because the native function pointer is needed in call
 * handling.
 */
DUK_INTERNAL duk_ret_t duk_bi_function_prototype_call(duk_hthread *thr) {
	/* .call() is dealt with in call handling by simulating its
	 * effects so this function is actually never called.
	 */
	DUK_UNREF(thr);
	return DUK_RET_TYPE_ERROR;
}

DUK_INTERNAL duk_ret_t duk_bi_function_prototype_apply(duk_hthread *thr) {
	/* Like .call(), never actually called. */
	DUK_UNREF(thr);
	return DUK_RET_TYPE_ERROR;
}

DUK_INTERNAL duk_ret_t duk_bi_reflect_apply(duk_hthread *thr) {
	/* Like .call(), never actually called. */
	DUK_UNREF(thr);
	return DUK_RET_TYPE_ERROR;
}

DUK_INTERNAL duk_ret_t duk_bi_reflect_construct(duk_hthread *thr) {
	/* Like .call(), never actually called. */
	DUK_UNREF(thr);
	return DUK_RET_TYPE_ERROR;
}

#if defined(DUK_USE_FUNCTION_BUILTIN)
/* Create a bound function which points to a target function which may
 * be bound or non-bound.  If the target is bound, the argument lists
 * and 'this' binding of the functions are merged and the resulting
 * function points directly to the non-bound target.
 */
DUK_INTERNAL duk_ret_t duk_bi_function_prototype_bind(duk_hthread *thr) {
	duk_hboundfunc *h_bound;
	duk_idx_t nargs;  /* bound args, not counting 'this' binding */
	duk_idx_t bound_nargs;
	duk_int_t bound_len;
	duk_tval *tv_prevbound;
	duk_idx_t n_prevbound;
	duk_tval *tv_res;
	duk_tval *tv_tmp;

	/* XXX: C API call, e.g. duk_push_bound_function(thr, target_idx, nargs); */

	/* Vararg function, careful arg handling, e.g. thisArg may not
	 * be present.
	 */
	nargs = duk_get_top(thr) - 1;  /* actual args, not counting 'this' binding */
	if (nargs < 0) {
		nargs++;
		duk_push_undefined(thr);
	}
	DUK_ASSERT(nargs >= 0);

	/* Limit 'nargs' for bound functions to guarantee arithmetic
	 * below will never wrap.
	 */
	if (nargs > (duk_idx_t) DUK_HBOUNDFUNC_MAX_ARGS) {
		DUK_DCERROR_RANGE_INVALID_COUNT(thr);
	}

	duk_push_this(thr);
	duk_require_callable(thr, -1);

	/* [ thisArg arg1 ... argN func ]  (thisArg+args == nargs+1 total) */
	DUK_ASSERT_TOP(thr, nargs + 2);

	/* Create bound function object. */
	h_bound = duk_push_hboundfunc(thr);
	DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&h_bound->target));
	DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&h_bound->this_binding));
	DUK_ASSERT(h_bound->args == NULL);
	DUK_ASSERT(h_bound->nargs == 0);
	DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, (duk_hobject *) h_bound) == NULL);

	/* [ thisArg arg1 ... argN func boundFunc ] */

	/* If the target is a bound function, argument lists must be
	 * merged.  The 'this' binding closest to the target function
	 * wins because in call handling the 'this' gets replaced over
	 * and over again until we call the non-bound function.
	 */
	tv_prevbound = NULL;
	n_prevbound = 0;
	tv_tmp = DUK_GET_TVAL_POSIDX(thr, 0);
	DUK_TVAL_SET_TVAL(&h_bound->this_binding, tv_tmp);
	tv_tmp = DUK_GET_TVAL_NEGIDX(thr, -2);
	DUK_TVAL_SET_TVAL(&h_bound->target, tv_tmp);

	if (DUK_TVAL_IS_OBJECT(tv_tmp)) {
		duk_hobject *h_target;
		duk_hobject *bound_proto;

		h_target = DUK_TVAL_GET_OBJECT(tv_tmp);
		DUK_ASSERT(DUK_HOBJECT_IS_CALLABLE(h_target));

		/* Internal prototype must be copied from the target.
		 * For lightfuncs Function.prototype is used and is already
		 * in place.
		 */
		bound_proto = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h_target);
		DUK_HOBJECT_SET_PROTOTYPE_INIT_INCREF(thr, (duk_hobject *) h_bound, bound_proto);

		/* The 'strict' flag is copied to get the special [[Get]] of E5.1
		 * Section 15.3.5.4 to apply when a 'caller' value is a strict bound
		 * function.  Not sure if this is correct, because the specification
		 * is a bit ambiguous on this point but it would make sense.
		 */
		/* Strictness is inherited from target. */
		if (DUK_HOBJECT_HAS_STRICT(h_target)) {
			DUK_HOBJECT_SET_STRICT((duk_hobject *) h_bound);
		}

		if (DUK_HOBJECT_HAS_BOUNDFUNC(h_target)) {
			duk_hboundfunc *h_boundtarget;

			h_boundtarget = (duk_hboundfunc *) (void *) h_target;

			/* The final function should always be non-bound, unless
			 * there's a bug in the internals.  Assert for it.
			 */
			DUK_ASSERT(DUK_TVAL_IS_LIGHTFUNC(&h_boundtarget->target) ||
			           (DUK_TVAL_IS_OBJECT(&h_boundtarget->target) &&
			            DUK_HOBJECT_IS_CALLABLE(DUK_TVAL_GET_OBJECT(&h_boundtarget->target)) &&
			            !DUK_HOBJECT_IS_BOUNDFUNC(DUK_TVAL_GET_OBJECT(&h_boundtarget->target))));

			DUK_TVAL_SET_TVAL(&h_bound->target, &h_boundtarget->target);
			DUK_TVAL_SET_TVAL(&h_bound->this_binding, &h_boundtarget->this_binding);

			tv_prevbound = h_boundtarget->args;
			n_prevbound = h_boundtarget->nargs;
		}
	} else {
		/* Lightfuncs are always strict. */
		duk_hobject *bound_proto;

		DUK_ASSERT(DUK_TVAL_IS_LIGHTFUNC(tv_tmp));
		DUK_HOBJECT_SET_STRICT((duk_hobject *) h_bound);
		bound_proto = thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE];
		DUK_HOBJECT_SET_PROTOTYPE_INIT_INCREF(thr, (duk_hobject *) h_bound, bound_proto);
	}

	DUK_TVAL_INCREF(thr, &h_bound->target);  /* old values undefined, no decref needed */
	DUK_TVAL_INCREF(thr, &h_bound->this_binding);

	bound_nargs = n_prevbound + nargs;
	if (bound_nargs > (duk_idx_t) DUK_HBOUNDFUNC_MAX_ARGS) {
		DUK_DCERROR_RANGE_INVALID_COUNT(thr);
	}
	tv_res = (duk_tval *) DUK_ALLOC_CHECKED(thr, ((duk_size_t) bound_nargs) * sizeof(duk_tval));
	DUK_ASSERT(tv_res != NULL || bound_nargs == 0);
	DUK_ASSERT(h_bound->args == NULL);
	DUK_ASSERT(h_bound->nargs == 0);
	h_bound->args = tv_res;
	h_bound->nargs = bound_nargs;

	DUK_ASSERT(n_prevbound >= 0);
	duk_copy_tvals_incref(thr, tv_res, tv_prevbound, (duk_size_t) n_prevbound);
	DUK_ASSERT(nargs >= 0);
	duk_copy_tvals_incref(thr, tv_res + n_prevbound, DUK_GET_TVAL_POSIDX(thr, 1), (duk_size_t) nargs);

	/* [ thisArg arg1 ... argN func boundFunc ] */

	/* Bound function 'length' property is interesting.
	 * For lightfuncs, simply read the virtual property.
	 */
	duk_get_prop_stridx_short(thr, -2, DUK_STRIDX_LENGTH);
	bound_len = duk_get_int(thr, -1);  /* ES2015: no coercion */
	if (bound_len < nargs) {
		bound_len = 0;
	} else {
		bound_len -= nargs;
	}
	if (sizeof(duk_int_t) > 4 && bound_len > (duk_int_t) DUK_UINT32_MAX) {
		bound_len = (duk_int_t) DUK_UINT32_MAX;
	}
	duk_pop(thr);
	DUK_ASSERT(bound_len >= 0);
	tv_tmp = thr->valstack_top++;
	DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(tv_tmp));
	DUK_ASSERT(!DUK_TVAL_NEEDS_REFCOUNT_UPDATE(tv_tmp));
	DUK_TVAL_SET_U32(tv_tmp, (duk_uint32_t) bound_len);  /* in-place update, fastint */
	duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_C);  /* attrs in E6 Section 9.2.4 */

	/* XXX: could these be virtual? */
	/* Caller and arguments must use the same thrower, [[ThrowTypeError]]. */
	duk_xdef_prop_stridx_thrower(thr, -1, DUK_STRIDX_CALLER);
	duk_xdef_prop_stridx_thrower(thr, -1, DUK_STRIDX_LC_ARGUMENTS);

	/* Function name and fileName (non-standard). */
	duk_push_literal(thr, "bound ");  /* ES2015 19.2.3.2. */
	duk_get_prop_stridx(thr, -3, DUK_STRIDX_NAME);
	if (!duk_is_string_notsymbol(thr, -1)) {
		/* ES2015 has requirement to check that .name of target is a string
		 * (also must check for Symbol); if not, targetName should be the
		 * empty string.  ES2015 19.2.3.2.
		 */
		duk_pop(thr);
		duk_push_hstring_empty(thr);
	}
	duk_concat(thr, 2);
	duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_C);
#if defined(DUK_USE_FUNC_FILENAME_PROPERTY)
	duk_get_prop_stridx_short(thr, -2, DUK_STRIDX_FILE_NAME);
	duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_C);
#endif

	DUK_DDD(DUK_DDDPRINT("created bound function: %!iT", (duk_tval *) duk_get_tval(thr, -1)));

	return 1;
}
#endif  /* DUK_USE_FUNCTION_BUILTIN */

/* %NativeFunctionPrototype% .length getter. */
DUK_INTERNAL duk_ret_t duk_bi_native_function_length(duk_hthread *thr) {
	duk_tval *tv;
	duk_hnatfunc *h;
	duk_int16_t func_nargs;

	tv = duk_get_borrowed_this_tval(thr);
	DUK_ASSERT(tv != NULL);

	if (DUK_TVAL_IS_OBJECT(tv)) {
		h = (duk_hnatfunc *) DUK_TVAL_GET_OBJECT(tv);
		DUK_ASSERT(h != NULL);
		if (!DUK_HOBJECT_IS_NATFUNC((duk_hobject *) h)) {
			goto fail_type;
		}
		func_nargs = h->nargs;
		duk_push_int(thr, func_nargs == DUK_HNATFUNC_NARGS_VARARGS ? 0 : func_nargs);
	} else if (DUK_TVAL_IS_LIGHTFUNC(tv)) {
		duk_small_uint_t lf_flags;
		duk_small_uint_t lf_len;

		lf_flags = DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv);
		lf_len = DUK_LFUNC_FLAGS_GET_LENGTH(lf_flags);
		duk_push_uint(thr, lf_len);
	} else {
		goto fail_type;
	}
	return 1;

 fail_type:
	DUK_DCERROR_TYPE_INVALID_ARGS(thr);
}

/* %NativeFunctionPrototype% .name getter. */
DUK_INTERNAL duk_ret_t duk_bi_native_function_name(duk_hthread *thr) {
	duk_tval *tv;
	duk_hnatfunc *h;

	tv = duk_get_borrowed_this_tval(thr);
	DUK_ASSERT(tv != NULL);

	if (DUK_TVAL_IS_OBJECT(tv)) {
		h = (duk_hnatfunc *) DUK_TVAL_GET_OBJECT(tv);
		DUK_ASSERT(h != NULL);
		if (!DUK_HOBJECT_IS_NATFUNC((duk_hobject *) h)) {
			goto fail_type;
		}
#if 0
		duk_push_hnatfunc_name(thr, h);
#endif
		duk_push_hstring_empty(thr);
	} else if (DUK_TVAL_IS_LIGHTFUNC(tv)) {
		duk_push_lightfunc_name(thr, tv);
	} else {
		goto fail_type;
	}
	return 1;

 fail_type:
	DUK_DCERROR_TYPE_INVALID_ARGS(thr);
}

#if defined(DUK_USE_SYMBOL_BUILTIN)
DUK_INTERNAL duk_ret_t duk_bi_function_prototype_hasinstance(duk_hthread *thr) {
	/* This binding: RHS, stack index 0: LHS. */
	duk_bool_t ret;

	ret = duk_js_instanceof_ordinary(thr, DUK_GET_TVAL_POSIDX(thr, 0), DUK_GET_THIS_TVAL_PTR(thr));
	duk_push_boolean(thr, ret);
	return 1;
}
#endif  /* DUK_USE_SYMBOL_BUILTIN */
