/*                                              -*- mode:C++; fill-column:100 -*-
  SFBPrintf.cpp - Cheapo crappy printf-like routines
  Copyright (C) 2009 The Regents of the University of New Mexico.  All rights reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
  USA
*/

/* TO COMPILE FOR TESTING:
  g++ -O0 -g3 -Wall -pedantic -Werror -Wundef    -I../../../src/include  -DHOST_MODE -DTEST_PRINTF_SCANF -o"./testsfbprintf" SFBPrintf.cpp SFBPrint.cpp SFBPacket.cpp ../host/SFBHWMiscHost.cpp;./testsfbprintf
*/

#include "SFBPrintf.h"
#include "SFBHWMisc.h"     /* For facePrintTimestamp */
#include "SFBAssert.h"
#include "SFBSerial.h"     /* For Faces */

u8 loggingLevel IN_BODYRAM = LOGLEVEL_NORMAL;
u8 loggingFace IN_BODYRAM = ALL_FACES;

void setLogLevel(int n) {
  loggingLevel = n;
}
int getLogLevel() {
  return loggingLevel;
}

void setLogFace(u8 face) {
  API_ASSERT_VALID_EXTENDED_FACE(face);
  loggingFace = face;
}
u8 getLogFace() {
  return loggingFace;
}

void logNormal(const char * format, ...) {
  va_list ap;
  va_start(ap,format);
  vfaceLogf(1,LOGLEVEL_NORMAL, loggingFace, format, ap);
}
void logQuiet(const char * format, ...) {
  va_list ap;
  va_start(ap,format);
  vfaceLogf(1,LOGLEVEL_QUIET, loggingFace, format, ap);
}
void logVerbose(const char * format, ...) {
  va_list ap;
  va_start(ap,format);
  vfaceLogf(1,LOGLEVEL_VERBOSE, loggingFace, format, ap);
}
void logDebug(const char * format, ...) {
  va_list ap;
  va_start(ap,format);
  vfaceLogf(1,LOGLEVEL_DEBUG, loggingFace, format, ap);
}

void msgNormal(const char * format, ...) {
  va_list ap;
  va_start(ap,format);
  vfaceLogf(0,LOGLEVEL_NORMAL, loggingFace, format, ap);
}
void msgQuiet(const char * format, ...) {
  va_list ap;
  va_start(ap,format);
  vfaceLogf(0,LOGLEVEL_QUIET, loggingFace, format, ap);
}
void msgVerbose(const char * format, ...) {
  va_list ap;
  va_start(ap,format);
  vfaceLogf(0,LOGLEVEL_VERBOSE, loggingFace, format, ap);
}
void msgDebug(const char * format, ...) {
  va_list ap;
  va_start(ap,format);
  vfaceLogf(0,LOGLEVEL_DEBUG, loggingFace, format, ap);
}

void faceLogf(int level, u8 face, const char * format, ...) {
  va_list ap;
  va_start(ap,format);
  vfaceLogf(1,level,face,format,ap);
}

void msgf(int level, u8 face, const char * format, ...) {
  va_list ap;
  va_start(ap,format);
  vfaceLogf(0,level,face,format,ap);
}

void vfaceLogf(int includeTimestamp, int level, u8 face, const char * format, va_list & ap) {
  if (level > getLogLevel())
    return;
  facePrint(face,"L");
  if (level > LOGLEVEL_DEBUG)
    facePrint(face,level,DEC);
  else
    facePrint(face,"_qnvd"[level],BYTE);
  facePrint(face," ");

  if (includeTimestamp) {
    facePrintTimestamp(face);
    facePrint(face," ");
  }
  vfacePrintf(face,format,ap);
}

void pprintf(const char * format, ...) {
  va_list ap;
  va_start(ap,format);
  vfacePrintf(ALL_FACES,format,ap);
}

static bool isPrint(u32 ch) { return ch >= ' ' && ch <= '~';  }


/**
 Formatted printing to the specified face.  

 \param face the face code to which the printing shall be directed.  This may be a "physical face"
             code like #WEST, an "extended face" code like #BRAIN or #ALL_FACES or #NO_FACES, or a
             sketch-defined "virtual face" code (such as produced by #faceFindFreeFace() and
             #faceSetPrinter()).

 \param format a null-terminated string controlling what is to be printed; see below.

 \param ... a variable sequence of arguments of sufficient number and appropriate types to
            supply to all of the "% codes" in \a format with the data they need.

 #facePrintf() performs simple formatted printing, in the same general style as the C language \c
 printf functions, but with many restrictions -- for example, there is only limited support for
 'field widths'.  There are also a few SFB-specific extensions.  This documentation presumes
 familiarity with the use of \c printf-like functions in general, and only documents the specifics
 of the SFB format codes.

 Quick format reference.  The following 'percent escape' codes are recognized:
 - \%c: Print 8 bit character into exactly one output byte.  
 - \%h: Print 16 bit value into exactly two output bytes in 'big endian' network order.
 - \%l: Print 32 bit value into exactly four output bytes in 'big endian' network order.
 - \%b: Print 32 bit value in base 2 (using only the characters '0' and '1').
 - \%o: Print 32 bit value in base 8 (using only the characters '0' through '7').
 - \%d: Print 32 bit value in base 10 (using only the characters '0' through '9').
 - \%x: Print 32 bit value in base 16 (using only the characters '0' through '9' and 'A' through 'F' for 10 to 15).
 - \%t: Print 32 bit value in base 36 (using only the characters '0' through '9' and 'A' through 'Z' for 10 to 35).
 - \%F: Print the 'face code' (e.g., 'N' for North) in one output byte
 - \%f: Print double value in floating point with two decimal places.
 - \%s: Print null-terminated string given a (const char *) pointer to the first byte
 - \%p: Print a packet given a (const u8 *) pointer to the packet
 - \%\%: Print a percent sign
 - \%\\n: Print a checkbyte and then a newline (as by #printlnCheckByte).

The '#' modifier character may be added to certain conversions to achieve special purposes, as follows: 
 - For the \%d code, '#' causes the argument to be printed as an unsigned value rather than signed.
 - For the \%b, \%o, \%x, and \%t codes, '#' causes the argument to be printed as a signed value
   rather than unsigned.
 - For the \%c code, '#' causes a 'non-printing' char arg to be printed as four bytes -- a '[', two
   hex digits, and a ']' -- rather than printing the char value directly.
 - For the \%p code, '#' causes any 'non-printing' bytes (or '\%' bytes) of the packet to be printed as
   three bytes each -- a '\%', followed by two hex digits -- rather than printing the byte values directly.

Since 0.9.7, there is limited support for field widths (a.k.a. padding and filling), with the
following restrictions:
 - Padding is supported only on the formatted integer conversions --\%b, \%o, \%d, \%x, and \%t,
 - Only padding on the left is supported (so, only 'right-justified columns').
 - Padding is always either spaces or zeros; spaces are used unless the field width begins with a
   '0'.

 XXX Still need formatting details; examples.

 \blinks #E_API_NULL_POINTER if \a format is null.

 \blinks #E_API_BAD_FACE if \a face is not a valid (physical, extended, or defined virtual) face.

 \blinks #E_API_BAD_FORMAT_CODE if an unrecognized "%" code is encountered in \a format.  (Also
                                occurs if a single "%" is the last character in \a format.)

\since 0.9.7 for field widths

 */
void facePrintf(u8 face, const char * format, ...) {
  va_list ap;
  va_start(ap,format);
  vfacePrintf(face,format,ap);
}

/**
 Formatted printing to the specified face, through a supplied \c va_list.  See #facePrintf(u8 face,
 const char * format, ...) for details of arguments \a face and \a format.
*/
void vfacePrintf(u8 face, const char * format, va_list & ap) {
  API_ASSERT_VALID_EXTENDED_FACE(face);
  API_ASSERT_NONNULL(format);

  char p;
  int type;
  int width;
  bool zerofill;
  bool alt;
  while ((p = *format++)) {
    if (p != '%') {
      if (p == '\n')            // '\n's _in_the_format_string_ are 
        facePrintln(face);            // treated as packet delimiters!
      else
        facePrint(face,p,BYTE);
      continue;
    }
    
    alt = false;
    width = 0;
    zerofill = false;
  again:
    switch (p = *format++) {
    case '#': alt = true; goto again;
    case '*': 
      if (width > 0) goto badformat; // Allow %*d or %0*d but not %1*d or %**d (usu.)
      width = va_arg(ap,int); 
      goto again;
    case '0': 
      if (width == 0) zerofill = true;
      /* FALL THROUGH */
    case '1': case '2': case '3': case '4': 
    case '5': case '6': case '7': case '8': case '9':
      width = 10*width + (p-'0');
      goto again;
    case 'c': 
      {
        u32 ch = va_arg(ap,int); 
        if (!alt || isPrint(ch)) facePrint(face,ch,BYTE);
        else 
          facePrintf(face,"[%02x]",ch); // Argh, burning the stack..
      }
      break;
    case 'h': type = BESHORT; goto print;
    case 'l': type = BELONG; goto print;

    case 'b': type = BIN; goto print;
    case 'o': type = OCT; goto print;
    case 'd': type = DEC; alt = !alt; goto print; // %d is signed, %#d unsigned
    case 'x': type = HEX; goto print;
    case 't': type = B36; goto print;

    print:
      facePrint(face,va_arg(ap,u32),type,alt,width,zerofill); 
      break;

    case 'F':
      facePrintFace(face);
      break;

    case 'f': {
      double v =  va_arg(ap,double);
      facePrint(face,v);
      break;
    }

    case 's': { 
      const char * s = va_arg(ap,const char *);
      if (!s) facePrint(face,"(null)");
      else facePrint(face,s); 
      break;
    }

    case 'P':  
      facePrintPacket(face,va_arg(ap,const u8 *));
      break;

    case 'p': { 
      const u8 * p = va_arg(ap,const u8 *);
      if (!validPacket(p)) facePrint(face,"(invalidp)");
      else if (!alt) facePrint(face,p,packetLength(p)); 
      else {
        for (u32 i = 0; i < packetLength(p); ++i) {
          u8 ch = p[i];
          if (isPrint(ch) && ch != '%')
            facePrint(face,ch,BYTE);
          else
            facePrintf(face,"%%%02x",ch);
        }
      }
      break;
    }

    case '%': 
      facePrint(face,p,BYTE); 
      break;

    case '\n':                  // "%\n" adds checkbyte then terminates packet!
      facePrintlnCheckByte(face); 
      break;

    badformat:
    default:                          // Either I don't know that code, or you're bogus.
      API_BUG(E_API_BAD_FORMAT_CODE); // Either way, I die.  You're welcome.
    }
  }
}

static u32 * getPtr4(va_list & ap) {
  u32 * ptr = va_arg(ap,u32*);
  API_ASSERT_NONNULL(ptr);
  API_ASSERT_WORD_ALIGNED(ptr);
  return ptr;
}

static u16 * getPtr2(va_list & ap) {
  u16 * ptr = va_arg(ap,u16*);
  API_ASSERT_NONNULL(ptr);
  API_ASSERT_HALFWORD_ALIGNED(ptr);
  return ptr;
}

static u8 * getPtr1(va_list & ap) {
  u8 * ptr = va_arg(ap,u8*);
  API_ASSERT_NONNULL(ptr);
  return ptr;
}

u32 packetScanf(u8 * packet, const char * format, ...) {
  va_list ap;
  va_start(ap,format);
  return vPacketScanf(packet, format, ap);
}

u32 vPacketScanf(u8 * packet, const char * format, va_list & ap) {
  API_ASSERT_VALID_PACKET(packet);
  API_ASSERT_NONNULL(format);

  char p;
  u32 result;

  bool alt;
  u32 matches = 0;
  int width;
  while ((p = *format++)) {
    if (p != '%') {
      if (p == '\n') {            // '\n's _in_the_format_string_: We have to see EOF
        if (packetReadEOF(packet)) {
          ++matches;
          continue;               // ..but only end of format string now makes sense..
        } else return matches;
      }
      if (!packetRead(packet,result,BYTE))
        return matches;
      if ((char) result != p)
        return matches;
      ++matches;
      continue;
    }
    
    alt = false;
    width = 0;
  again:
    u32 type = 0;
    switch (p = *format++) {
    case '#': alt = true; goto again;
    case '*':
      if (width > 0) goto badformat; // Allow %*d or %0*d but not %1*d or %**d (usu.)
      width = va_arg(ap,int); 
      goto again;

    case '0': case '1': case '2': case '3': case '4': 
    case '5': case '6': case '7': case '8': case '9':
      width = 10*width + (p-'0');
      goto again;

    case 'c': 
      if (!packetRead(packet,result,BYTE)) 
        return matches; 
      *getPtr1(ap) = result;
      ++matches;
      break;

    case 'h': 
      if (!packetRead(packet,result,BESHORT)) 
        return matches; 
      *getPtr2(ap) = result;
      ++matches;
      break;

    case 'l': type = (u32) BELONG; goto store;
    case 'b': type = BIN; goto store;
    case 'o': type = OCT; goto store;
    case 'd': type = DEC; goto store;
    case 'x': type = HEX; goto store;
    case 't': type = B36; goto store;

    store:
      if (!packetRead(packet,result,type,width==0?MAX_PACKET_LENGTH:width)) 
        return matches; 
      *getPtr4(ap) = result;
      ++matches;
      break;

    case 'F':
      if (!packetRead(packet,result,BYTE)) return matches; 
      if (!IS_FACE_CODE(result)) return matches;
      *getPtr1(ap) = FACE_NUMBER_FROM_CODE(result);
      ++matches;
      break;

#if 0
    case 'f': {
      double v =  va_arg(ap,double);
      facePrint(face,v);
      break;
    }

    case 's': { 
      const char * s = va_arg(ap,const char *);
      if (!s) facePrint(face,"(null)");
      else facePrint(face,s); 
      break;
    }
#endif

    case 'P': { 
      u8 * subp = 0;
      if (!packetReadPacket(packet,subp)) return matches; 
      u8 ** ptr = va_arg(ap,u8**);
      API_ASSERT_NONNULL(ptr);
      *ptr = subp;
      ++matches;
      break;
    }

    case '%': 
      if (!packetRead(packet,result,BYTE)) return matches; 
      if (result != '%') return matches;
      ++matches;
      break;

    case '\n':                  // "%\n" adds checkbyte then terminates packet!
      if (!packetReadCheckByte(packet)) return matches; 
      ++matches;
      break;

    badformat:
    default:                          // Either I don't know that code, or you're bogus.
      API_BUG(E_API_BAD_FORMAT_CODE); // Either way, I die.  You're welcome.
    }
  }
  return matches;
}

#ifdef TEST_PRINTF_SCANF

#include <stdio.h>
#include <stdlib.h> // for exit, random
#include <string.h> // for strcmp

#define TEST(condition) do { if (!(condition)) die(__FILE__,__LINE__,#condition); } while (0)

void _apiError_(u32 code,const char * file, int lineno) { 
  fprintf(stderr,"APIERROR %d %s:%d\n",code,file,lineno);
  exit(code); 
}

void die(const char * file, int line, const char * msg) {
  fprintf(stderr,"%s:%d: %s\n",file, line, msg);
  exit(100);
}

static u8 testBuf[1000];
static u8 testFace;
static u8 * testPacket;

static void initPrinter() {
  testPacket = makePacketPrinter(testFace,testBuf,1000,NORTH);
  TEST(testPacket!=0);
}

static void test1() {
  facePrintf(testFace,"hello world\n");
  TEST(strcmp((const char*) testPacket,"hello world")==0);
}

#define TESTFMT(fmt,arg) do {    \
  char buf[1000];                \
  sprintf(buf,fmt,arg);          \
  facePrintf(testFace,fmt,arg);  \
  facePrintln(testFace);         \
  TEST(strcasecmp((const char*) testPacket,buf)==0); \
} while (0)

static void test2() {
  TESTFMT("hello %d world",1234);
  TESTFMT("hello %o world",01234);
  TESTFMT("hello %x world",0x1234);
  TESTFMT("hello %s world","0x1234");
  TESTFMT("hello %s world"," d d d ");
  TESTFMT("hello %s world"," \1 \2 \3 ");
  TESTFMT("hello %s world","");

  facePrintf(testFace,"hello %b world\n",1234);
  TEST(strcmp((const char*) testPacket,"hello 10011010010 world")==0);

  for (u32 width = 0; width < 30; ++width) {
    for (s32 val = -18; val < 150; val += 9) {
      char fmt[100];
      sprintf(fmt,"(%%%dd)",width);
      TESTFMT(fmt,val);

      sprintf(fmt,"(%%%do)",width);
      TESTFMT(fmt,val);
      
      sprintf(fmt,"(%%%dx)",width);
      TESTFMT(fmt,val);
    }
  }      
}

#define TESTRES(fmt,arg,res) do {\
  facePrintf(testFace,fmt,arg);  \
  facePrintln(testFace);         \
  TEST(strcasecmp((const char*) testPacket,res)==0); \
} while (0)


#define TESTRES2(fmt,arg,a2,res) do {             \
  facePrintf(testFace,fmt,arg,a2);                \
  facePrintln(testFace);         \
  TEST(strcasecmp((const char*) testPacket,res)==0); \
} while (0)


static void test3() {
  TESTRES("foo%cbar",0x10,"foo\20bar");
  TESTRES("foo%hbar",0x0102,"foo\1\2bar");
  TESTRES("foo%lbar",0x04050607,"foo\4\5\6\7bar");

  TESTRES("foo%fbar",1.23,"foo1.23bar");
  TESTRES("foo%fbar",12.3,"foo12.30bar");
  TESTRES("foo%fbar",123.0,"foo123.00bar");

  TESTRES("foo%Fbar",0,"fooNbar");
  TESTRES("foo%Fbar",1,"fooNbar");
  TESTRES("foo%Fbar",2,"fooNbar");

  TESTRES("foo%tbar",0x7fffffff,"fooZIK0ZJbar");
  TESTRES("foo%tbar",-1,"foo1Z141Z3bar");
}

#define TESTSCAN1(fmt,arg,count) do {  \
  u8 a1;                               \
  facePrintf(testFace,fmt,arg);        \
  facePrintln(testFace);               \
  u8 ct = packetScanf(testPacket,fmt,&a1); \
  TEST(count==ct);                     \
  TEST(a1 == arg);                     \
} while (0)
#define TESTSCAN2(fmt,arg,count) do {  \
  u16 a1;                              \
  facePrintf(testFace,fmt,arg);        \
  facePrintln(testFace);               \
  u8 ct = packetScanf(testPacket,fmt,&a1); \
  TEST(count==ct);                     \
  TEST(a1 == arg);                     \
} while (0)
#define TESTSCAN4(fmt,arg,count) do {  \
  s32 a1;                              \
  facePrintf(testFace,fmt,arg);        \
  facePrintln(testFace);               \
  u8 ct = packetScanf(testPacket,fmt,&a1); \
  TEST(count==ct);                     \
  TEST(a1 == arg);                     \
} while (0)
#define TESTSCAN4N(fmt,arg,count) do {  \
  s32 a1;                              \
  facePrintf(testFace,fmt,arg);        \
  u8 ct = packetScanf(testPacket,fmt,&a1); \
  TEST(count==ct);                     \
  TEST(a1 == arg);                     \
} while (0)

#define TESTSCAN4W(fmt,w,arg,count) do {        \
  u32 a1;                              \
  facePrintf(testFace,fmt,w,arg);     \
  facePrintln(testFace);               \
  u8 ct = packetScanf(testPacket,fmt,w,&a1);  \
  TEST(count==ct);                     \
  TEST(a1 == arg);                     \
} while (0)


static void test4() {
  for (u32 c = 0; c < 256; ++c) 
    TESTSCAN1("--%c--",c,5);

  for (u32 c = 0; c < 65536; c += 123) 
    TESTSCAN2("--%h--",c,5);

  for (u32 width = 0; width < 30; width+=3) {  // skip width 1,2 since scanf!=printf when field overflows
    for (s32 val = -18; val < 150; val += 9) {
      char fmt[100];
      sprintf(fmt,"(%%%dd)",width);
      TESTSCAN4(fmt,val,3u);

      if (val < 0) continue;
      sprintf(fmt,"(%%%dx)",width);
      TESTSCAN4(fmt,val,3u);

      sprintf(fmt,"(%%%do)",width);
      TESTSCAN4(fmt,val,3u);
    }
  }
}

static void test5() {
  for (u32 i = 0; i < 100; ++i) {
    s32 val = random();
    TESTSCAN4N("flip%dflop%\n",val,10u);
    TESTSCAN4N("fly%oidont%\n",val,10u);
    TESTSCAN4N("care%xif%\n",val,8u);
    TESTSCAN4N("i%bdie%\n",val,6u);
  }
}


static void test6() {
  TESTRES("100%% tested. %d%%",1,"100% tested. 1%");
  TESTSCAN4("100%% tested. %d%%",1,15u);
  TESTSCAN4("100%% tested. %d%%",10,15u);
  TESTSCAN4("100%% tested. %d%%",100,15u);
}

static void test7() {
  u8 buf[100];
  u8 * sub = makePacket(buf,100,WEST,"fungoBat");
  const char * fmt = "test(%P)zang";
  facePrintf(testFace,fmt,sub);   
  facePrintln(testFace);   
  u8 * rsub;
  u8 ct = packetScanf(testPacket,fmt,&rsub);
  TEST(11u==ct);
  TEST(packetEqual(sub,rsub));
}

static void test8() {

  srandom(8);

  const char * format = "Xt%c%h%l%c%h%l%c%h%l%\n";
  for (u32 i = 0; i < 10000; ++i) {
    u32 l1 = random(), l2 = random(), l3 = random();
    u16 h1 = random()&0xffff, h2 = random()&0xffff, h3 = random()&0xffff;
    u8 b1 = random()&0xff, b2 = random()&0xff, b3 = random()&0xff;
    
    facePrintf(testFace,format,b1,h1,l1,b2,h2,l2,b3,h3,l3);

    u32 L1, L2, L3;
    u16 H1, H2, H3;
    u8 B1, B2, B3;

    u32 matches = 
      packetScanf(testPacket,format, &B1,&H1,&L1,&B2,&H2,&L2,&B3,&H3,&L3);
    TEST(matches==12);
    TEST(b1==B1);
    TEST(h1==H1);
    TEST(l1==L1);

    TEST(b2==B2);
    TEST(h2==H2);
    TEST(l2==L2);

    TEST(b3==B3);
    TEST(h3==H3);
    TEST(l3==L3);
  }
}

void test9() {

  srandom(9);

  const char * format = "foo %d %x %t bar\n";
  for (u32 i = 0; i < 10000; ++i) {
    int l1 = random()-10000000;
    u32 l2 = random(), l3 = random();
    
    facePrintf(testFace,format,l1,l2,l3);

    int L1;
    u32 L2, L3;

    u32 matches = 
      packetScanf(testPacket,format,&L1,&L2,&L3);
    TEST(matches==14);
    TEST(l1==L1);
    TEST(l2==L2);
    TEST(l3==L3);
  }
}

void test10() {
  TESTRES("foo%dbar",12,"foo12bar");
  TESTRES("foo%obar",012,"foo12bar");
  TESTRES("foo%xbar",0x12,"foo12bar");
  TESTRES("foo%tbar",35+36,"foo1Zbar");
  TESTRES("foo%bbar",2,"foo10bar");

  TESTRES("foo%1dbar",12,"foo12bar");
  TESTRES("foo%1obar",012,"foo12bar");
  TESTRES("foo%1xbar",0x12,"foo12bar");
  TESTRES("foo%1tbar",35+36,"foo1Zbar");
  TESTRES("foo%1bbar",2,"foo10bar");

  TESTRES("foo%2dbar",12,"foo12bar");
  TESTRES("foo%2obar",012,"foo12bar");
  TESTRES("foo%2xbar",0x12,"foo12bar");
  TESTRES("foo%2tbar",35+36,"foo1Zbar");
  TESTRES("foo%2bbar",2,"foo10bar");

  TESTRES("foo%3dbar",12,"foo 12bar");
  TESTRES("foo%3obar",012,"foo 12bar");
  TESTRES("foo%3xbar",0x12,"foo 12bar");
  TESTRES("foo%3tbar",35+36,"foo 1Zbar");
  TESTRES("foo%3bbar",2,"foo 10bar");

  TESTRES("foo%4dbar",12,"foo  12bar");
  TESTRES("foo%4obar",012,"foo  12bar");
  TESTRES("foo%4xbar",0x12,"foo  12bar");
  TESTRES("foo%4tbar",35+36,"foo  1Zbar");
  TESTRES("foo%4bbar",2,"foo  10bar");

  TESTRES("foo%04dbar",12,"foo0012bar");
  TESTRES("foo%04obar",012,"foo0012bar");
  TESTRES("foo%04xbar",0x12,"foo0012bar");
  TESTRES("foo%04tbar",35+36,"foo001Zbar");
  TESTRES("foo%04bbar",2,"foo0010bar");

  TESTRES2("foo%*dbar",4,12,"foo  12bar");
  TESTRES2("foo%*obar",4,012,"foo  12bar");
  TESTRES2("foo%*xbar",4,0x12,"foo  12bar");
  TESTRES2("foo%*tbar",4,35+36,"foo  1Zbar");
  TESTRES2("foo%*bbar",4,2,"foo  10bar");

  TESTRES2("foo%0*dbar",4,12,"foo0012bar");
  TESTRES2("foo%0*obar",4,012,"foo0012bar");
  TESTRES2("foo%0*xbar",4,0x12,"foo0012bar");
  TESTRES2("foo%0*tbar",4,35+36,"foo001Zbar");
  TESTRES2("foo%0*bbar",4,2,"foo0010bar");

  for (u32 i = 4; i < 20; ++i) {
    TESTSCAN4W("[%0*d0]",i,i,4);
    TESTSCAN4W("[%0*o0]",i,i,4);
    TESTSCAN4W("[%0*x0]",i,i,4);
    TESTSCAN4W("[%0*t0]",i,i,4);
    TESTSCAN4W("[%0*b0]",i,i,4);

    TESTSCAN4W("[%*d0]",i,i,4);
    TESTSCAN4W("[%*o0]",i,i,4);
    TESTSCAN4W("[%*x0]",i,i,4);
    TESTSCAN4W("[%*t0]",i,i,4);
    TESTSCAN4W("[%*b0]",i,i,4);

    TESTSCAN4W("[%0*d0]",i,-i,4);
    TESTSCAN4W("[%*d0]",i,-i,4);
  }
}

int main() {
  initPrinter();

  test10();
  test9();
  test8();
  test7();
  test6();
  test5();
  test4();
  test3();
  test2();
  test1();

  return 0;
}

#endif /* TEST_PRINTF_SCANF */
