/*
 *  Number built-ins
 */

#include "duk_internal.h"

#if defined(DUK_USE_NUMBER_BUILTIN)

DUK_LOCAL duk_double_t duk__push_this_number_plain(duk_hthread *thr) {
	duk_hobject *h;

	/* Number built-in accepts a plain number or a Number object (whose
	 * internal value is operated on).  Other types cause TypeError.
	 */

	duk_push_this(thr);
	if (duk_is_number(thr, -1)) {
		DUK_DDD(DUK_DDDPRINT("plain number value: %!T", (duk_tval *) duk_get_tval(thr, -1)));
		goto done;
	}
	h = duk_get_hobject(thr, -1);
	if (!h ||
	    (DUK_HOBJECT_GET_CLASS_NUMBER(h) != DUK_HOBJECT_CLASS_NUMBER)) {
		DUK_DDD(DUK_DDDPRINT("unacceptable this value: %!T", (duk_tval *) duk_get_tval(thr, -1)));
		DUK_ERROR_TYPE(thr, "number expected");
		DUK_WO_NORETURN(return 0.0;);
	}
	duk_xget_owndataprop_stridx_short(thr, -1, DUK_STRIDX_INT_VALUE);
	DUK_ASSERT(duk_is_number(thr, -1));
	DUK_DDD(DUK_DDDPRINT("number object: %!T, internal value: %!T",
	                     (duk_tval *) duk_get_tval(thr, -2), (duk_tval *) duk_get_tval(thr, -1)));
	duk_remove_m2(thr);

 done:
	return duk_get_number(thr, -1);
}

DUK_INTERNAL duk_ret_t duk_bi_number_constructor(duk_hthread *thr) {
	duk_idx_t nargs;
	duk_hobject *h_this;

	/*
	 *  The Number constructor uses ToNumber(arg) for number coercion
	 *  (coercing an undefined argument to NaN).  However, if the
	 *  argument is not given at all, +0 must be used instead.  To do
	 *  this, a vararg function is used.
	 */

	nargs = duk_get_top(thr);
	if (nargs == 0) {
		duk_push_int(thr, 0);
	}
	duk_to_number(thr, 0);
	duk_set_top(thr, 1);
	DUK_ASSERT_TOP(thr, 1);

	if (!duk_is_constructor_call(thr)) {
		return 1;
	}

	/*
	 *  E5 Section 15.7.2.1 requires that the constructed object
	 *  must have the original Number.prototype as its internal
	 *  prototype.  However, since Number.prototype is non-writable
	 *  and non-configurable, this doesn't have to be enforced here:
	 *  The default object (bound to 'this') is OK, though we have
	 *  to change its class.
	 *
	 *  Internal value set to ToNumber(arg) or +0; if no arg given,
	 *  ToNumber(undefined) = NaN, so special treatment is needed
	 *  (above).  String internal value is immutable.
	 */

	/* XXX: helper */
	duk_push_this(thr);
	h_this = duk_known_hobject(thr, -1);
	DUK_HOBJECT_SET_CLASS_NUMBER(h_this, DUK_HOBJECT_CLASS_NUMBER);

	DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h_this) == thr->builtins[DUK_BIDX_NUMBER_PROTOTYPE]);
	DUK_ASSERT(DUK_HOBJECT_GET_CLASS_NUMBER(h_this) == DUK_HOBJECT_CLASS_NUMBER);
	DUK_ASSERT(DUK_HOBJECT_HAS_EXTENSIBLE(h_this));

	duk_dup_0(thr);  /* -> [ val obj val ] */
	duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_NONE);
	return 0;  /* no return value -> don't replace created value */
}

DUK_INTERNAL duk_ret_t duk_bi_number_prototype_value_of(duk_hthread *thr) {
	(void) duk__push_this_number_plain(thr);
	return 1;
}

DUK_INTERNAL duk_ret_t duk_bi_number_prototype_to_string(duk_hthread *thr) {
	duk_small_int_t radix;
	duk_small_uint_t n2s_flags;

	(void) duk__push_this_number_plain(thr);
	if (duk_is_undefined(thr, 0)) {
		radix = 10;
	} else {
		radix = (duk_small_int_t) duk_to_int_check_range(thr, 0, 2, 36);
	}
	DUK_DDD(DUK_DDDPRINT("radix=%ld", (long) radix));

	n2s_flags = 0;

	duk_numconv_stringify(thr,
	                      radix /*radix*/,
	                      0 /*digits*/,
	                      n2s_flags /*flags*/);
	return 1;
}

DUK_INTERNAL duk_ret_t duk_bi_number_prototype_to_locale_string(duk_hthread *thr) {
	/* XXX: just use toString() for now; permitted although not recommended.
	 * nargs==1, so radix is passed to toString().
	 */
	return duk_bi_number_prototype_to_string(thr);
}

/*
 *  toFixed(), toExponential(), toPrecision()
 */

/* XXX: shared helper for toFixed(), toExponential(), toPrecision()? */

DUK_INTERNAL duk_ret_t duk_bi_number_prototype_to_fixed(duk_hthread *thr) {
	duk_small_int_t frac_digits;
	duk_double_t d;
	duk_small_int_t c;
	duk_small_uint_t n2s_flags;

	/* In ES5.1 frac_digits is coerced first; in ES2015 the 'this number
	 * value' check is done first.
	 */
	d = duk__push_this_number_plain(thr);
	frac_digits = (duk_small_int_t) duk_to_int_check_range(thr, 0, 0, 20);

	c = (duk_small_int_t) DUK_FPCLASSIFY(d);
	if (c == DUK_FP_NAN || c == DUK_FP_INFINITE) {
		goto use_to_string;
	}

	if (d >= 1.0e21 || d <= -1.0e21) {
		goto use_to_string;
	}

	n2s_flags = DUK_N2S_FLAG_FIXED_FORMAT |
	            DUK_N2S_FLAG_FRACTION_DIGITS;

	duk_numconv_stringify(thr,
	                      10 /*radix*/,
	                      frac_digits /*digits*/,
	                      n2s_flags /*flags*/);
	return 1;

 use_to_string:
	DUK_ASSERT_TOP(thr, 2);
	duk_to_string(thr, -1);
	return 1;
}

DUK_INTERNAL duk_ret_t duk_bi_number_prototype_to_exponential(duk_hthread *thr) {
	duk_bool_t frac_undefined;
	duk_small_int_t frac_digits;
	duk_double_t d;
	duk_small_int_t c;
	duk_small_uint_t n2s_flags;

	d = duk__push_this_number_plain(thr);

	frac_undefined = duk_is_undefined(thr, 0);
	duk_to_int(thr, 0);  /* for side effects */

	c = (duk_small_int_t) DUK_FPCLASSIFY(d);
	if (c == DUK_FP_NAN || c == DUK_FP_INFINITE) {
		goto use_to_string;
	}

	frac_digits = (duk_small_int_t) duk_to_int_check_range(thr, 0, 0, 20);

	n2s_flags = DUK_N2S_FLAG_FORCE_EXP |
	           (frac_undefined ? 0 : DUK_N2S_FLAG_FIXED_FORMAT);

	duk_numconv_stringify(thr,
	                      10 /*radix*/,
	                      frac_digits + 1 /*leading digit + fractions*/,
	                      n2s_flags /*flags*/);
	return 1;

 use_to_string:
	DUK_ASSERT_TOP(thr, 2);
	duk_to_string(thr, -1);
	return 1;
}

DUK_INTERNAL duk_ret_t duk_bi_number_prototype_to_precision(duk_hthread *thr) {
	/* The specification has quite awkward order of coercion and
	 * checks for toPrecision().  The operations below are a bit
	 * reordered, within constraints of observable side effects.
	 */

	duk_double_t d;
	duk_small_int_t prec;
	duk_small_int_t c;
	duk_small_uint_t n2s_flags;

	DUK_ASSERT_TOP(thr, 1);

	d = duk__push_this_number_plain(thr);
	if (duk_is_undefined(thr, 0)) {
		goto use_to_string;
	}
	DUK_ASSERT_TOP(thr, 2);

	duk_to_int(thr, 0);  /* for side effects */

	c = (duk_small_int_t) DUK_FPCLASSIFY(d);
	if (c == DUK_FP_NAN || c == DUK_FP_INFINITE) {
		goto use_to_string;
	}

	prec = (duk_small_int_t) duk_to_int_check_range(thr, 0, 1, 21);

	n2s_flags = DUK_N2S_FLAG_FIXED_FORMAT |
	            DUK_N2S_FLAG_NO_ZERO_PAD;

	duk_numconv_stringify(thr,
	                      10 /*radix*/,
	                      prec /*digits*/,
	                      n2s_flags /*flags*/);
	return 1;

 use_to_string:
	/* Used when precision is undefined; also used for NaN (-> "NaN"),
	 * and +/- infinity (-> "Infinity", "-Infinity").
	 */

	DUK_ASSERT_TOP(thr, 2);
	duk_to_string(thr, -1);
	return 1;
}

/*
 *  ES2015 isFinite() etc
 */

#if defined(DUK_USE_ES6)
DUK_INTERNAL duk_ret_t duk_bi_number_check_shared(duk_hthread *thr) {
	duk_int_t magic;
	duk_bool_t ret = 0;

	if (duk_is_number(thr, 0)) {
		duk_double_t d;

		magic = duk_get_current_magic(thr);
		d = duk_get_number(thr, 0);

		switch (magic) {
		case 0:  /* isFinite() */
			ret = duk_double_is_finite(d);
			break;
		case 1:  /* isInteger() */
			ret = duk_double_is_integer(d);
			break;
		case 2:  /* isNaN() */
			ret = duk_double_is_nan(d);
			break;
		default:  /* isSafeInteger() */
			DUK_ASSERT(magic == 3);
			ret = duk_double_is_safe_integer(d);
		}
	}

	duk_push_boolean(thr, ret);
	return 1;
}
#endif  /* DUK_USE_ES6 */

#endif  /* DUK_USE_NUMBER_BUILTIN */
