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


#include <njs_main.h>


/*
 * Supported formats:
 *
 *    %[0][width][x][X]O        njs_off_t
 *    %[0][width]T              njs_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    njs_int_t/njs_uint_t
 *    %[0][width][u][x|X]D      int32_t/uint32_t
 *    %[0][width][u][x|X]L      int64_t/uint64_t
 *    %[0][width][.width]f      double, max valid number fits to %18.15f
 *
 *    %d                        int
 *
 *    %s                        null-terminated string
 *    %*s                       length and string
 *
 *    %p                        void *
 *    %b                        njs_bool_t
 *    %V                        njs_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 *
njs_sprintf(u_char *buf, u_char *end, const char *fmt, ...)
{
    u_char   *p;
    va_list  args;

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

    return p;
}


/*
 * njs_sprintf_t is used:
 *    to pass several parameters of njs_integer() via single pointer
 *    and to store little used variables of njs_vsprintf().
 */

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


static u_char *njs_integer(njs_sprintf_t *spf, u_char *buf, uint64_t ui64);
static u_char *njs_float(njs_sprintf_t *spf, u_char *buf, double n);


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


u_char *
njs_vsprintf(u_char *buf, u_char *end, const char *fmt, va_list args)
{
    u_char         *p;
    int            d;
    double         f, i;
    size_t         size, length;
    int64_t        i64;
    uint64_t       ui64, frac;
    njs_str_t      *v;
    njs_uint_t     scale, n;
    njs_bool_t     sign;
    njs_sprintf_t  spf;

    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, njs_str_t *);

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

            continue;

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

            if (njs_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 (njs_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 'O':
            i64 = (int64_t) va_arg(args, njs_off_t);
            sign = 1;
            goto number;

        case 'T':
            i64 = (int64_t) va_arg(args, njs_time_t);
            sign = 1;
            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, njs_int_t);
            } else {
                ui64 = (uint64_t) va_arg(args, njs_uint_t);
            }

            if (spf.max_width != 0) {
                spf.width = NJS_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 'b':
            ui64 = (uint64_t) va_arg(args, njs_bool_t);
            sign = 0;
            goto number;

        case 'f':
            fmt++;

            f = va_arg(args, double);

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

            if (njs_slow_path(isnan(f))) {
                p = (u_char *) nan;
                length = njs_length(nan);

                goto copy;

            } else if (njs_slow_path(isinf(f))) {
                p = (u_char *) infinity;
                length = njs_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 = njs_float(&spf, buf, i);

            if (spf.frac_width > 0) {

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

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

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

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

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

            continue;

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

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

            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 = njs_integer(&spf, buf, ui64);

        fmt++;
        continue;

    copy:

        size = njs_min((size_t) (end - buf), length);

        if (size != 0) {
            buf = njs_cpymem(buf, p, size);
        }

        continue;
    }

    return buf;
}


static u_char *
njs_integer(njs_sprintf_t *spf, u_char *buf, uint64_t ui64)
{
    u_char  *p, *end;
    size_t  length;
    u_char  temp[NJS_INT64_T_LEN];

    p = temp + NJS_INT64_T_LEN;

    if (spf->hex == NULL) {

#if (NJS_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  /* NJS_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 + NJS_INT64_T_LEN) - p;
        end = buf + (spf->width - length);
        end = njs_min(end, spf->end);

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

    /* Number copying. */

    length = (temp + NJS_INT64_T_LEN) - p;
    end = buf + length;
    end = njs_min(end, spf->end);

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

    return buf;
}


static u_char *
njs_float(njs_sprintf_t *spf, u_char *buf, double n)
{
    u_char  *p, *end;
    size_t  length;
    u_char  temp[NJS_DOUBLE_LEN];

    p = temp + NJS_DOUBLE_LEN;

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

    /* Zero or space padding. */

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

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

    /* Number copying. */

    length = (temp + NJS_DOUBLE_LEN) - p;

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

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

    return buf;
}


NJS_EXPORT
int njs_dprint(int fd, u_char *buf, size_t size)
{
    return write(fd, buf, size);
}


int
njs_dprintf(int fd, const char *fmt, ...)
{
    size_t   size;
    u_char   text[2048], *p;
    va_list  args;

    va_start(args, fmt);
    p = njs_vsprintf(text, text + sizeof(text), fmt, args);
    va_end(args);

    size = p - text;

    return write(fd, text, size);
}
