/*
 * SoftFP Library
 * 
 * Copyright (c) 2016 Fabrice Bellard
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#if ICVT_SIZE == 32
#define ICVT_UINT uint32_t
#define ICVT_INT int32_t
#elif ICVT_SIZE == 64
#define ICVT_UINT uint64_t
#define ICVT_INT int64_t
#elif ICVT_SIZE == 128
#define ICVT_UINT uint128_t
#define ICVT_INT int128_t
#else
#error unsupported icvt
#endif

/* conversions between float and integers */
static ICVT_INT glue(glue(glue(internal_cvt_sf, F_SIZE), _i), ICVT_SIZE)(F_UINT a, RoundingModeEnum rm,
                                                                         uint32_t *pfflags, BOOL is_unsigned)
{
    uint32_t a_sign, addend, rnd_bits;
    int32_t a_exp;
    F_UINT a_mant;
    ICVT_UINT r, r_max;

    a_sign = a >> (F_SIZE - 1);
    a_exp = (a >> MANT_SIZE) & EXP_MASK;
    a_mant = a & MANT_MASK;
    if (a_exp == EXP_MASK && a_mant != 0)
        a_sign = 0; /* NaN is like +infinity */
    if (a_exp == 0) {
        a_exp = 1;
    } else {
        a_mant |= (F_UINT)1 << MANT_SIZE;
    }
    a_mant <<= RND_SIZE;
    a_exp = a_exp - (EXP_MASK / 2) - MANT_SIZE;

    if (is_unsigned)
        r_max = (ICVT_UINT)a_sign - 1;
    else
        r_max = ((ICVT_UINT)1 << (ICVT_SIZE - 1)) - (ICVT_UINT)(a_sign ^ 1);
    if (a_exp >= 0) {
        if (a_exp <= (ICVT_SIZE - 1 - MANT_SIZE)) {
            r = (ICVT_UINT)(a_mant >> RND_SIZE) << a_exp;
            if (r > r_max)
                goto overflow;
        } else {
        overflow:
            *pfflags |= FFLAG_INVALID_OP;
            return r_max;
        }
    } else {
        a_mant = rshift_rnd(a_mant, -a_exp);

        switch(rm) {
        case RM_RNE:
        case RM_RMM:
            addend = (1 << (RND_SIZE - 1));
            break;
        case RM_RTZ:
            addend = 0;
            break;
        default:
        case RM_RDN:
        case RM_RUP:
            if (a_sign ^ (rm & 1))
                addend = (1 << RND_SIZE) - 1;
            else
                addend = 0;
            break;
        }
        
        rnd_bits = a_mant & ((1 << RND_SIZE ) - 1);
        a_mant = (a_mant + addend) >> RND_SIZE;
        /* half way: select even result */
        if (rm == RM_RNE && rnd_bits == (1 << (RND_SIZE - 1)))
            a_mant &= ~1;
        if (a_mant > r_max)
            goto overflow;
        r = a_mant;
        if (rnd_bits != 0)
            *pfflags |= FFLAG_INEXACT;
    }
    if (a_sign)
        r = -r;
    return r;
}

ICVT_INT glue(glue(glue(cvt_sf, F_SIZE), _i), ICVT_SIZE)(F_UINT a, RoundingModeEnum rm,
                                                          uint32_t *pfflags)
{
    return glue(glue(glue(internal_cvt_sf, F_SIZE), _i), ICVT_SIZE)(a, rm, 
                                                                    pfflags, FALSE);
}

ICVT_UINT glue(glue(glue(cvt_sf, F_SIZE), _u), ICVT_SIZE)(F_UINT a, RoundingModeEnum rm,
                                                          uint32_t *pfflags)
{
    return glue(glue(glue(internal_cvt_sf, F_SIZE), _i), ICVT_SIZE) (a, rm, 
                                                                     pfflags, TRUE);
}

/* conversions between float and integers */
static F_UINT glue(glue(glue(internal_cvt_i, ICVT_SIZE), _sf), F_SIZE)(ICVT_INT a, 
                                                                       RoundingModeEnum rm,
                                                                       uint32_t *pfflags,
                                                                       BOOL is_unsigned)
{
    uint32_t a_sign;
    int32_t a_exp;
    F_UINT a_mant;
    ICVT_UINT r, mask;
    int l;

    if (!is_unsigned && a < 0) {
        a_sign = 1;
        r = -(ICVT_UINT)a;
    } else {
        a_sign = 0;
        r = a;
    }
    a_exp = (EXP_MASK / 2) + F_SIZE - 2;
    /* need to reduce range before generic float normalization */
    l = ICVT_SIZE - glue(clz, ICVT_SIZE)(r) - (F_SIZE - 1);
    if (l > 0) {
        mask = r & (((ICVT_UINT)1 << l) - 1);
        r = (r >> l) | ((r & mask) != 0);
        a_exp += l;
    }
    a_mant = r;
    return normalize_sf(a_sign, a_exp, a_mant, rm, pfflags);
}

F_UINT glue(glue(glue(cvt_i, ICVT_SIZE), _sf), F_SIZE)(ICVT_INT a, 
                                                       RoundingModeEnum rm,
                                                       uint32_t *pfflags)
{
    return glue(glue(glue(internal_cvt_i, ICVT_SIZE), _sf), F_SIZE)(a, rm, pfflags, FALSE);
}

F_UINT glue(glue(glue(cvt_u, ICVT_SIZE), _sf), F_SIZE)(ICVT_UINT a, 
                                                       RoundingModeEnum rm,
                                                       uint32_t *pfflags)
{
    return glue(glue(glue(internal_cvt_i, ICVT_SIZE), _sf), F_SIZE)(a, rm, pfflags, TRUE);
}

#undef ICVT_SIZE
#undef ICVT_INT
#undef ICVT_UINT
