#include "modp_numtoa.h"

#include <math.h>
#include <stdio.h>

#include "modp_stdint.h"

/*
 * other interesting references on num to string convesion
 * http://www.jb.man.ac.uk/~slowe/cpp/itoa.html
 * and http://www.ddj.com/dept/cpp/184401596?pgno=6
 *
 * Version 19-Nov-2007
 * Fixed round-to-even rules to match printf
 *   thanks to Johannes Otepka
 * Version 22-Sep-2016
 *  Fixed rounding error with decimals ending in 5 and low precision
 */

/**
 * Powers of 10
 * 10^0 to 10^9
 */
static const double powers_of_10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000,
    10000000, 100000000, 1000000000 };

static void strreverse(char* begin, char* end)
{
    char aux;
    while (end > begin)
        aux = *end, *end-- = *begin, *begin++ = aux;
}

size_t modp_itoa10(int32_t value, char* str)
{
    char* wstr = str;
    /* Take care of sign */
    uint32_t uvalue = (value < 0) ? (uint32_t)(-value) : (uint32_t)(value);
    /* Conversion. Number is reversed. */
    do
        *wstr++ = (char)(48 + (uvalue % 10));
    while (uvalue /= 10);
    if (value < 0)
        *wstr++ = '-';
    *wstr = '\0';

    /* Reverse string */
    strreverse(str, wstr - 1);
    return (size_t)(wstr - str);
}

size_t modp_uitoa10(uint32_t value, char* str)
{
    char* wstr = str;
    /* Conversion. Number is reversed. */
    do
        *wstr++ = (char)(48 + (value % 10));
    while (value /= 10);
    *wstr = '\0';
    /* Reverse string */
    strreverse(str, wstr - 1);
    return (size_t)(wstr - str);
}

size_t modp_litoa10(int64_t value, char* str)
{
    char* wstr = str;
    uint64_t uvalue = (value < 0) ? (uint64_t)(-value) : (uint64_t)(value);

    /* Conversion. Number is reversed. */
    do
        *wstr++ = (char)(48 + (uvalue % 10));
    while (uvalue /= 10);
    if (value < 0)
        *wstr++ = '-';
    *wstr = '\0';

    /* Reverse string */
    strreverse(str, wstr - 1);
    return (size_t)(wstr - str);
}

size_t modp_ulitoa10(uint64_t value, char* str)
{
    char* wstr = str;
    /* Conversion. Number is reversed. */
    do
        *wstr++ = (char)(48 + (value % 10));
    while (value /= 10);
    *wstr = '\0';
    /* Reverse string */
    strreverse(str, wstr - 1);
    return (size_t)(wstr - str);
}

size_t modp_dtoa(double value, char* str, int prec)
{
    /* Hacky test for NaN
	 * under -fast-math this won't work, but then you also won't
	 * have correct nan values anyways.  The alternative is
	 * to link with libmath (bad) or hack IEEE double bits (bad)
	 */
    if (!(value == value)) {
        str[0] = 'n';
        str[1] = 'a';
        str[2] = 'n';
        str[3] = '\0';
        return (size_t)3;
    }
    /* if input is larger than thres_max, revert to exponential */
    const double thres_max = (double)(0x7FFFFFFF);

    double diff = 0.0;
    char* wstr = str;

    if (prec < 0) {
        prec = 0;
    } else if (prec > 9) {
        /* precision of >= 10 can lead to overflow errors */
        prec = 9;
    }

    /* we'll work in positive values and deal with the
	   negative sign issue later */
    int neg = 0;
    if (value < 0) {
        neg = 1;
        value = -value;
    }

    // given 0.05, prec=1
    // whole = 0
    // tmp = (0.05)* 10 = 0.5
    // frac = 0
    // diff = tmp -frac == 0.5 - 0.0 = 0.5
    //
    int whole = (int)value;
    double tmp = (value - whole) * powers_of_10[prec];
    uint32_t frac = (uint32_t)(tmp);
    diff = tmp - frac;

    if (diff > 0.5) {
        ++frac;
        /* handle rollover, e.g.  case 0.99 with prec 1 is 1.0  */
        if (frac >= powers_of_10[prec]) {
            frac = 0;
            ++whole;
        }
    } else if (diff == 0.5 && prec > 0 && (frac & 1)) {
        /* if halfway, round up if odd, OR
		   if last digit is 0.  That last part is strange */
        ++frac;
        if (frac >= powers_of_10[prec]) {
            frac = 0;
            ++whole;
        }
    } else if (diff == 0.5 && prec == 0 && (whole & 1)) {
        ++frac;
        if (frac >= powers_of_10[prec]) {
            frac = 0;
            ++whole;
        }
    }

    /* for very large numbers switch back to native sprintf for exponentials.
	   anyone want to write code to replace this? */
    /*
	   normal printf behavior is to print EVERY whole number digit
	   which can be 100s of characters overflowing your buffers == bad
	   */
    if (value > thres_max) {
        sprintf(str, "%e", neg ? -value : value);
        return strlen(str);
    }

    int count = prec;
    while (count > 0) {
        --count;
        *wstr++ = (char)(48 + (frac % 10));
        frac /= 10;
    }
    if (frac > 0) {
        ++whole;
    }

    /* add decimal */
    if (prec > 0) {
        *wstr++ = '.';
    }

    /* do whole part
	 * Take care of sign conversion
	 * Number is reversed.
	 */
    do
        *wstr++ = (char)(48 + (whole % 10));
    while (whole /= 10);
    if (neg) {
        *wstr++ = '-';
    }
    *wstr = '\0';
    strreverse(str, wstr - 1);
    return (size_t)(wstr - str);
}

/* This is near identical to modp_dtoa above
 *   The differnce is noted below
 */
size_t modp_dtoa2(double value, char* str, int prec)
{
    /* Hacky test for NaN
	 * under -fast-math this won't work, but then you also won't
	 * have correct nan values anyways.  The alternative is
	 * to link with libmath (bad) or hack IEEE double bits (bad)
	 */
    if (!(value == value)) {
        str[0] = 'n';
        str[1] = 'a';
        str[2] = 'n';
        str[3] = '\0';
        return (size_t)3;
    }

    /* if input is larger than thres_max, revert to exponential */
    const double thres_max = (double)(0x7FFFFFFF);

    double diff = 0.0;
    char* wstr = str;

    if (prec < 0) {
        prec = 0;
    } else if (prec > 9) {
        /* precision of >= 10 can lead to overflow errors */
        prec = 9;
    }

    /* we'll work in positive values and deal with the
	   negative sign issue later */
    int neg = 0;
    if (value < 0) {
        neg = 1;
        value = -value;
    }

    int whole = (int)value;
    double tmp = (value - whole) * powers_of_10[prec];
    uint32_t frac = (uint32_t)(tmp);
    diff = tmp - frac;

    if (diff > 0.5) {
        ++frac;
        /* handle rollover, e.g.  case 0.99 with prec 1 is 1.0  */
        if (frac >= powers_of_10[prec]) {
            frac = 0;
            ++whole;
        }
    } else if (diff == 0.5 && prec > 0 && (frac & 1)) {
        /* if halfway, round up if odd, OR
		   if last digit is 0.  That last part is strange */
        ++frac;
        if (frac >= powers_of_10[prec]) {
            frac = 0;
            ++whole;
        }
    } else if (diff == 0.5 && prec == 0 && (whole & 1)) {
        ++frac;
        if (frac >= powers_of_10[prec]) {
            frac = 0;
            ++whole;
        }
    }

    /* for very large numbers switch back to native sprintf for exponentials.
	   anyone want to write code to replace this? */
    /*
	   normal printf behavior is to print EVERY whole number digit
	   which can be 100s of characters overflowing your buffers == bad
	   */
    if (value > thres_max) {
        sprintf(str, "%e", neg ? -value : value);
        return strlen(str);
    }

    int has_decimal = 0;
    int count = prec;

    /* Remove ending zeros */
    if (prec > 0) {
        while (count > 0 && ((frac % 10) == 0)) {
            count--;
            frac /= 10;
        }
    }

    while (count > 0) {
        --count;
        *wstr++ = (char)(48 + (frac % 10));
        frac /= 10;
        has_decimal = 1;
    }

    if (frac > 0) {
        ++whole;
    }

    /* add decimal */
    if (has_decimal) {
        *wstr++ = '.';
    }
    /* do whole part
	 * Take care of sign conversion
	 * Number is reversed.
	 */
    do
        *wstr++ = (char)(48 + (whole % 10));
    while (whole /= 10);
    if (neg) {
        *wstr++ = '-';
    }
    *wstr = '\0';
    strreverse(str, wstr - 1);
    return (size_t)(wstr - str);
}

char* modp_uitoa16(uint32_t value, char* str, int isfinal)
{
    static const char* hexchars = "0123456789ABCDEF";

/**
	 * Implementation note: 
	 *  No re-assignment of "value"
	 *  Each line is independent than the previous, so
	 *    even dumb compilers can pipeline without loop unrolling
	 */
    str[0] = hexchars[(value >> 28) & 0x0000000F];
    str[1] = hexchars[(value >> 24) & 0x0000000F];
    str[2] = hexchars[(value >> 20) & 0x0000000F];
    str[3] = hexchars[(value >> 16) & 0x0000000F];
    str[4] = hexchars[(value >> 12) & 0x0000000F];
    str[5] = hexchars[(value >> 8) & 0x0000000F];
    str[6] = hexchars[(value >> 4) & 0x0000000F];
    str[7] = hexchars[(value)&0x0000000F];

    if (isfinal) {
        str[8] = '\0';
        return str;
    } else {
        return str + 8;
    }
}
