/* Copyright 2020 Alexander Bolz
 * 
 * Boost Software License - Version 1.0 - August 17th, 2003
 * 
 * Permission is hereby granted, free of charge, to any person or organization
 * obtaining a copy of the software and accompanying documentation covered by
 * this license (the "Software") to use, reproduce, display, distribute,
 * execute, and transmit the Software, and to prepare derivative works of the
 * Software, and to permit third-parties to whom the Software is furnished to
 * do so, all subject to the following:
 * 
 * The copyright notices in the Software and this entire statement, including
 * the above license grant, this restriction and the following disclaimer,
 * must be included in all copies of the Software, in whole or in part, and
 * all derivative works of the Software, unless such copies or derivative
 * works are solely in the form of machine-executable object code generated by
 * a source language processor.
 *
 * Unless required by applicable law or agreed to in writing, this software
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.
 * 
 * This file may have been modified by ByteDance authors. All ByteDance
 * Modifications are Copyright 2022 ByteDance Authors.
 */

#include "native.h"
#include "tab.h"
#include "test/xassert.h"

#define F64_BITS         64
#define F64_EXP_BITS     11
#define F64_SIG_BITS     52
#define F64_EXP_MASK     0x7FF0000000000000ull // middle 11 bits
#define F64_SIG_MASK     0x000FFFFFFFFFFFFFull // lower 52 bits
#define F64_EXP_BIAS     1023
#define F64_INF_NAN_EXP  0x7FF
#define F64_HIDDEN_BIT   0x0010000000000000ull

struct f64_dec {
    uint64_t sig;
    int64_t exp;
};
typedef struct f64_dec f64_dec;

typedef __uint128_t uint128_t;

static always_inline unsigned ctz10(const uint64_t v) {
    xassert(0 <= v && v < 100000000000000000ull);
    if (v >= 10000000000ull) {
        if (v <      100000000000ull) return 11;
        if (v <     1000000000000ull) return 12;
        if (v <    10000000000000ull) return 13;
        if (v <   100000000000000ull) return 14;
        if (v <  1000000000000000ull) return 15;
        if (v < 10000000000000000ull) return 16;
        else                          return 17;
    }
        if (v <                10ull) return 1;
        if (v <               100ull) return 2;
        if (v <              1000ull) return 3;
        if (v <             10000ull) return 4;
        if (v <            100000ull) return 5;
        if (v <           1000000ull) return 6;
        if (v <          10000000ull) return 7;
        if (v <         100000000ull) return 8;
        if (v <        1000000000ull) return 9;
        else                          return 10;

}

static always_inline char* format_significand(uint64_t sig, char *out, int cnt) {
    char *p = out + cnt;
    int ctz = 0;

    if ((sig >> 32) != 0) {
        uint64_t q = sig / 100000000;
        uint32_t r = ((uint32_t)sig) - 100000000 * ((uint32_t) q);
        sig = q;
        if (r != 0) {
            uint32_t c  = r % 10000;
            r /= 10000;
            uint32_t d  = r % 10000;
            uint32_t c0 = (c % 100) << 1;
            uint32_t c1 = (c / 100) << 1;
            uint32_t d0 = (d % 100) << 1;
            uint32_t d1 = (d / 100) << 1;
            copy_two_digs(p - 2, Digits + c0);
            copy_two_digs(p - 4, Digits + c1);
            copy_two_digs(p - 6, Digits + d0);
            copy_two_digs(p - 8, Digits + d1);
        } else {
            ctz += 8;
        }
        p -= 8;
    }

    uint32_t sig2 = (uint32_t)sig;
    while (sig2 >= 10000) {
        uint32_t c = sig2 - 10000 * (sig2 / 10000);
        sig2 /= 10000;
        uint32_t c0 = (c % 100) << 1;
        uint32_t c1 = (c / 100) << 1;
        copy_two_digs(p - 2, Digits + c0);
        copy_two_digs(p - 4, Digits + c1);
        p -= 4;
    }
    if (sig2 >= 100) {
        uint32_t c = (sig2 % 100) << 1;
        sig2 /= 100;
        copy_two_digs(p - 2, Digits + c);
        p -= 2;
    }
    if (sig2 >= 10) {
        uint32_t c = sig2 << 1;
        copy_two_digs(p - 2, Digits + c);
    } else {
        *out = (char) ('0' + sig2);
    }
    return out + cnt - ctz;
}

static always_inline char* format_integer(uint64_t sig, char *out, unsigned cnt) {
    char *p = out + cnt;
    if ((sig >> 32) != 0) {
        uint64_t q = sig / 100000000;
        uint32_t r = ((uint32_t)sig) - 100000000 * ((uint32_t) q);
        sig = q;
        uint32_t c  = r % 10000;
        r /= 10000;
        uint32_t d  = r % 10000;
        uint32_t c0 = (c % 100) << 1;
        uint32_t c1 = (c / 100) << 1;
        uint32_t d0 = (d % 100) << 1;
        uint32_t d1 = (d / 100) << 1;
        copy_two_digs(p - 2, Digits + c0);
        copy_two_digs(p - 4, Digits + c1);
        copy_two_digs(p - 6, Digits + d0);
        copy_two_digs(p - 8, Digits + d1);
        p -= 8;
    }

    uint32_t sig2 = (uint32_t)sig;
    while (sig2 >= 10000) {
        uint32_t c = sig2 - 10000 * (sig2 / 10000);
        sig2 /= 10000;
        uint32_t c0 = (c % 100) << 1;
        uint32_t c1 = (c / 100) << 1;
        copy_two_digs(p - 2, Digits + c0);
        copy_two_digs(p - 4, Digits + c1);
        p -= 4;
    }
    if (sig2 >= 100) {
        uint32_t c = (sig2 % 100) << 1;
        sig2 /= 100;
        copy_two_digs(p - 2, Digits + c);
        p -= 2;
    }
    if (sig2 >= 10) {
        uint32_t c = sig2 << 1;
        copy_two_digs(p - 2, Digits + c);
    } else {
        *out = (char) ('0' + sig2);
    }
    return out + cnt;
}

static always_inline char* format_exponent(f64_dec v, char *out, unsigned cnt) {
    char* p = out + 1;
    char* end = format_significand(v.sig, p, cnt);
    while (*(end - 1) == '0') end--;

    /* print decimal point if needed */
    *out = *p;
    if (end - p > 1) {
        *p = '.';
    } else {
        end--;
    }

    /* print the exponent */
    *end++ = 'e';
    int32_t exp = v.exp + (int32_t) cnt - 1;
    if (exp < 0) {
        *end++ = '-';
        exp = -exp;
    } else {
        *end++ = '+';
    }

    if (exp >= 100) {
        int32_t c = exp % 10;
        copy_two_digs(end, Digits + 2 * (exp / 10));
        end[2] = (char) ('0' + c);
        end += 3;
    } else if (exp >= 10) {
        copy_two_digs(end, Digits + 2 * exp);
        end += 2;
    } else {
        *end++ = (char) ('0' + exp);
    }
    return end;
}

static always_inline char* format_decimal(f64_dec v, char* out, unsigned cnt) {
    char* p = out;
    char* end;
    int point = cnt + v.exp;

    /* print leading zeros if fp < 1 */
    if (point <= 0) {
        *p++ = '0', *p++ = '.';
        for (int i = 0; i < -point; i++) {
            *p++ = '0';
        }
    }

    /* add the remaining digits */
    end = format_significand(v.sig, p, cnt);
    while (*(end - 1) == '0') end--;
    if (point <= 0) {
        return end;
    }

    /* insert point or add trailing zeros */
    int digs = end - p;
    if (digs > point) {
        for (int i = 0; i < digs - point; i++) {
            *(end - i) = *(end - i - 1);
        }
        p[point] = '.';
        end++;
    } else {
        for (int i = 0; i < point - digs; i++) {
            *end++ = '0';
        }
    }
    return end;
}

static always_inline char* write_dec(f64_dec dec, char* p) {
    int cnt = ctz10(dec.sig);
    int dot = cnt + dec.exp;
    int sci_exp = dot - 1;
    bool exp_fmt = sci_exp < -6 || sci_exp > 20;
    bool has_dot = dot < cnt;

    if (exp_fmt) {
        return format_exponent(dec, p, cnt);
    }
    if (has_dot) {
        return format_decimal(dec, p, cnt);
    }

    char* end = p + dot;
    p = format_integer(dec.sig, p, cnt);
    while (p < end) *p++ = '0';
    return end;
}

static always_inline uint64_t f64toraw(double fp) {
    union {
        uint64_t u64;
        double   f64;
    } uval;
    uval.f64 = fp;
    return uval.u64;
}

static always_inline uint64_t round_odd(uint64x2 g, uint64_t cp) {
    const uint128_t x = ((uint128_t)cp) * g.lo;
    const uint128_t y = ((uint128_t)cp) * g.hi + ((uint64_t)(x >> 64));

    const uint64_t y0 = ((uint64_t)y);
    const uint64_t y1 = ((uint64_t)(y >> 64));
    return y1 | (y0 > 1);
}

/**
 Rendering float point number into decimal.
 The function used Schubfach algorithm, reference:
 The Schubfach way to render doubles, Raffaello Giulietti, 2022-03-20.
 https://drive.google.com/file/d/1gp5xv4CAa78SVgCeWfGqqI4FfYYYuNFb
 https://mail.openjdk.java.net/pipermail/core-libs-dev/2021-November/083536.html
 https://github.com/openjdk/jdk/pull/3402 (Java implementation)
 https://github.com/abolz/Drachennest (C++ implementation)
 */
static always_inline f64_dec f64todec(uint64_t rsig, int32_t rexp, uint64_t c, int32_t q) {
    uint64_t cbl, cb, cbr, vbl, vb, vbr, lower, upper, s;
    int32_t k, h;
    bool even, irregular, w_inside, u_inside;
    f64_dec dec;

    even = !(c & 1);
    irregular = rsig == 0 && rexp > 1;

    cbl = 4 * c - 2 + irregular;
    cb = 4 * c;
    cbr = 4 * c + 2;

    /*  q is in [-1500, 1500]
        k = irregular ? floor(log_10(3/4 * 2^q)) : floor(log10(pow(2^q)))
        floor(log10(3/4 * 2^q))  = (q * 1262611 - 524031) >> 22
        floor(log10(pow(2^q))) = (q * 1262611) >> 22 */
    k = (q * 1262611 - (irregular ? 524031 : 0)) >> 22;

    /*  k is in [-1233, 1233]
        s = floor(V) = floor(c * 2^q * 10^-k)
        vb = 4V = 4 * c * 2^q * 10^-k */
    h = q + ((-k) * 1741647 >> 19) + 1;
    uint64x2 pow10 = pow10_ceil_sig(-k);
    vbl = round_odd(pow10, cbl << h);
    vb = round_odd(pow10, cb << h);
    vbr = round_odd(pow10, cbr << h);

    lower = vbl + !even;
    upper = vbr - !even;

    s = vb / 4;
    if (s >= 10) {
        /* R_k+1 interval contains at most one: up or wp */
        uint64_t sp = s / 10;
        bool up_inside = lower <= (40 * sp);
        bool wp_inside = (40 * sp + 40) <= upper;
        if (up_inside != wp_inside) {
            dec.sig = sp + wp_inside;
            dec.exp = k + 1;
            return dec;
        }
    }

    /* R_k interval contains at least one: u or w */
    u_inside = lower <= (4 * s);
    w_inside = (4 * s + 4) <= upper;
    if (u_inside != w_inside) {
        dec.sig = s + w_inside;
        dec.exp = k;
        return dec;
    }

    /* R_k interval contains both u or w */
    uint64_t mid = 4 * s + 2;
    bool round_up = vb > mid || (vb == mid && (s & 1) != 0);
    dec.sig = s + round_up;
    dec.exp = k;
    return dec;
}

int f64toa(char *out, double fp) {
    char* p = out;
    uint64_t raw = f64toraw(fp);
    bool neg;
    uint64_t rsig, c;
    int32_t rexp, q;

    neg = ((raw >> (F64_BITS - 1)) != 0);
    rsig = raw & F64_SIG_MASK;
    rexp = (int32_t)((raw & F64_EXP_MASK) >> F64_SIG_BITS);

    /* check infinity and nan */
    if (unlikely(rexp == F64_INF_NAN_EXP)) {
        return 0;
    }

    /* check negative numbers */
    *p = '-';
    p += neg;

    /* simple case of 0.0 */
    if ((raw << 1) ==  0) {
        *p++ = '0';
        return p - out;
    }

    /* fp = c * 2^q */
    if (likely(rexp != 0)) {
        /* double is normal */
        c = rsig | F64_HIDDEN_BIT;
        q = rexp - F64_EXP_BIAS - F64_SIG_BITS;

        /* fast path for integer */
        if (q <= 0 && q >= -F64_SIG_BITS && is_div_pow2(c, -q)) {
            uint64_t u = c >> -q;
            p = format_integer(u, p, ctz10(u));
            return p - out;
        }

    } else {
        c = rsig;
        q = 1 - F64_EXP_BIAS - F64_SIG_BITS;
    }

    f64_dec dec = f64todec(rsig, rexp, c, q);
    p = write_dec(dec, p);
    return p - out;
}

#undef F64_BITS       
#undef F64_EXP_BITS   
#undef F64_SIG_BITS   
#undef F64_EXP_MASK   
#undef F64_SIG_MASK   
#undef F64_EXP_BIAS   
#undef F64_INF_NAN_EXP
#undef F64_HIDDEN_BIT 
