/*
 * Copyright (c) 2004-2005 The Trustees of Indiana University and Indiana
 *                         University Research and Technology
 *                         Corporation.  All rights reserved.
 * Copyright (c) 2004-2013 The University of Tennessee and The University
 *                         of Tennessee Research Foundation.  All rights
 *                         reserved.
 * Copyright (c) 2004-2005 High Performance Computing Center Stuttgart,
 *                         University of Stuttgart.  All rights reserved.
 * Copyright (c) 2004-2005 The Regents of the University of California.
 *                         All rights reserved.
 * Copyright (c) 2007      Cisco Systems, Inc.  All rights reserved.
 * Copyright (c) 2018      Amazon.com, Inc. or its affiliates.  All Rights reserved.
 * Copyright (c) 2015-2020 Intel, Inc.  All rights reserved.
 * Copyright (c) 2021-2025 Nanook Consulting  All rights reserved.
 * $COPYRIGHT$
 *
 * Additional copyrights may follow
 *
 * $HEADER$
 */

/*
 * Buffer safe printf functions for portability to archaic platforms.
 */

#include "src/include/pmix_config.h"

#include "src/util/pmix_output.h"
#include "src/util/pmix_printf.h"

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifndef HAVE_VASPRINTF
/*
 * Make a good guess about how long a printf-style varargs formatted
 * string will be once all the % escapes are filled in.  We don't
 * handle every % escape here, but we handle enough, and then add a
 * fudge factor in at the end.
 */
static int guess_strlen(const char *fmt, va_list ap)
{
#if HAVE_VSNPRINTF
    char dummy[1];

    /* vsnprintf() returns the number of bytes that would have been
     copied if the provided buffer were infinite. */
    return 1 + vsnprintf(dummy, sizeof(dummy), fmt, ap);
#else
    char *sarg, carg;
    double darg;
    float farg;
    size_t i;
    int iarg;
    int len;
    long larg;

    /* Start off with a fudge factor of 128 to handle the % escapes that
     we aren't calculating here */

    len = (int) strlen(fmt) + 128;
    for (i = 0; i < strlen(fmt); ++i) {
        if ('%' == fmt[i] && i + 1 < strlen(fmt) && '%' != fmt[i + 1]) {
            ++i;
            switch (fmt[i]) {
                case 'c':
                    carg = va_arg(ap, int);
                    len += 1; /* let's suppose it's a printable char */
                    (void)
                    carg; /* prevent compiler from complaining about set but not used variables */
                    break;
                case 's':
                    sarg = va_arg(ap, char *);

                    /* If there's an arg, get the strlen, otherwise we'll
                     * use (null) */

                    if (NULL != sarg) {
                        len += (int) strlen(sarg);
                    } else {
#if PMIX_ENABLE_DEBUG
                        pmix_output(0, "PMIX DEBUG WARNING: Got a NULL argument to pmix_vasprintf!\n");
#endif
                        len += 5;
                    }
                    break;

                case 'd':
                case 'i':
                    iarg = va_arg(ap, int);
                    /* Alloc for minus sign */
                    if (iarg < 0)
                        ++len;
                    /* Now get the log10 */
                    do {
                        ++len;
                        iarg /= 10;
                    } while (0 != iarg);
                    break;

                case 'x':
                case 'X':
                    iarg = va_arg(ap, int);
                    /* Now get the log16 */
                    do {
                        ++len;
                        iarg /= 16;
                    } while (0 != iarg);
                    break;

                case 'f':
                    farg = (float) va_arg(ap, int);
                    /* Alloc for minus sign */
                    if (farg < 0) {
                        ++len;
                        farg = -farg;
                    }
                    /* Alloc for 3 decimal places + '.' */
                    len += 4;
                    /* Now get the log10 */
                    do {
                        ++len;
                        farg /= 10.0;
                    } while (0 != farg);
                    break;

                case 'g':
                    darg = va_arg(ap, int);
                    /* Alloc for minus sign */
                    if (darg < 0) {
                        ++len;
                        darg = -darg;
                    }
                    /* Alloc for 3 decimal places + '.' */
                    len += 4;
                    /* Now get the log10 */
                    do {
                        ++len;
                        darg /= 10.0;
                    } while (0 != darg);
                    break;

                case 'l':
                    /* Get %ld %lx %lX %lf */
                    if (i + 1 < strlen(fmt)) {
                        ++i;
                        switch (fmt[i]) {
                            case 'x':
                            case 'X':
                                larg = va_arg(ap, int);
                                /* Now get the log16 */
                                do {
                                    ++len;
                                    larg /= 16;
                                } while (0 != larg);
                                break;

                            case 'f':
                                darg = va_arg(ap, int);
                                /* Alloc for minus sign */
                                if (darg < 0) {
                                    ++len;
                                    darg = -darg;
                                }
                                /* Alloc for 3 decimal places + '.' */
                                len += 4;
                                /* Now get the log10 */
                                do {
                                    ++len;
                                    darg /= 10.0;
                                } while (0 != darg);
                                break;

                            case 'd':
                            default:
                                larg = va_arg(ap, int);
                                /* Now get the log10 */
                                do {
                                    ++len;
                                    larg /= 10;
                                } while (0 != larg);
                                break;
                        }
                    }

                default:
                    break;
            }
        }
    }

    return len;
#endif
}
#endif /* #ifndef HAVE_VASPRINTF */

int pmix_asprintf(char **ptr, const char *fmt, ...)
{
    int length;
    va_list ap;

    va_start(ap, fmt);
    /* pmix_vasprintf guarantees that *ptr is set to NULL on error */
    length = pmix_vasprintf(ptr, fmt, ap);
    va_end(ap);

    return length;
}

int pmix_vasprintf(char **ptr, const char *fmt, va_list ap)
{
#ifdef HAVE_VASPRINTF
    int length;

    length = vasprintf(ptr, fmt, ap);
    if (length < 0) {
        *ptr = NULL;
    }

    return length;
#else
    int length;
    va_list ap2;

    /* va_list might have pointer to internal state and using
     it twice is a bad idea.  So make a copy for the second
     use.  Copy order taken from Autoconf docs. */
#if PMIX_HAVE_VA_COPY
    va_copy(ap2, ap);
#elif PMIX_HAVE_UNDERSCORE_VA_COPY
    __va_copy(ap2, ap);
#else
    memcpy(&ap2, &ap, sizeof(va_list));
#endif

    /* guess the size */
    length = guess_strlen(fmt, ap);

    /* allocate a buffer */
    *ptr = (char *) malloc((size_t) length + 1);
    if (NULL == *ptr) {
        errno = ENOMEM;
        va_end(ap2);
        return -1;
    }

    /* fill the buffer */
    length = vsprintf(*ptr, fmt, ap2);
#if PMIX_HAVE_VA_COPY || PMIX_HAVE_UNDERSCORE_VA_COPY
    va_end(ap2);
#endif /* PMIX_HAVE_VA_COPY || PMIX_HAVE_UNDERSCORE_VA_COPY */

    /* realloc */
    *ptr = (char *) realloc(*ptr, (size_t) length + 1);
    if (NULL == *ptr) {
        errno = ENOMEM;
        return -1;
    }

    return length;
#endif
}

int pmix_snprintf(char *str, size_t size, const char *fmt, ...)
{
    int length;
    va_list ap;

    va_start(ap, fmt);
    length = pmix_vsnprintf(str, size, fmt, ap);
    va_end(ap);

    return length;
}

int pmix_vsnprintf(char *str, size_t size, const char *fmt, va_list ap)
{
    int length;
    char *buf;

    length = pmix_vasprintf(&buf, fmt, ap);
    if (length < 0) {
        return length;
    }

    /* return the length when given a null buffer (C99) */
    if (str && size > 0) {
        if ((size_t) length < size) {
            strcpy(str, buf);
        } else {
            memcpy(str, buf, size - 1);
            str[size - 1] = '\0';
        }
    }

    /* free allocated buffer */
    free(buf);

    return length;
}

#ifdef TEST

int main(int argc, char *argv[])
{
    char a[10];
    char b[173];
    char *s;
    int length;

    puts("test for NULL buffer in snprintf:");
    length = pmix_snprintf(NULL, 0, "this is a string %d", 1004);
    printf("length = %d\n", length);

    puts("test of snprintf to an undersize buffer:");
    length = pmix_snprintf(a, sizeof(a), "this is a string %d", 1004);
    printf("string = %s\n", a);
    printf("length = %d\n", length);
    printf("strlen = %d\n", (int) strlen(a));

    puts("test of snprintf to an oversize buffer:");
    length = pmix_snprintf(b, sizeof(b), "this is a string %d", 1004);
    printf("string = %s\n", b);
    printf("length = %d\n", length);
    printf("strlen = %d\n", (int) strlen(b));

    puts("test of asprintf:");
    length = pmix_asprintf(&s, "this is a string %d", 1004);
    printf("string = %s\n", s);
    printf("length = %d\n", length);
    printf("strlen = %d\n", (int) strlen(s));

    free(s);

    return 0;
}

#endif
