/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│vi: set net ft=c ts=8 sts=2 sw=2 fenc=utf-8                                :vi│
╚══════════════════════════════════════════════════════════════════════════════╝
│ @author (c) Marco Paland (info@paland.com)                                   │
│             2014-2019, PALANDesign Hannover, Germany                         │
│                                                                              │
│ @license The MIT License (MIT)                                               │
│                                                                              │
│ Permission is hereby granted, free of charge, to any person obtaining a copy │
│ of this software and associated documentation files (the "Software"), to deal│
│ in the Software without restriction, including without limitation the rights │
│ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell    │
│ copies of the Software, and to permit persons to whom the Software is        │
│ furnished to do so, subject to the following conditions:                     │
│                                                                              │
│ The above copyright notice and this permission notice shall be included in   │
│ all copies or substantial portions of the Software.                          │
│                                                                              │
│ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR   │
│ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,     │
│ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE  │
│ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER       │
│ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,│
│ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN    │
│ THE SOFTWARE.                                                                │
│                                                                              │
│ @brief Tiny printf, sprintf and (v)snprintf implementation, optimized for    │
│        embedded systems with a very limited resources. These routines are    │
│        thread safe and reentrant! Use this instead of the bloated            │
│        standard/newlib printf cause these use malloc for printf (and may not │
│        be thread safe).                                                      │
│                                                                              │
│ @brief Modified by Justine Tunney to support three different types of        │
│        UNICODE, 128-bit arithmetic, binary conversion, string escaping,      │
│        AVX2 character scanning, and possibly a tinier footprint too, so      │
│        long as extremely wild linker hacks aren't considered cheating.       │
└─────────────────────────────────────────────────────────────────────────────*/
#include "libc/assert.h"
#include "libc/bits/bits.h"
#include "libc/bits/weaken.h"
#include "libc/conv/conv.h"
#include "libc/fmt/fmt.h"
#include "libc/fmt/paland.inc"
#include "libc/fmt/palandprintf.h"
#include "libc/mem/mem.h"
#include "libc/runtime/internal.h"
#include "libc/str/str.h"
#include "libc/sysv/errfuns.h"

static int ppatoi(const char **str) {
  int i;
  for (i = 0; '0' <= **str && **str <= '9'; ++*str) {
    i *= 10;
    i += **str - '0';
  }
  return i;
}

/**
 * Implements {,v}{,s{,n},{,{,x}as},f,d}printf domain-specific language.
 *
 * Type Specifiers
 *
 *   - `%s`   char * (thompson-pike unicode)
 *   - `%ls`  wchar_t * (32-bit unicode → thompson-pike unicode)
 *   - `%hs`  char16_t * (16-bit unicode → thompson-pike unicode)
 *   - `%b`   int (radix 2 binary)
 *   - `%o`   int (radix 8 octal)
 *   - `%d`   int (radix 10 decimal)
 *   - `%x`   int (radix 16 hexadecimal)
 *   - `%X`   int (radix 16 hexadecimal uppercase)
 *   - `%u`   unsigned
 *   - `%f`   double
 *   - `%Lf`  long double
 *   - `%p`   pointer (48-bit hexadecimal)
 *
 * Size Modifiers
 *
 *   - `%hhd` char (8-bit)
 *   - `%hd`  short (16-bit)
 *   - `%ld`  long (64-bit)
 *   - `%lu`  unsigned long (64-bit)
 *   - `%lx`  unsigned long (64-bit hexadecimal)
 *   - `%jd`  intmax_t (128-bit)
 *
 * Width Modifiers
 *
 *   - `%08d` fixed columns w/ zero leftpadding
 *   - `%8d`  fixed columns w/ space leftpadding
 *   - `%*s`  variable column string (thompson-pike)
 *
 * Precision Modifiers
 *
 *   - `%.8s`  supplied byte length (obeys nul terminator)
 *   - `%.*s`  supplied byte length argument (obeys nul terminator)
 *   - `%`.*s` supplied byte length argument c escaped (ignores nul terminator)
 *   - `%#.*s` supplied byte length argument visualized (ignores nul terminator)
 *   - `%.*hs` supplied char16_t length argument (obeys nul terminator)
 *   - `%.*ls` supplied wchar_t length argument (obeys nul terminator)
 *
 * Formatting Modifiers
 *
 *   - `%,d`  thousands separators
 *   - `%'s`  escaped c string literal
 *   - `%`c`  c escaped character
 *   - `%`'c` c escaped character quoted
 *   - `%`s`  c escaped string
 *   - `%`'s` c escaped string quoted
 *   - `%`s`  escaped double quoted c string literal
 *   - `%`c`  escaped double quoted c character literal
 *   - `%+d`  plus leftpad if positive (aligns w/ negatives)
 *   - `% d`  space leftpad if positive (aligns w/ negatives)
 *   - `%#s`  datum (radix 256 null-terminated ibm cp437)
 *   - `%#x`  int (radix 16 hexadecimal w/ 0x prefix if not zero)
 *
 * @note implementation detail of printf(), snprintf(), etc.
 * @see printf() for wordier documentation
 */
hidden int palandprintf(void *fn, void *arg, const char *format, va_list va) {
  void *p;
  char qchar;
  long double ldbl;
  wchar_t charbuf[1];
  const char *alphabet;
  int (*out)(long, void *);
  unsigned char signbit, log2base;
  int w, flags, width, lasterr, precision;

  lasterr = errno;
  out = fn ? fn : (int (*)(int, void *))missingno;

  while (*format) {
    /* %[flags][width][.precision][length] */
    if (*format != '%') {
      /* no */
      if (out(*format, arg) == -1) return -1;
      format++;
      continue;
    } else {
      /* yes, evaluate it */
      format++;
    }

    /* evaluate flags */
    flags = 0;
  getflag:
    switch (*format++) {
      case '0':
        flags |= FLAGS_ZEROPAD;
        goto getflag;
      case '-':
        flags |= FLAGS_LEFT;
        goto getflag;
      case '+':
        flags |= FLAGS_PLUS;
        goto getflag;
      case ' ':
        flags |= FLAGS_SPACE;
        goto getflag;
      case '#':
        flags |= FLAGS_HASH;
        goto getflag;
      case ',':
        flags |= FLAGS_GROUPING;
        goto getflag;
      case '`':
        flags |= FLAGS_REPR;
        /* fallthrough */
      case '\'':
        flags |= FLAGS_QUOTE;
        goto getflag;
      default:
        format--;
        break;
    }

    /* evaluate width field */
    width = 0;
    if (isdigit(*format)) {
      width = ppatoi(&format);
    } else if (*format == '*') {
      w = va_arg(va, int);
      if (w < 0) {
        flags |= FLAGS_LEFT; /* reverse padding */
        width = -w;
      } else {
        width = w;
      }
      format++;
    }

    /* evaluate precision field */
    precision = 0;
    if (*format == '.') {
      flags |= FLAGS_PRECISION;
      format++;
      if (isdigit(*format)) {
        precision = ppatoi(&format);
      } else if (*format == '*') {
        precision = va_arg(va, int);
        format++;
      }
    }
    if (precision < 0) {
      precision = 0;
    }

    /* evaluate length field */
    signbit = 31;
    switch (*format) {
      case 'j': /* intmax_t */
        format++;
        signbit = sizeof(intmax_t) * 8 - 1;
        break;
      case 'l':
        if (format[1] == 'l') format++;
        /* fallthrough */
      case 't': /* ptrdiff_t */
      case 'z': /* size_t */
      case 'Z': /* size_t */
      case 'L': /* long double */
        format++;
        signbit = 63;
        break;
      case 'h':
        format++;
        if (*format == 'h') {
          format++;
          signbit = 7;
        } else {
          signbit = 15;
        }
        break;
      default:
        break;
    }

    /* evaluate specifier */
    alphabet = "0123456789abcdef";
    log2base = 0;
    qchar = '"';
    switch (*format++) {
      case 'p':
        flags |= FLAGS_ZEROPAD;
        width = POINTER_XDIGITS;
        log2base = 4;
        signbit = 47;
        goto DoNumber;
      case 'X':
        alphabet = "0123456789ABCDEF";
        /* fallthrough */
      case 'x':
        log2base = 4;
        goto DoNumber;
      case 'b':
        log2base = 1;
        goto DoNumber;
      case 'o':
        log2base = 3;
        goto DoNumber;
      case 'd':
      case 'i':
        flags |= FLAGS_ISSIGNED;
        /* fallthrough */
      case 'u': {
        flags &= ~FLAGS_HASH; /* no hash for dec format */
      DoNumber:
        if (!weaken(ntoa) ||
            weaken(ntoa)(out, arg, va, signbit, log2base, precision, width,
                         flags, alphabet) == -1) {
          return -1;
        }
        break;
      }

      case 'f':
      case 'F':
        if (signbit == 63) {
          ldbl = va_arg(va, long double);
        } else {
          ldbl = va_arg(va, double);
        }
        if (!weaken(ftoa) ||
            weaken(ftoa)(out, arg, ldbl, precision, width, flags) == -1) {
          return -1;
        }
        break;

      case 'c':
        precision = 1;
        flags |= FLAGS_PRECISION;
        qchar = '\'';
        p = charbuf;
        charbuf[0] = va_arg(va, int); /* assume little endian */
        goto showstr;

      case 'm':
        p = weaken(strerror) ? weaken(strerror)(lasterr) : "?";
        signbit = 0;
        goto showstr;

      case 'r':
        flags |= FLAGS_REPR;
        /* fallthrough */

      case 'q':
        flags |= FLAGS_QUOTE;
        /* fallthrough */

      case 's':
        p = va_arg(va, void *);
      showstr:
        if (!weaken(stoa) || weaken(stoa)(out, arg, p, flags, precision, width,
                                          signbit, qchar) == -1) {
          return -1;
        }
        break;

      case '%':
        if (out('%', arg) == -1) return -1;
        break;

      default:
        if (out(format[-1], arg) == -1) return -1;
        break;
    }
  }
  return 0;
}
