
/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) NGINX, Inc.
 */

#include <nxt_main.h>
#include <math.h>
#include <float.h>


/*
 * Supported formats:
 *
 *    %[0][width][x|X]O         nxt_off_t
 *    %[0][width][x|X]T         nxt_time_t
 *    %[0][width][u][x|X]z      ssize_t/size_t
 *    %[0][width][u][x|X]d      int/u_int
 *    %[0][width][u][x|X]l      long
 *    %[0][width|m][u][x|X]i    nxt_int_t/nxt_uint_t
 *    %[0][width][u][x|X]D      int32_t/uint32_t
 *    %[0][width][u][x|X]L      int64_t/uint64_t
 *    %[0][width|m][u][x|X]A    nxt_atomic_int_t/nxt_atomic_uint_t
 *    %[0][width][.width]f      double, max valid number fits to %18.15f
 *
 *    %FD                       nxt_fd_t, int / HANDLE
 *    %d                        nxt_socket_t, int
 *
 *    %PI                       nxt_pid_t, process id
 *    %PT                       nxt_tid_t, thread id
 *    %PF                       nxt_fid_t, fiber id
 *    %PH                       pthread_t handle returned by pthread_self()
 *
 *    %s                        null-terminated string
 *    %*s                       length and string
 *    %FN                       nxt_file_name_t *
 *
 *    %M                        nxt_msec_t
 *    %N                        nxt_nsec_t
 *    %r                        rlim_t
 *    %p                        void *
 *    %b                        nxt_bool_t
 *    %E                        nxt_err_t
 *    %V                        nxt_str_t *
 *    %Z                        '\0'
 *    %n                        '\n'
 *    %c                        char
 *    %%                        %
 *
 *  Reserved:
 *    %t                        ptrdiff_t
 *    %S                        null-terminated wchar string
 *    %C                        wchar
 *    %[0][width][u][x|X]Q      int128_t/uint128_t
 */


u_char * nxt_cdecl
nxt_sprintf(u_char *buf, u_char *end, const char *fmt, ...)
{
    u_char   *p;
    va_list  args;

    va_start(args, fmt);
    p = nxt_vsprintf(buf, end, fmt, args);
    va_end(args);

    return p;
}


/*
 * nxt_sprintf_t is used:
 *    to pass several parameters of nxt_integer() via single pointer
 *    and to store little used variables of nxt_vsprintf().
 */

typedef struct {
    u_char        *end;
    const u_char  *hex;
    uint32_t      width;
    int32_t       frac_width;
    uint8_t       max_width;
    u_char        padding;
} nxt_sprintf_t;


static u_char *nxt_integer(nxt_sprintf_t *spf, u_char *buf, uint64_t ui64);
static u_char *nxt_number(nxt_sprintf_t *spf, u_char *buf, double n);


/* A right way of "f == 0.0". */
#define                                                                       \
nxt_double_is_zero(f)                                                         \
    (fabs(f) <= FLT_EPSILON)


u_char *
nxt_vsprintf(u_char *buf, u_char *end, const char *fmt, va_list args)
{
    u_char               *p;
    int                  d;
    double               f, i;
    size_t               length;
    int64_t              i64;
    uint64_t             ui64, frac;
    nxt_str_t            *v;
    nxt_err_t            err;
    nxt_uint_t           scale, n;
    nxt_msec_t           ms;
    nxt_nsec_t           ns;
    nxt_bool_t           sign;
    nxt_sprintf_t        spf;
    nxt_file_name_t      *fn;

    static const u_char  hexadecimal[16] = "0123456789abcdef";
    static const u_char  HEXADECIMAL[16] = "0123456789ABCDEF";
    static const u_char  nan[] = "[nan]";
    static const u_char  infinity[] = "[infinity]";

    spf.end = end;

    while (*fmt != '\0' && buf < end) {

        /*
         * "buf < end" means that we could copy at least one character:
         * a plain character, "%%", "%c", or a minus without test.
         */

        if (*fmt != '%') {
            *buf++ = *fmt++;
            continue;
        }

        fmt++;

        /* Test some often used text formats first. */

        switch (*fmt) {

        case 'V':
            fmt++;
            v = va_arg(args, nxt_str_t *);

            if (nxt_fast_path(v != NULL)) {
                length = v->length;
                p = v->start;
                goto copy;
            }

            continue;

        case 's':
            p = va_arg(args, u_char *);

            if (nxt_fast_path(p != NULL)) {
                while (*p != '\0' && buf < end) {
                    *buf++ = *p++;
                }
            }

            fmt++;
            continue;

        case '*':
            length = va_arg(args, size_t);

            fmt++;

            if (*fmt == 's') {
                fmt++;
                p = va_arg(args, u_char *);

                if (nxt_fast_path(p != NULL)) {
                    goto copy;
                }
            }

            continue;

        default:
            break;
        }

        spf.hex = NULL;
        spf.width = 0;
        spf.frac_width = -1;
        spf.max_width = 0;
        spf.padding = (*fmt == '0') ? '0' : ' ';

        sign = 1;

        i64 = 0;
        ui64 = 0;

        while (*fmt >= '0' && *fmt <= '9') {
            spf.width = spf.width * 10 + (*fmt++ - '0');
        }


        for ( ;; ) {
            switch (*fmt) {

            case 'u':
                sign = 0;
                fmt++;
                continue;

            case 'm':
                spf.max_width = 1;
                fmt++;
                continue;

            case 'X':
                spf.hex = HEXADECIMAL;
                sign = 0;
                fmt++;
                continue;

            case 'x':
                spf.hex = hexadecimal;
                sign = 0;
                fmt++;
                continue;

            case '.':
                fmt++;
                spf.frac_width = 0;

                while (*fmt >= '0' && *fmt <= '9') {
                    spf.frac_width = spf.frac_width * 10 + *fmt++ - '0';
                }

                break;

            default:
                break;
            }

            break;
        }


        switch (*fmt) {

        case 'E':
            err = va_arg(args, nxt_err_t);

            *buf++ = '(';
            spf.hex = NULL;
            spf.width = 0;
            buf = nxt_integer(&spf, buf, err);

            if (buf < end - 1) {
                *buf++ = ':';
                *buf++ = ' ';
            }

            buf = nxt_strerror(err, buf, end - buf);

            if (buf < end) {
                *buf++ = ')';
            }

            fmt++;
            continue;

        case 'O':
            i64 = (int64_t) va_arg(args, nxt_off_t);
            sign = 1;
            goto number;

        case 'T':
            i64 = (int64_t) va_arg(args, nxt_time_t);
            sign = 1;
            goto number;

        case 'M':
            ms = (nxt_msec_t) va_arg(args, nxt_msec_t);
            if ((nxt_msec_int_t) ms == -1 && spf.hex == NULL) {
                i64 = -1;
                sign = 1;
            } else {
                ui64 = (uint64_t) ms;
                sign = 0;
            }
            goto number;

        case 'N':
            ns = (nxt_nsec_t) va_arg(args, nxt_nsec_t);
            if ((nxt_nsec_int_t) ns == -1) {
                i64 = -1;
                sign = 1;
            } else {
                ui64 = (uint64_t) ns;
                sign = 0;
            }
            goto number;

        case 'z':
            if (sign) {
                i64 = (int64_t) va_arg(args, ssize_t);
            } else {
                ui64 = (uint64_t) va_arg(args, size_t);
            }
            goto number;

        case 'i':
            if (sign) {
                i64 = (int64_t) va_arg(args, nxt_int_t);
            } else {
                ui64 = (uint64_t) va_arg(args, nxt_uint_t);
            }

            if (spf.max_width != 0) {
                spf.width = NXT_INT_T_LEN;
            }

            goto number;

        case 'd':
            if (sign) {
                i64 = (int64_t) va_arg(args, int);
            } else {
                ui64 = (uint64_t) va_arg(args, u_int);
            }
            goto number;

        case 'l':
            if (sign) {
                i64 = (int64_t) va_arg(args, long);
            } else {
                ui64 = (uint64_t) va_arg(args, u_long);
            }
            goto number;

        case 'D':
            if (sign) {
                i64 = (int64_t) va_arg(args, int32_t);
            } else {
                ui64 = (uint64_t) va_arg(args, uint32_t);
            }
            goto number;

        case 'L':
            if (sign) {
                i64 = va_arg(args, int64_t);
            } else {
                ui64 = va_arg(args, uint64_t);
            }
            goto number;

        case 'A':
            if (sign) {
                i64 = (int64_t) va_arg(args, nxt_atomic_int_t);
            } else {
                ui64 = (uint64_t) va_arg(args, nxt_atomic_uint_t);
            }

            if (spf.max_width != 0) {
                spf.width = NXT_ATOMIC_T_LEN;
            }

            goto number;

        case 'b':
            ui64 = (uint64_t) va_arg(args, nxt_bool_t);
            sign = 0;
            goto number;

        case 'f':
            fmt++;

            f = va_arg(args, double);

            if (f < 0) {
                *buf++ = '-';
                f = -f;
            }

            if (nxt_slow_path(isnan(f))) {
                p = (u_char *) nan;
                length = nxt_length(nan);

                goto copy;

            } else if (nxt_slow_path(isinf(f))) {
                p = (u_char *) infinity;
                length = nxt_length(infinity);

                goto copy;
            }

            (void) modf(f, &i);
            frac = 0;

            if (spf.frac_width > 0) {

                scale = 1;
                for (n = spf.frac_width; n != 0; n--) {
                    scale *= 10;
                }

                frac = (uint64_t) ((f - i) * scale + 0.5);

                if (frac == scale) {
                    i += 1;
                    frac = 0;
                }
            }

            buf = nxt_number(&spf, buf, i);

            if (spf.frac_width > 0) {

                if (buf < end) {
                    *buf++ = '.';

                    spf.hex = NULL;
                    spf.padding = '0';
                    spf.width = spf.frac_width;
                    buf = nxt_integer(&spf, buf, frac);
                }

            } else if (spf.frac_width < 0) {
                f = modf(f, &i);

                if (!nxt_double_is_zero(f) && buf < end) {
                    *buf++ = '.';

                    while (!nxt_double_is_zero(f) && buf < end) {
                        f *= 10;
                        f = modf(f, &i);
                        *buf++ = (u_char) i + '0';
                    }
                }
            }

            continue;

        case 'r':
            i64 = (int64_t) va_arg(args, rlim_t);
            sign = 1;
            break;

        case 'p':
            ui64 = (uintptr_t) va_arg(args, void *);
            sign = 0;
            spf.hex = HEXADECIMAL;
            /*
             * spf.width = NXT_PTR_SIZE * 2;
             * spf.padding = '0';
             */
            goto number;

        case 'c':
            d = va_arg(args, int);
            *buf++ = (u_char) (d & 0xFF);
            fmt++;

            continue;

        case 'F':
            fmt++;

            switch (*fmt) {

            case 'D':
                i64 = (int64_t) va_arg(args, nxt_fd_t);
                sign = 1;

                goto number;

            case 'N':
                fn = va_arg(args, nxt_file_name_t *);
                p = fn;

                while (*p != '\0' && buf < end) {
                    *buf++ = *p++;
                }

                fmt++;
                continue;

            default:
                continue;
            }

        case 'P':
            fmt++;

            switch (*fmt) {

            case 'I':
                i64 = (int64_t) va_arg(args, nxt_pid_t);
                sign = 1;
                goto number;

            case 'T':
                ui64 = (uint64_t) (uintptr_t) va_arg(args, nxt_tid_t);
                sign = 0;
                goto number;
#if 0
            case 'F':
                ui64 = (uint64_t) va_arg(args, nxt_fid_t);
                sign = 0;
                goto number;
#endif
            case 'H':
                ui64 = (uint64_t) (uintptr_t) va_arg(args, pthread_t);
                spf.hex = HEXADECIMAL;
                sign = 0;
                goto number;

            default:
                continue;
            }

        case 'Z':
            *buf++ = '\0';
            fmt++;
            continue;

        case 'n':
            *buf++ = '\n';
            fmt++;
            continue;

        case '%':
            *buf++ = '%';
            fmt++;
            continue;

        default:
            *buf++ = *fmt++;
            continue;
        }

    number:

        if (sign) {
            if (i64 < 0) {
                *buf++ = '-';
                ui64 = (uint64_t) -i64;

            } else {
                ui64 = (uint64_t) i64;
            }
        }

        buf = nxt_integer(&spf, buf, ui64);

        fmt++;
        continue;

    copy:

        buf = nxt_cpymem(buf, p, nxt_min((size_t) (end - buf), length));
        continue;
    }

    return buf;
}


static u_char *
nxt_integer(nxt_sprintf_t *spf, u_char *buf, uint64_t ui64)
{
    u_char  *p, *end;
    size_t  length;
    u_char  temp[NXT_INT64_T_LEN];

    p = temp + NXT_INT64_T_LEN;

    if (spf->hex == NULL) {

#if (NXT_32BIT)

        for ( ;; ) {
            u_char    *start;
            uint32_t  ui32;

            /*
             * 32-bit platforms usually lack hardware support of 64-bit
             * division and remainder operations.  For this reason C compiler
             * adds calls to the runtime library functions which provides
             * these operations.  These functions usually have about hundred
             * lines of code.
             *
             * For 32-bit numbers and some constant divisors GCC, Clang and
             * other compilers can use inlined multiplications and shifts
             * which are faster than division or remainder operations.
             * For example, unsigned "ui32 / 10" is compiled to
             *
             *     ((uint64_t) ui32 * 0xCCCCCCCD) >> 35
             *
             * So a 64-bit number is split to parts by 10^9.  The parts fit
             * to 32 bits and are processed separately as 32-bit numbers.  A
             * number of 64-bit division/remainder operations is significantly
             * decreased depending on the 64-bit number's value, it is
             *   0 if the 64-bit value is less than 4294967296,
             *   1 if the 64-bit value is greater than 4294967295
             *                           and less than 4294967296000000000,
             *   2 otherwise.
             */

            if (ui64 <= 0xFFFFFFFF) {
                ui32 = (uint32_t) ui64;
                start = NULL;

            } else {
                ui32 = (uint32_t) (ui64 % 1000000000);
                start = p - 9;
            }

            do {
                *(--p) = (u_char) (ui32 % 10 + '0');
                ui32 /= 10;
            } while (ui32 != 0);

            if (start == NULL) {
                break;
            }

            /* Add leading zeros of part. */

            while (p > start) {
                *(--p) = '0';
            }

            ui64 /= 1000000000;
        }

#else  /* NXT_64BIT */

        do {
            *(--p) = (u_char) (ui64 % 10 + '0');
            ui64 /= 10;
        } while (ui64 != 0);

#endif

    } else {

        do {
            *(--p) = spf->hex[ui64 & 0xF];
            ui64 >>= 4;
        } while (ui64 != 0);
    }

    /* Zero or space padding. */

    if (spf->width != 0) {

        length = (temp + NXT_INT64_T_LEN) - p;
        end = buf + (spf->width - length);
        end = nxt_min(end, spf->end);

        while (buf < end) {
            *buf++ = spf->padding;
        }
    }

    /* Number copying. */

    length = (temp + NXT_INT64_T_LEN) - p;
    end = buf + length;
    end = nxt_min(end, spf->end);

    while (buf < end) {
        *buf++ = *p++;
    }

    return buf;
}


static u_char *
nxt_number(nxt_sprintf_t *spf, u_char *buf, double n)
{
    u_char  *p, *end;
    size_t  length;
    u_char  temp[NXT_DOUBLE_LEN];

    p = temp + NXT_DOUBLE_LEN;

    do {
        *(--p) = (u_char) (fmod(n, 10) + '0');
        n = trunc(n / 10);
    } while (!nxt_double_is_zero(n));

    /* Zero or space padding. */

    if (spf->width != 0) {
        length = (temp + NXT_DOUBLE_LEN) - p;
        end = buf + (spf->width - length);
        end = nxt_min(end, spf->end);

        while (buf < end) {
            *buf++ = spf->padding;
        }
    }

    /* Number copying. */

    length = (temp + NXT_DOUBLE_LEN) - p;

    end = buf + length;
    end = nxt_min(end, spf->end);

    while (buf < end) {
        *buf++ = *p++;
    }

    return buf;
}
