/*
 * snprintf() and vsnprintf() functions for the Fast Light Tool Kit (FLTK).
 *
 * Copyright 1998-2010 by Bill Spitzak and others.
 *
 * This library is free software. Distribution and use rights are outlined in
 * the file "COPYING" which should have been included with this file.  If this
 * file is missing or damaged, see the license at:
 *
 *     https://www.fltk.org/COPYING.php
 *
 * Please see the following page on how to report bugs and issues:
 *
 *     https://www.fltk.org/bugs.php
 */

#include <stdio.h>
#include "flstring.h"

#ifdef HAVE_SYS_STDTYPES_H
#  include <sys/stdtypes.h>
#endif /* HAVE_SYS_STDTYPES_H */

#ifdef __cplusplus
extern "C" {
#endif

/**
  \file vsnprintf.c
  \brief  Portable vsnprintf() implementation.
*/

/**
  FLTK's platform independent wrapper for the vsnprintf() C library function.

  This function guarantees:

  - access to vsnprintf(), even on systems that don't have it (FLTK's own
    built-in code is used)

  - Guarantees NUL termination. Even if string expands larger than the buffer,
    a terminating NUL is included, unlike some implementations of vsnprintf(),
    notably Microsoft Visual Studio (pre-2015), which can leave the string
    unterminated when truncated.

  If the build environment for FLTK has vsnprintf(), fl_vsnprintf()
  is just a wrapper around the compiler's provided function. Otherwise,
  if the function is NOT available, FLTK's own built-in version is provided.

  The FLTK built in provides these style options:

  - %[ -+#']
  - *  -- padding width
  - .* -- precision width
  - Data types: h, l, ll, L
  - Floating point formats: E, G, e, f, g
  - Integer formats: B, X, b, d, i, o, u, x
  - Pointer format: p
  - String/char: c, s, n
*/
int fl_vsnprintf(char* buffer, size_t bufsize, const char* format, va_list ap) {
#if defined(HAVE_VSNPRINTF) && defined(__linux__)
  return vsnprintf(buffer, bufsize, format, ap);
#else
  char          *bufptr,                /* Pointer to position in buffer */
                *bufend,                /* Pointer to end of buffer */
                sign,                   /* Sign of format width */
                size,                   /* Size character (h, l, L) */
                type;                   /* Format type character */
  int           width,                  /* Width of field */
                prec;                   /* Number of characters of precision */
  char          tformat[100],           /* Temporary format string for sprintf() */
                *tptr,                  /* Pointer into temporary format */
                temp[1024];             /* Buffer for formatted numbers */
  char          *s;                     /* Pointer to string */
  int           slen;                   /* Length of string */
  int           bytes;                  /* Total number of bytes needed */


 /*
  * Loop through the format string, formatting as needed...
  */

  bufptr = buffer;
  bufend = buffer + bufsize - 1;
  bytes  = 0;

  while (*format) {
    if (*format == '%') {
      tptr = tformat;
      *tptr++ = *format++;

      if (*format == '%') {
        if (bufptr && bufptr < bufend) *bufptr++ = *format;
        bytes ++;
        format ++;
        continue;
      } else if (strchr(" -+#\'", *format)) {
        *tptr++ = *format;
        sign = *format++;
      } else sign = 0;

      if (*format == '*') {
        /* Get width from argument... */
        format ++;
        width = va_arg(ap, int);
        snprintf(tptr, sizeof(tformat) - (tptr - tformat), "%d", width);
        tptr += strlen(tptr);
      } else {
        width = 0;
        while (isdigit(*format & 255)) {
          if (tptr < (tformat + sizeof(tformat) - 1)) *tptr++ = *format;
          width = width * 10 + *format++ - '0';
        }
      }

      if (*format == '.') {
        if (tptr < (tformat + sizeof(tformat) - 1)) *tptr++ = *format;
        format ++;

        if (*format == '*') {
          /* Get precision from argument... */
          format ++;
          prec = va_arg(ap, int);
          snprintf(tptr, sizeof(tformat) - (tptr - tformat), "%d", prec);
          tptr += strlen(tptr);
        } else {
          prec = 0;
          while (isdigit(*format & 255)) {
            if (tptr < (tformat + sizeof(tformat) - 1)) *tptr++ = *format;
            prec = prec * 10 + *format++ - '0';
          }
        }
      } else prec = -1;

      size = '\0';

      if (*format == 'l' && format[1] == 'l') {
        size = 'L';
        if (tptr < (tformat + sizeof(tformat) - 2)) {
          *tptr++ = 'l';
          *tptr++ = 'l';
        }
        format += 2;
      } else if (*format == 'h' || *format == 'l' || *format == 'L') {
        if (tptr < (tformat + sizeof(tformat) - 1)) *tptr++ = *format;
        size = *format++;
      }

      if (!*format) break;

      if (tptr < (tformat + sizeof(tformat) - 1)) *tptr++ = *format;
      type  = *format++;
      *tptr = '\0';

      switch (type) {
        case 'E' : /* Floating point formats */
        case 'G' :
        case 'e' :
        case 'f' :
        case 'g' :
          if ((width + 2) > (int)sizeof(temp)) break;

          sprintf(temp, tformat, va_arg(ap, double));

          bytes += (int) strlen(temp);

          if (bufptr) {
            if ((bufptr + strlen(temp)) > bufend) {
              strncpy(bufptr, temp, (size_t)(bufend - bufptr));
              bufptr = bufend;
            } else {
              strcpy(bufptr, temp);
              bufptr += strlen(temp);
            }
          }
          break;

        case 'B' : /* Integer formats */
        case 'X' :
        case 'b' :
        case 'd' :
        case 'i' :
        case 'o' :
        case 'u' :
        case 'x' :
          if ((width + 2) > (int)sizeof(temp)) break;

#ifdef HAVE_LONG_LONG
          if (size == 'L')
            sprintf(temp, tformat, va_arg(ap, long long));
          else
#endif /* HAVE_LONG_LONG */
          if (size == 'l')
            sprintf(temp, tformat, va_arg(ap, long));
          else
            sprintf(temp, tformat, va_arg(ap, int));

          bytes += (int) strlen(temp);

          if (bufptr) {
            if ((bufptr + strlen(temp)) > bufend) {
              strncpy(bufptr, temp, (size_t)(bufend - bufptr));
              bufptr = bufend;
            } else {
              strcpy(bufptr, temp);
              bufptr += strlen(temp);
            }
          }
          break;

        case 'p' : /* Pointer value */
          if ((width + 2) > (int)sizeof(temp)) break;

          sprintf(temp, tformat, va_arg(ap, void *));

          bytes += (int) strlen(temp);

          if (bufptr) {
            if ((bufptr + strlen(temp)) > bufend) {
              strncpy(bufptr, temp, (size_t)(bufend - bufptr));
              bufptr = bufend;
            } else {
              strcpy(bufptr, temp);
              bufptr += strlen(temp);
            }
          }
          break;

        case 'c' : /* Character or character array */
          bytes += width;

          if (bufptr) {
            if (width <= 1) *bufptr++ = va_arg(ap, int);
            else {
              if ((bufptr + width) > bufend) width = (int) (bufend - bufptr);

              memcpy(bufptr, va_arg(ap, char *), (size_t)width);
              bufptr += width;
            }
          }
          break;

        case 's' : /* String */
          if ((s = va_arg(ap, char *)) == NULL) s = "(null)";

          slen = (int) strlen(s);
          if (slen > width && prec != width) width = slen;

          bytes += width;

          if (bufptr) {
            if ((bufptr + width) > bufend) width = (int) (bufend - bufptr);

            if (slen > width) slen = width;

            if (sign == '-') {
              strncpy(bufptr, s, (size_t)slen);
              memset(bufptr + slen, ' ', (size_t)(width - slen));
            } else {
              memset(bufptr, ' ', (size_t)(width - slen));
              strncpy(bufptr + width - slen, s, (size_t)slen);
            }

            bufptr += width;
          }
          break;

        case 'n' : /* Output number of chars so far */
          *(va_arg(ap, int *)) = bytes;
          break;
      }
    } else {
      bytes ++;

      if (bufptr && bufptr < bufend) *bufptr++ = *format;
      format ++;
    }
  }

 /*
  * Nul-terminate the string and return the number of characters needed.
  */

  if (bufptr) *bufptr = '\0';

  return (bytes);
#endif /* HAVE_VSNPRINTF */
}

int fl_snprintf(char* str, size_t size, const char* fmt, ...) {
  int ret;
  va_list ap;
  va_start(ap, fmt);
  ret = vsnprintf(str, size, fmt, ap);
  va_end(ap);
  return ret;
}

#ifdef __cplusplus
}
#endif
