/*************************************************************************\
* Copyright (c) 2012 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.
\*************************************************************************/
/* Authors: Eric Norum & Andrew Johnson */

#include <ctype.h>
#include <stdio.h>
#include <errno.h>
#include <float.h>

#include "epicsMath.h"
#include "epicsStdlib.h"
#include "epicsString.h"
#include "epicsConvert.h"


/* These are the conversion primitives */

LIBCOM_API int
epicsParseLong(const char *str, long *to, int base, char **units)
{
    int c;
    char *endp;
    long value;

    while ((c = *str) && isspace(c))
        ++str;

    errno = 0;
    value = strtol(str, &endp, base);

    if (endp == str)
        return S_stdlib_noConversion;
    if (errno == EINVAL)    /* Not universally supported */
        return S_stdlib_badBase;
    if (errno == ERANGE)
        return S_stdlib_overflow;

    while ((c = *endp) && isspace(c))
        ++endp;
    if (c && !units)
        return S_stdlib_extraneous;

    *to = value;
    if (units)
        *units = endp;
    return 0;
}

LIBCOM_API int
epicsParseULong(const char *str, unsigned long *to, int base, char **units)
{
    int c;
    char *endp;
    unsigned long value;

    while ((c = *str) && isspace(c))
        ++str;

    errno = 0;
    value = strtoul(str, &endp, base);

    if (endp == str)
        return S_stdlib_noConversion;
    if (errno == EINVAL)    /* Not universally supported */
        return S_stdlib_badBase;
    if (errno == ERANGE)
        return S_stdlib_overflow;

    while ((c = *endp) && isspace(c))
        ++endp;
    if (c && !units)
        return S_stdlib_extraneous;

    *to = value;
    if (units)
        *units = endp;
    return 0;
}

LIBCOM_API int
epicsParseLLong(const char *str, long long *to, int base, char **units)
{
    int c;
    char *endp;
    long long value;

    while ((c = *str) && isspace(c))
        ++str;

    errno = 0;
    value = strtoll(str, &endp, base);

    if (endp == str)
        return S_stdlib_noConversion;
    if (errno == EINVAL)    /* Not universally supported */
        return S_stdlib_badBase;
    if (errno == ERANGE)
        return S_stdlib_overflow;

    while ((c = *endp) && isspace(c))
        ++endp;
    if (c && !units)
        return S_stdlib_extraneous;

    *to = value;
    if (units)
        *units = endp;
    return 0;
}

LIBCOM_API int
epicsParseULLong(const char *str, unsigned long long *to, int base, char **units)
{
    int c;
    char *endp;
    unsigned long long value;

    while ((c = *str) && isspace(c))
        ++str;

    errno = 0;
    value = strtoull(str, &endp, base);

    if (endp == str)
        return S_stdlib_noConversion;
    if (errno == EINVAL)    /* Not universally supported */
        return S_stdlib_badBase;
    if (errno == ERANGE)
        return S_stdlib_overflow;

    while ((c = *endp) && isspace(c))
        ++endp;
    if (c && !units)
        return S_stdlib_extraneous;

    *to = value;
    if (units)
        *units = endp;
    return 0;
}

LIBCOM_API int
epicsParseDouble(const char *str, double *to, char **units)
{
    int c;
    char *endp;
    double value;

    while ((c = *str) && isspace(c))
        ++str;

    errno = 0;
    value = epicsStrtod(str, &endp);

    if (endp == str)
        return S_stdlib_noConversion;
    if (errno == ERANGE)
        return (value == 0) ? S_stdlib_underflow : S_stdlib_overflow;

    while ((c = *endp) && isspace(c))
        ++endp;
    if (c && !units)
        return S_stdlib_extraneous;

    *to = value;
    if (units)
        *units = endp;
    return 0;
}


/* These call the primitives */

LIBCOM_API int
epicsParseInt8(const char *str, epicsInt8 *to, int base, char **units)
{
    long value;
    int status = epicsParseLong(str, &value, base, units);

    if (status)
        return status;

    if (value < -0x80 || value > 0x7f)
        return S_stdlib_overflow;

    *to = (epicsInt8) value;
    return 0;
}

LIBCOM_API int
epicsParseUInt8(const char *str, epicsUInt8 *to, int base, char **units)
{
    unsigned long value;
    int status = epicsParseULong(str, &value, base, units);

    if (status)
        return status;

    if (value > 0xff && value <= ~0xffUL)
        return S_stdlib_overflow;

    *to = (epicsUInt8) value;
    return 0;
}

LIBCOM_API int
epicsParseInt16(const char *str, epicsInt16 *to, int base, char **units)
{
    long value;
    int status = epicsParseLong(str, &value, base, units);

    if (status)
        return status;

    if (value < -0x8000 || value > 0x7fff)
        return S_stdlib_overflow;

    *to = (epicsInt16) value;
    return 0;
}

LIBCOM_API int
epicsParseUInt16(const char *str, epicsUInt16 *to, int base, char **units)
{
    unsigned long value;
    int status = epicsParseULong(str, &value, base, units);

    if (status)
        return status;

    if (value > 0xffff && value <= ~0xffffUL)
        return S_stdlib_overflow;

    *to = (epicsUInt16) value;
    return 0;
}

LIBCOM_API int
epicsParseInt32(const char *str, epicsInt32 *to, int base, char **units)
{
    long value;
    int status = epicsParseLong(str, &value, base, units);

    if (status)
        return status;

#if (LONG_MAX > 0x7fffffffLL)
    if (value < -0x80000000L || value > 0x7fffffffL)
        return S_stdlib_overflow;
#endif

    *to = (epicsInt32) value;
    return 0;
}

LIBCOM_API int
epicsParseUInt32(const char *str, epicsUInt32 *to, int base, char **units)
{
    unsigned long value;
    int status = epicsParseULong(str, &value, base, units);

    if (status)
        return status;

#if (ULONG_MAX > 0xffffffffULL)
    if (value > 0xffffffffUL && value <= ~0xffffffffUL)
        return S_stdlib_overflow;
#endif

    *to = (epicsUInt32) value;
    return 0;
}

LIBCOM_API int
epicsParseInt64(const char *str, epicsInt64 *to, int base, char **units)
{
#if (LONG_MAX == 0x7fffffffffffffffLL)
    long value;
    int status = epicsParseLong(str, &value, base, units);
#else
    long long value;
    int status = epicsParseLLong(str, &value, base, units);
#endif

    if (status)
        return status;

    *to = (epicsInt64) value;
    return 0;
}

LIBCOM_API int
epicsParseUInt64(const char *str, epicsUInt64 *to, int base, char **units)
{
#if (ULONG_MAX == 0xffffffffffffffffULL)
    unsigned long value;
    int status = epicsParseULong(str, &value, base, units);
#else
    unsigned long long value;
    int status = epicsParseULLong(str, &value, base, units);
#endif

    if (status)
        return status;

    *to = (epicsUInt64) value;
    return 0;
}


LIBCOM_API int
epicsParseFloat(const char *str, float *to, char **units)
{
    double value, abs;
    int status = epicsParseDouble(str, &value, units);

    if (status)
        return status;

    abs = fabs(value);
    if (value > 0 && abs <= FLT_MIN)
        return S_stdlib_underflow;
    if (finite(value) && abs >= FLT_MAX)
        return S_stdlib_overflow;

    *to = (float) value;
    return 0;
}


/* If strtod() works properly, the OS-specific osdStrtod.h does:
 *   #define epicsStrtod strtod
 *
 * If strtod() is broken, osdStrtod.h defines this prototype:
 *   LIBCOM_API double epicsStrtod(const char *str, char **endp);
 */

#ifndef epicsStrtod
LIBCOM_API double
epicsStrtod(const char *str, char **endp)
{
    const char *cp = str;
    int negative = 0;
    double res;

    while (isspace((int)*cp))
        cp++;

    if (*cp == '+') {
        cp++;
    } else if (*cp == '-') {
        negative = 1;
        cp++;
    }

    if (epicsStrnCaseCmp("0x", cp, 2) == 0) {
        if (negative)
            return strtoll(str, endp, 16);
        else
            return strtoull(str, endp, 16);
    }
    if (!isalpha((int)*cp)) {
        res = strtod(str, endp);
        if (isinf(res))
            errno = ERANGE;
        return res;
    }

    if (epicsStrnCaseCmp("NAN", cp, 3) == 0) {
        res = epicsNAN;
        cp += 3;
        if (*cp == '(') {
            cp++;
            while (*cp && (*cp++ != ')'))
                continue;
        }
    }
    else if (epicsStrnCaseCmp("INF", cp, 3) == 0) {
        res = negative ? -epicsINF : epicsINF;
        cp += 3;
        if (epicsStrnCaseCmp("INITY", cp, 5) == 0) {
            cp += 5;
        }
    } else {
        cp = str;
        res = 0;
    }

    if (endp)
        *endp = (char *)cp;

    return res;
}
#endif
