/*
 * %CopyrightBegin%
 *
 * Copyright Ericsson AB 2006-2021. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * %CopyrightEnd%
 */

/*
 * This file implements the former GC BIFs. They used to do a GC when
 * they needed heap space. Because of changes to the implementation of
 * literals, those BIFs are now allowed to allocate heap fragments
 * (using HeapFragOnlyAlloc()). Note that they must NOT call HAlloc(),
 * because the caller does not do any SWAPIN / SWAPOUT (that is,
 * HEAP_TOP(p) and HEAP_LIMIT(p) contain stale values).
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include "sys.h"
#include "erl_vm.h"
#include "global.h"
#include "erl_process.h"
#include "error.h"
#include "bif.h"
#include "big.h"
#include "erl_binary.h"
#include "erl_map.h"

static Eterm double_to_integer(Process* p, double x);
static BIF_RETTYPE erlang_length_trap(BIF_ALIST_3);
static Export erlang_length_export;

void erts_init_bif_guard(void)
{
    erts_init_trap_export(&erlang_length_export,
			  am_erlang, am_length, 3,
			  &erlang_length_trap);
}

BIF_RETTYPE abs_1(BIF_ALIST_1)
{
    Eterm res;
    Sint i0, i;
    Eterm* hp;

    /* integer arguments */
    if (is_small(BIF_ARG_1)) {
	i0 = signed_val(BIF_ARG_1);
	i = ERTS_SMALL_ABS(i0);
	if (i0 == MIN_SMALL) {
	    hp = HeapFragOnlyAlloc(BIF_P, BIG_UINT_HEAP_SIZE);
	    BIF_RET(uint_to_big(i, hp));
	} else {
	    BIF_RET(make_small(i));
	}
    } else if (is_big(BIF_ARG_1)) {
	if (!big_sign(BIF_ARG_1)) {
	    BIF_RET(BIF_ARG_1);
	} else {
	    int sz = big_arity(BIF_ARG_1) + 1;
	    Uint* x;

	    hp = HeapFragOnlyAlloc(BIF_P, sz);	/* See note at beginning of file */
	    sz--;
	    res = make_big(hp);
	    x = big_val(BIF_ARG_1);
	    *hp++ = make_pos_bignum_header(sz);
	    x++;                          /* skip thing */
	    while(sz--)
		*hp++ = *x++;
	    BIF_RET(res);
	}
    } else if (is_float(BIF_ARG_1)) {
	FloatDef f;

	GET_DOUBLE(BIF_ARG_1, f);
	if (f.fd < 0.0) {
	    hp = HeapFragOnlyAlloc(BIF_P, FLOAT_SIZE_OBJECT);
	    f.fd = fabs(f.fd);
	    res = make_float(hp);
	    PUT_DOUBLE(f, hp);
	    BIF_RET(res);
	}
	else
	    BIF_RET(BIF_ARG_1);
    }
    BIF_ERROR(BIF_P, BADARG);
}

BIF_RETTYPE float_1(BIF_ALIST_1)
{
    Eterm res;
    Eterm* hp;
    FloatDef f;
     
    /* check args */
    if (is_not_integer(BIF_ARG_1)) {
	if (is_float(BIF_ARG_1))  {
	    BIF_RET(BIF_ARG_1);
	} else {
	badarg:
	    BIF_ERROR(BIF_P, BADARG);
	}
    }
    if (is_small(BIF_ARG_1)) {
	Sint i = signed_val(BIF_ARG_1);
	f.fd = i;		/* use "C"'s auto casting */
    } else if (big_to_double(BIF_ARG_1, &f.fd) < 0) {
	goto badarg;
    }
    hp = HeapFragOnlyAlloc(BIF_P, FLOAT_SIZE_OBJECT);
    res = make_float(hp);
    PUT_DOUBLE(f, hp);
    BIF_RET(res);
}

BIF_RETTYPE trunc_1(BIF_ALIST_1)
{
    Eterm res;
    FloatDef f;
     
    /* check arg */
    if (is_not_float(BIF_ARG_1)) {
	if (is_integer(BIF_ARG_1)) 
	    BIF_RET(BIF_ARG_1);
	BIF_ERROR(BIF_P, BADARG);
    }
    /* get the float */
    GET_DOUBLE(BIF_ARG_1, f);

    /* truncate it and return the resultant integer */
    res = double_to_integer(BIF_P, (f.fd >= 0.0) ? floor(f.fd) : ceil(f.fd));
    BIF_RET(res);
}

BIF_RETTYPE floor_1(BIF_ALIST_1)
{
    Eterm res;
    FloatDef f;

    if (is_not_float(BIF_ARG_1)) {
	if (is_integer(BIF_ARG_1))
	    BIF_RET(BIF_ARG_1);
	BIF_ERROR(BIF_P, BADARG);
    }
    GET_DOUBLE(BIF_ARG_1, f);
    res = double_to_integer(BIF_P, floor(f.fd));
    BIF_RET(res);
}

BIF_RETTYPE ceil_1(BIF_ALIST_1)
{
    Eterm res;
    FloatDef f;

    /* check arg */
    if (is_not_float(BIF_ARG_1)) {
	if (is_integer(BIF_ARG_1))
	    BIF_RET(BIF_ARG_1);
	BIF_ERROR(BIF_P, BADARG);
    }
    /* get the float */
    GET_DOUBLE(BIF_ARG_1, f);

    res = double_to_integer(BIF_P, ceil(f.fd));
    BIF_RET(res);
}

BIF_RETTYPE round_1(BIF_ALIST_1)
{
    Eterm res;
    FloatDef f;
     
    /* check arg */ 
    if (is_not_float(BIF_ARG_1)) {
	if (is_integer(BIF_ARG_1)) 
	    BIF_RET(BIF_ARG_1);
	BIF_ERROR(BIF_P, BADARG);
    }
     
    /* get the float */
    GET_DOUBLE(BIF_ARG_1, f);

    /* round it and return the resultant integer */
    res = double_to_integer(BIF_P, round(f.fd));
    BIF_RET(res);
}

/*
 * This version of length/1 is called from native code and apply/3.
 */

BIF_RETTYPE length_1(BIF_ALIST_1)
{
    Eterm args[3];

    /*
     * Arrange argument registers the way expected by
     * erts_trapping_length_1(). We save the original argument in
     * args[2] in case an error should signaled.
     */

    args[0] = BIF_ARG_1;
    args[1] = make_small(0);
    args[2] = BIF_ARG_1;
    return erlang_length_trap(BIF_P, args, A__I);
}

static BIF_RETTYPE erlang_length_trap(BIF_ALIST_3)
{
    Eterm res;

    res = erts_trapping_length_1(BIF_P, BIF__ARGS);
    if (is_value(res)) {        /* Success. */
        BIF_RET(res);
    } else {                    /* Trap or error. */
        if (BIF_P->freason == TRAP) {
            /*
             * The available reductions were exceeded. Trap.
             */
            BIF_TRAP3(&erlang_length_export, BIF_P, BIF_ARG_1, BIF_ARG_2, BIF_ARG_3);
        } else {
            /*
             * Signal an error. The original argument was tucked away in BIF_ARG_3.
             */
            ERTS_BIF_ERROR_TRAPPED1(BIF_P, BIF_P->freason,
                                    BIF_TRAP_EXPORT(BIF_length_1), BIF_ARG_3);
        }
    }
}

/*
 * Trappable helper function for calculating length/1.
 *
 * When calling this function, entries in args[] should be set up as
 * follows:
 *
 *   args[0] = List to calculate length for.
 *   args[1] = Length accumulator (tagged integer).
 *
 * If the return value is a tagged integer, the length was calculated
 * successfully.
 *
 * Otherwise, if return value is THE_NON_VALUE and p->freason is TRAP,
 * the available reductions were exceeded and this function must be called
 * again after rescheduling. args[0] and args[1] have been updated to
 * contain the next part of the list and length so far, respectively.
 *
 * Otherwise, if return value is THE_NON_VALUE, the list did not end
 * in an empty list (and p->freason is BADARG).
 */

Eterm erts_trapping_length_1(Process* p, Eterm* args)
{
    Eterm list;
    Uint i;
    Uint max_iter;
    Uint saved_max_iter;

#if defined(DEBUG) || defined(VALGRIND)
    max_iter = 50;
#else
    max_iter = ERTS_BIF_REDS_LEFT(p) * 16;
#endif
    saved_max_iter = max_iter;
    ASSERT(max_iter > 0);

    list = args[0];
    i = unsigned_val(args[1]);
    while (is_list(list) && max_iter != 0) {
	list = CDR(list_val(list));
	i++, max_iter--;
    }

    if (is_list(list)) {
        /*
         * We have exceeded the allotted number of iterations.
         * Save the result so far and signal a trap.
         */
        args[0] = list;
        args[1] = make_small(i);
        p->freason = TRAP;
        BUMP_ALL_REDS(p);
        return THE_NON_VALUE;
    } else if (is_not_nil(list))  {
        /* Error. Should be NIL. */
	BIF_ERROR(p, BADARG);
    }

    /*
     * We reached the end of the list successfully. Bump reductions
     * and return result.
     */
    BUMP_REDS(p, (saved_max_iter - max_iter) / 16);
    return make_small(i);
}

/* returns the size of a tuple or a binary */

BIF_RETTYPE size_1(BIF_ALIST_1)
{
    if (is_tuple(BIF_ARG_1)) {
	Eterm* tupleptr = tuple_val(BIF_ARG_1);

	BIF_RET(make_small(arityval(*tupleptr)));
    } else if (is_binary(BIF_ARG_1)) {
	Uint sz = binary_size(BIF_ARG_1);
	if (IS_USMALL(0, sz)) {
	    return make_small(sz);
	} else {
	    Eterm* hp = HeapFragOnlyAlloc(BIF_P, BIG_UINT_HEAP_SIZE);
	    BIF_RET(uint_to_big(sz, hp));
	}
    }
    BIF_ERROR(BIF_P, BADARG);
}

/**********************************************************************/
/* returns the bitsize of a bitstring */

BIF_RETTYPE bit_size_1(BIF_ALIST_1)
{
    Uint low_bits;
    Uint bytesize;
    Uint high_bits;
    if (is_binary(BIF_ARG_1)) {
	bytesize = binary_size(BIF_ARG_1);
	high_bits = bytesize >>  ((sizeof(Uint) * 8)-3);
	low_bits = (bytesize << 3) + binary_bitsize(BIF_ARG_1);
	if (high_bits == 0) {
	    if (IS_USMALL(0,low_bits)) {
		BIF_RET(make_small(low_bits));
	    } else {
		Eterm* hp = HeapFragOnlyAlloc(BIF_P, BIG_UINT_HEAP_SIZE);
		BIF_RET(uint_to_big(low_bits, hp));
	    }
	} else {
	    Uint sz = BIG_UINT_HEAP_SIZE+1;
	    Eterm* hp = HeapFragOnlyAlloc(BIF_P, sz);
	    hp[0] = make_pos_bignum_header(sz-1);
	    BIG_DIGIT(hp,0) = low_bits;
	    BIG_DIGIT(hp,1) = high_bits;
	    BIF_RET(make_big(hp));
	}
    } else {
	BIF_ERROR(BIF_P, BADARG);
    }
}

/**********************************************************************/
/* returns the number of bytes need to store a bitstring */

BIF_RETTYPE byte_size_1(BIF_ALIST_1)
{
    if (is_binary(BIF_ARG_1)) {
	Uint bytesize = binary_size(BIF_ARG_1);
	if (binary_bitsize(BIF_ARG_1) > 0) {
	    bytesize++;
	}
	if (IS_USMALL(0, bytesize)) {
	    BIF_RET(make_small(bytesize));
	} else {
	    Eterm* hp = HeapFragOnlyAlloc(BIF_P, BIG_UINT_HEAP_SIZE);
	    BIF_RET(uint_to_big(bytesize, hp));
	}
    } else {
	BIF_ERROR(BIF_P, BADARG);
    }
}

/*
 * Generate the integer part from a double.
 */
static Eterm
double_to_integer(Process* p, double x)
{
    int is_negative;
    int ds;
    ErtsDigit* xp;
    int i;
    Eterm res;
    size_t sz;
    Eterm* hp;
    double dbase;

    if ((x < (double) (MAX_SMALL+1)) && (x > (double) (MIN_SMALL-1))) {
	Sint xi = x;
	return make_small(xi);
    }

    if (x >= 0) {
	is_negative = 0;
    } else {
	is_negative = 1;
	x = -x;
    }

    /* Unscale & (calculate exponent) */
    ds = 0;
    dbase = ((double)(D_MASK)+1);
    while(x >= 1.0) {
	x /= dbase;         /* "shift" right */
	ds++;
    }
    sz = BIG_NEED_SIZE(ds);          /* number of words including arity */

    hp = HeapFragOnlyAlloc(p, sz);
    res = make_big(hp);
    xp = (ErtsDigit*) (hp + 1);

    for (i = ds-1; i >= 0; i--) {
	ErtsDigit d;

	x *= dbase;      /* "shift" left */
	d = x;            /* trunc */
	xp[i] = d;        /* store digit */
	x -= d;           /* remove integer part */
    }

    if (is_negative) {
	*hp = make_neg_bignum_header(sz-1);
    } else {
	*hp = make_pos_bignum_header(sz-1);
    }
    return res;
}

/********************************************************************************
 * binary_part guards. The actual implementation is in erl_bif_binary.c
 ********************************************************************************/
BIF_RETTYPE binary_part_3(BIF_ALIST_3)
{
    return erts_binary_part(BIF_P,BIF_ARG_1,BIF_ARG_2, BIF_ARG_3);
}

BIF_RETTYPE binary_part_2(BIF_ALIST_2)
{
    Eterm *tp;
    if (is_not_tuple(BIF_ARG_2)) {
	goto badarg;
    }
    tp = tuple_val(BIF_ARG_2);
    if (arityval(*tp) != 2) {
	goto badarg;
    }
    return erts_binary_part(BIF_P,BIF_ARG_1,tp[1], tp[2]);
 badarg:
   BIF_ERROR(BIF_P,BADARG);
}
