/*                                              -*- 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 SFBPacketIO.cpp SFBReactor.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 = NORTH;

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.

 \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 defiend 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.)

 #facePrintf() performs simple formatted printing, in the same general style as the C language \c
 printf functions, but with many restrictions -- for example, 'field widths' are not supported -- as
 well as 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).

XXX Still undocumented: The '#' modifier; formatting details; examples.

 */
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;
  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;
  again:
    switch (p = *format++) {
    case '#': alt = true; goto again;
    case 'c': 
      {
        u32 ch = va_arg(ap,int); 
        if (!alt || isPrint(ch)) facePrint(face,ch,BYTE);
        else {
          facePrint(face,'[',BYTE);
          if (ch < 0x10)
            facePrint(face,'0',BYTE);
          facePrint(face,ch,HEX);
          facePrint(face,']',BYTE);
        }
      }
      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': 
      if (alt) {
        type = DEC; 
        goto print;
      } else {
        facePrint(face,va_arg(ap,int)); 
        break;
      }
    case 'x': type = HEX; goto print;
    case 't': type = B36; goto print;

    print:
      facePrint(face,va_arg(ap,u32),type); 
      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': { 
      const u8 * p = va_arg(ap,const u8 *);
      if (!p) facePrint(face,"(nullp)");
      else 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,"%%%s%x",(ch<0x10?"0":""),ch);
        }
      }
      break;
    }

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

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

    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;
  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;
  again:
    u32 type = 0;
    switch (p = *format++) {
    case '#': alt = true; 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 = 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)) 
        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;

    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[SFBPACKETIO_BUFFER_SIZE_BYTES];
static SFBPacketIO test(testBuf);
static u8 testBufa[SFBPACKETIO_BUFFER_SIZE_BYTES];
static SFBPacketIO testa(testBufa);
static u8 vface;
static u8 vface2;
static u8 vface3;

PacketBuffer packetBuffer;

static void facePrintTester(u8 face, u8 byte) {
  if (face==vface || face==vface3)
    test.print(byte,BYTE);
  if (face==vface2 || face==vface3)
    testa.print(byte,BYTE);
}
static void facePrintlnTester(u8 face) {
  if (face==vface || face==vface3)
    test.println();
  if (face==vface2 || face==vface3)
    testa.println();
}
static void facePrintFaceCodeTester(u8 face) {
  if (face==vface)
    facePrint(face,'T');
  else if (face==vface2)
    facePrint(face,'2');
  else if (face==vface3)
    facePrint(face,'B');
  else TEST(0);
}
static const char * faceGetFaceNameTester(u8 face) {
  if (face==vface)
    return "TEST";
  else if (face==vface2)
    return "TESTA";
  else if (face==vface3)
    return "BOTH";
  else TEST(0);
  return 0;
}
const FacePrinter testerPrinter = {
  facePrintTester,
  facePrintlnTester,
  facePrintFaceCodeTester,
  faceGetFaceNameTester
};

static void initPrinter() {
  print_startup_initialization();

  {
    API_ASSERT_ZERO(vface);
    bool ret = faceFindFreeFace(vface);
    API_ASSERT_TRUE(ret);
    faceSetPrinter(vface,&testerPrinter);
  }

  {
    API_ASSERT_ZERO(vface2);
    bool ret = faceFindFreeFace(vface2);
    API_ASSERT_TRUE(ret);
    faceSetPrinter(vface2,&testerPrinter);
  }
  {
    API_ASSERT_ZERO(vface3);
    bool ret = faceFindFreeFace(vface3);
    API_ASSERT_TRUE(ret);
    faceSetPrinter(vface3,&testerPrinter);
  }
}

static void testReset() {
  test.reset();
  test.forceSync();

  testa.reset();
  testa.forceSync();
}

void test1() {
  testReset();
  for (u32 i = 0; i < 10000; ++i) 
    facePrintf(NO_FACES,"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
  TEST(test.packetsRemovable()==0);
}

void test2() {
  testReset();

  facePrintf(vface,"hello world\n");
  TEST(test.packetsRemovable()==1);

  facePrintf(vface,"multiple \npackets \nin \nsingle \nprintf\n");
  TEST(test.packetsRemovable()==6);

  facePrintf(vface,"incomplete packet started in one print");
  TEST(test.packetsRemovable()==6);

  facePrintf(vface," and ended in another\n");
  TEST(test.packetsRemovable()==7);

  facePrintf(vface,"consecutive \\n's make empty packets\n\n\n\n\n");
  TEST(test.packetsRemovable()==12);

  TEST(!strcmp((char*) test.copyPacketAndDiscard(packetBuffer),"hello world"));
  TEST(!strcmp((char*) test.copyPacketAndDiscard(packetBuffer),"multiple "));
  TEST(!strcmp((char*) test.copyPacketAndDiscard(packetBuffer),"packets "));
  TEST(!strcmp((char*) test.copyPacketAndDiscard(packetBuffer),"in "));
  TEST(!strcmp((char*) test.copyPacketAndDiscard(packetBuffer),"single "));
  TEST(!strcmp((char*) test.copyPacketAndDiscard(packetBuffer),"printf"));

  TEST(!strcmp((char*) test.copyPacketAndDiscard(packetBuffer),"incomplete packet started in one print and ended in another"));
  TEST(!strcmp((char*) test.copyPacketAndDiscard(packetBuffer),"consecutive \\n's make empty packets"));
  TEST(!strcmp((char*) test.copyPacketAndDiscard(packetBuffer),""));
  TEST(!strcmp((char*) test.copyPacketAndDiscard(packetBuffer),""));
  TEST(!strcmp((char*) test.copyPacketAndDiscard(packetBuffer),""));
  TEST(!strcmp((char*) test.copyPacketAndDiscard(packetBuffer),""));

  TEST(test.packetsRemovable()==0);
}

void test3() {
  testReset();

  facePrintf(vface,"packet to 1\n");
  TEST(test.packetsRemovable()==1);
  TEST(testa.packetsRemovable()==0);

  facePrintf(vface2,"packet to 2\n");
  TEST(test.packetsRemovable()==1);
  TEST(testa.packetsRemovable()==1);

  facePrintf(vface3,"packet to both\n");
  TEST(test.packetsRemovable()==2);
  TEST(testa.packetsRemovable()==2);

}

void test4() {
  testReset();

  facePrintf(vface,"%c%c%h%l%c%h%l%c\n",0x00,0x01,0x0203,0x04050607,0x08,0x090a,0x0b0c0d0e,0x0f);
  TEST(test.packetsRemovable()==1);
  {
    u8 * ret = test.copyPacketAndDiscard(packetBuffer);
    TEST(packetLength(ret)==16);
    for (u32 i = 0; i < 16; ++i)
      TEST(ret[i]==i);
  }
}

void test5() {
  testReset();

  facePrintf(vface,"%\n");
  TEST(test.packetsRemovable()==1);
  {
    u8 * ret = test.copyPacketAndDiscard(packetBuffer);
    TEST(packetLength(ret)==1);
    TEST(ret[0]==CHECK_BYTE_INIT_VALUE);
  }
  TEST(test.packetsRemovable()==0);

  for (u32 len = 1; len < 100; ++len) {
    u8 check = CHECK_BYTE_INIT_VALUE;
    for (u32 byte = 0; byte < len; ++byte) {
      CHECK_BYTE_UPDATE(check,byte);
      facePrintf(vface2,"%c",byte);
    }
    facePrintf(vface2,"%\n");
    TEST(testa.packetsRemovable()==1);
    {
      u8 * ret = testa.copyPacketAndDiscard(packetBuffer);
      TEST(packetLength(ret)==len+1);
      TEST(ret[packetLength(ret)-1]==check);
    }
  }
}

void test6() {
  testReset();

  {
    const char * str = "a man a plan a canal panama\n";
    facePrintf(vface,str);
    TEST(test.packetsRemovable()==1);
    
    u8 * ret = test.copyPacketAndDiscard(packetBuffer);
    u32 matches = packetScanf(ret,str);
    TEST(matches==strlen(str));
  }

}

void test7() {
  testReset();

  {
    const char * str = "a man a plan a canal panama\n";
    facePrintf(vface,str);
    TEST(test.packetsRemovable()==1);
    
    u8 * ret = test.copyPacketAndDiscard(packetBuffer);
    u8 c1, c2;
    u16 h1;
    u32 l1;
    u32 matches = 
      packetScanf(ret,"a%cman a %l a %hnal pana%ca\n",
                  &c1, &l1, &h1, &c2);
    TEST(matches==24);
    TEST(c1==' ');
    TEST(l1==(('p'<<24)+('l'<<16)+('a'<<8)+'n'));
    TEST(h1==(('c'<<8)+'a'));
    TEST(c2=='m');
  }
}

void test8() {
  testReset();

  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(vface,format,b1,h1,l1,b2,h2,l2,b3,h3,l3);
    TEST(test.packetsRemovable()==1);
    
    u8 * ret = test.copyPacketAndDiscard(packetBuffer);
    u32 L1, L2, L3;
    u16 H1, H2, H3;
    u8 B1, B2, B3;

    u32 matches = 
      packetScanf(ret,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() {
  testReset();

  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(vface,format,l1,l2,l3);
    TEST(test.packetsRemovable()==1);
    
    u8 * ret = test.copyPacketAndDiscard(packetBuffer);
    int L1;
    u32 L2, L3;

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


int main() {
  initPrinter();

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

  return 0;
}

#endif /* TEST_PRINTF_SCANF */
