/*************************************************************************\
* Copyright (c) 2013 UChicago Argonne LLC, as Operator of Argonne
*     National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
*     Operator of Los Alamos National Laboratory.
* SPDX-License-Identifier: EPICS
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
/* Fast numeric to string conversions
 *
 * Original Authors:
 *    Bob Dalesio, Mark Anderson and Marty Kraimer
 *    Date:            12 January 1993
 */

#include <string.h>
#include <limits.h>

#include "cvtFast.h"
#include "epicsMath.h"
#include "epicsStdio.h"

/*
 * These routines convert numbers up to +/- 10,000,000.
 * They defer to sprintf() for numbers requiring more than
 * 8 places of precision.
 */
static epicsInt32 frac_multiplier[] =
    {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};

int cvtFloatToString(float flt_value, char *pdest,
    epicsUInt16 precision)
{
    int got_one, i;
    epicsInt32  whole, iplace, number, fraction, fplace;
    float       ftemp;
    char        *startAddr;

    /* can this routine handle this conversion */
    if (isnan(flt_value) || precision > 8 ||
        flt_value > 10000000.0 || flt_value < -10000000.0) {
        if (precision > 8 || flt_value >= 1e8 || flt_value <= -1e8) {
            if (precision > 12) precision = 12; /* FIXME */
            sprintf(pdest, "%*.*e", precision+6, precision, (double) flt_value);
        } else {
            if (precision > 3) precision = 3; /* FIXME */
            sprintf(pdest, "%.*f", precision, (double) flt_value);
        }
        return((int)strlen(pdest));
    }
    startAddr = pdest;

    /* determine the sign */
    if (flt_value < 0){
        *pdest++ = '-';
        flt_value = -flt_value;
    };

    /* remove the whole number portion */
    whole = (epicsInt32)flt_value;
    ftemp = flt_value - whole;

    /* multiplier to convert fractional portion to integer */
    fplace = frac_multiplier[precision];
    fraction = (epicsInt32)(ftemp * fplace * 10);
    fraction = (fraction + 5) / 10;     /* round up */

    /* determine rounding into the whole number portion */
    if ((fraction / fplace) >= 1){
        whole++;
        fraction -= fplace;
    }

    /* whole numbers */
    got_one = 0;
    for (iplace = 10000000; iplace >= 1; iplace /= 10){
        if (whole >= iplace){
            got_one = 1;
            number = whole / iplace;
            whole = whole - (number * iplace);
            *pdest = number + '0';
            pdest++;
        }else if (got_one){
            *pdest = '0';
            pdest++;
        }
    }
    if (!got_one){
        *pdest = '0';
        pdest++;
    }

    /* fraction */
    if (precision > 0){
    /* convert fractional portion to ASCII */
        *pdest = '.';
        pdest++;
        for (fplace /= 10, i = precision; i > 0; fplace /= 10,i--){
            number = fraction / fplace;
            fraction -= number * fplace;
            *pdest = number + '0';
            pdest++;
        }
    }
    *pdest = 0;

    return((int)(pdest - startAddr));
}

int cvtDoubleToString(
    double flt_value,
    char  *pdest,
    epicsUInt16 precision)
{
    epicsUInt16 got_one,i;
    epicsInt32  whole,iplace,number,fraction,fplace;
    double      ftemp;
    char        *startAddr;

    /* can this routine handle this conversion */
    if (isnan(flt_value) || precision > 8 || flt_value > 10000000.0 || flt_value < -10000000.0) {
        if (precision > 8 || flt_value > 1e16 || flt_value < -1e16) {
            if(precision>17) precision=17;
            sprintf(pdest,"%*.*e",precision+7,precision,
            flt_value);
        } else {
            if(precision>3) precision=3;
            sprintf(pdest,"%.*f",precision,flt_value);
        }
        return((int)strlen(pdest));
    }
    startAddr = pdest;

    /* determine the sign */
    if (flt_value < 0){
        *pdest++ = '-';
        flt_value = -flt_value;
    };

    /* remove the whole number portion */
    whole = (epicsInt32)flt_value;
    ftemp = flt_value - whole;

    /* multiplier to convert fractional portion to integer */
    fplace = frac_multiplier[precision];
    fraction = (epicsInt32)(ftemp * fplace * 10);
    fraction = (fraction + 5) / 10;     /* round up */

    /* determine rounding into the whole number portion */
    if ((fraction / fplace) >= 1){
        whole++;
        fraction -= fplace;
    }

    /* whole numbers */
    got_one = 0;
    for (iplace = 10000000; iplace >= 1; iplace /= 10){
        if (whole >= iplace){
            got_one = 1;
            number = whole / iplace;
            whole = whole - (number * iplace);
            *pdest = number + '0';
            pdest++;
        }else if (got_one){
            *pdest = '0';
            pdest++;
        }
    }
    if (!got_one){
        *pdest = '0';
        pdest++;
    }

    /* fraction */
    if (precision > 0){
    /* convert fractional portion to ASCII */
        *pdest = '.';
        pdest++;
        for (fplace /= 10, i = precision; i > 0; fplace /= 10,i--){
            number = fraction / fplace;
            fraction -= number * fplace;
            *pdest = number + '0';
            pdest++;
        }
    }
    *pdest = 0;

    return((int)(pdest - startAddr));
}

/*
 * These routines are provided for backwards compatibility,
 * extensions such as MEDM, edm and histtool use them.
 */

/*
 * cvtFloatToExpString
 *
 * Converts a float to a %e formatted string
 */
int cvtFloatToExpString(float val, char *pdest, epicsUInt16 precision)
{
    return epicsSnprintf(pdest, MAX_STRING_SIZE, "%.*e", precision, val);
}

/*
 * cvtFloatToCompactString
 *
 * Converts a float to a %g formatted string.
 * The result uses %f notation for 10e-4 < |value| < 10e+4,
 * otherwise %e notation.
 */
int cvtFloatToCompactString(float val, char *pdest, epicsUInt16 precision)
{
    if ((val < 1.e4 && val > 1.e-4) ||
        (val > -1.e4 && val < -1.e-4) ||
        val == 0.0)
        return cvtFloatToString(val, pdest, precision);

    return cvtFloatToExpString(val, pdest, precision);
}


/*
 * cvtDoubleToExpString
 *
 * Converts a double to a %e formatted string
 */

int cvtDoubleToExpString(double val, char *pdest, epicsUInt16 precision)
{
    return epicsSnprintf(pdest, MAX_STRING_SIZE, "%.*e", precision, val);
}


/*
 * cvtDoubleToCompactString
 *
 * Converts a double to %g formatted string.
 * The result uses %f notation for 10e-4 < |value| < 10e+4,
 * otherwise %e notation.
 */
int cvtDoubleToCompactString(double val, char *pdest, epicsUInt16 precision)
{
    if ((val < 1.e4 && val > 1.e-4) ||
        (val > -1.e4 && val < -1.e-4) ||
        val == 0.0)
        return cvtDoubleToString(val, pdest, precision);

    return cvtDoubleToExpString(val, pdest, precision);
}


/* Integer conversion primitives */

static size_t
    UInt32ToDec(epicsUInt32 val, char *pdest)
{
    int i;
    char digit[10];
    size_t len;

    for (i = 0; val; i++) {
        epicsUInt32 tenth = val / 10;

        digit[i] = val - tenth * 10 + '0';
        val = tenth;
    }
    len = i;

    while (i > 0)
        *pdest++ = digit[--i];

    *pdest = 0;
    return len;
}

static size_t
    UInt32ToBase(epicsUInt32 val, char *pdest, int base)
{
    int i;
    char digit, digits[32];
    size_t len;

    for (i = 0; val; i++) {
        epicsUInt32 tenth = val / base;

        digit = val - tenth * base;
        digits[i] = digit < 10 ? digit + '0' : digit - 10 + 'a';
        val = tenth;
    }
    len = i;

    while (i > 0)
        *pdest++ = digits[--i];

    *pdest = 0;
    return len;
}

static size_t
    UInt64ToDec(epicsUInt64 val, char *pdest)
{
    int i;
    char digit[20];
    size_t len;

    for (i = 0; val; i++) {
        epicsUInt64 tenth = val / 10;

        digit[i] = val - tenth * 10 + '0';
        val = tenth;
    }

    len = i;
    while (i > 0)
        *pdest++ = digit[--i];

    *pdest = 0;
    return len;
}

static size_t
    UInt64ToBase(epicsUInt64 val, char *pdest, int base)
{
    int i;
    char digit, digits[64];
    size_t len;

    for (i = 0; val; i++) {
        epicsUInt64 tenth = val / base;

        digit = val - tenth * base;
        digits[i] = digit < 10 ? digit + '0' : digit - 10 + 'a';
        val = tenth;
    }
    len = i;

    while (i > 0)
        *pdest++ = digits[--i];

    *pdest = 0;
    return len;
}


/* Integer conversion routines */

size_t
    cvtUInt32ToString(epicsUInt32 val, char *pdest)
{
    if (val == 0) {
        *pdest++ = '0';
        *pdest = 0;
        return 1;
    }

    return UInt32ToDec(val, pdest);
}

size_t
    cvtInt32ToString(epicsInt32 val, char *pdest)
{
    if (val == 0) {
        *pdest++ = '0';
        *pdest = 0;
        return 1;
    }

    if (val > 0)
        return UInt32ToDec(val, pdest);

    if (val == -0x80000000) {
        strcpy(pdest, "-2147483648");
        return strlen(pdest);
    }

    *pdest++ = '-';
    return 1 + UInt32ToDec(-val, pdest);
}


size_t
    cvtUInt64ToString(epicsUInt64 val, char *pdest)
{
    if (val == 0) {
        *pdest++ = '0';
        *pdest = 0;
        return 1;
    }

    return UInt64ToDec(val, pdest);
}

size_t
    cvtInt64ToString(epicsInt64 val, char *pdest)
{
    if (val == 0) {
        *pdest++ = '0';
        *pdest = 0;
        return 1;
    }

    if (val > 0)
        return UInt64ToDec(val, pdest);

    if (val == -0x8000000000000000LL) {
        strcpy(pdest, "-9223372036854775808");
        return strlen(pdest);
    }

    *pdest++ = '-';
    return 1 + UInt64ToDec(-val, pdest);
}


size_t
    cvtInt32ToHexString(epicsInt32 val, char *pdest)
{
    if (val < 0)
        *pdest++ = '-';

    *pdest++ = '0';
    *pdest++ = 'x';

    if (val == 0) {
        *pdest++ = '0';
        *pdest = 0;
        return 3;
    }

    if (val > 0)
        return 2 + UInt32ToBase(val, pdest, 16);

    if (val == -0x80000000) {
        strcpy(pdest, "80000000");
        return 11;
    }

    return 3 + UInt32ToBase(-val, pdest, 16);
}

size_t
    cvtUInt32ToHexString(epicsUInt32 val, char *pdest)
{
    *pdest++ = '0';
    *pdest++ = 'x';

    if (val == 0) {
        *pdest++ = '0';
        *pdest = 0;
        return 3;
    }

    return 2 + UInt32ToBase(val, pdest, 16);
}

size_t
    cvtInt32ToOctalString(epicsInt32 val, char *pdest)
{
    if (val == 0) {
        *pdest++ = '0';
        *pdest = 0;
        return 1;
    }

    if (val > 0) {
        *pdest++ = '0';
        return 1 + UInt32ToBase(val, pdest, 8);
    }

    if (val == -0x80000000) {
        strcpy(pdest, "-020000000000");
        return strlen(pdest);
    }

    *pdest++ = '-';
    *pdest++ = '0';
    return 2 + UInt32ToBase(-val, pdest, 8);
}

size_t
    cvtInt64ToHexString(epicsInt64 val, char *pdest)
{
    if (val < 0)
        *pdest++ = '-';

    *pdest++ = '0';
    *pdest++ = 'x';

    if (val == 0) {
        *pdest++ = '0';
        *pdest = 0;
        return 3;
    }

    if (val > 0)
        return 2 + UInt64ToBase(val, pdest, 16);

    if (val == -0x8000000000000000LL) {
        strcpy(pdest, "8000000000000000");
        return 19;
    }

    return 3 + UInt64ToBase(-val, pdest, 16);
}

size_t
    cvtUInt64ToHexString(epicsUInt64 val, char *pdest)
{
    *pdest++ = '0';
    *pdest++ = 'x';

    if (val == 0) {
        *pdest++ = '0';
        *pdest = 0;
        return 3;
    }

    return 2 + UInt64ToBase(val, pdest, 16);
}

