
#include "commands.h"

#include <ArduinoJson.h>

#include "RBPXPLRTERM_config.h"
#include "softwareInfo.h"
#include "textBuffer.h"
#include "timeouts.h"
#include "timeutils.h"
#include "FlexRestClient.h"
#include "loopScanner.h"
#include "leds.h"
#include "keyboard.h"
#include "net.h"
#include "printer.h"

byte lineState;
char lastChar;

int hashCollector;
unsigned long int haParam;
String inputBuffer;
String statusLine;

//
// keyboard/serial input data scanner states
//
#define LS_CHAR 0
#define LS_DOT  1
#define LS_ESC  2
#define LS_HASH 3
#define LS_HASHAST 4
#define LS_AST  5
#define LS_ASTHASH 6
#define LS_ASTHASHDOT 7
#define LS_ASTHASHDOTAST 8
#define LS_DOTHASH 9
#define LS_DOTHASHAST 10
#define LS_DOTAST 11
#define LS_DOTASTHASH 12
#define LS_MENU 13
#define MAXLS 14

// String names for input states
const String lsnames[MAXLS] = { "CHAR", "DOT", "ESC", "HASH", "HASHAST", "AST", "ASTHASH", "ASTHASHDOT", "ASTHASHDOTAST", "DOTHASH", "DOTHASHAST", "DOTAST", "DOTASTHASH", "MENU" };


#define CPSTATE_MENU      2049    // command processor is showing a menu that can be navigated by cursor keys
#define CPSTATE_CMD       2050    // command processor is executing a command, usually waiting for user input 
#define CPSTATE_RLINE     2051    // command processor is reading data into line input buffer

int   cmdProcessorState;      // the state the command processor is in, values defined CPSTATE_xxxx

int   menuId;                 // the menu we're currently in
int   itemId;                 // the menu item we're currently at in navigation
int   itemWindowOffset;       // the offset the current window showing items is at (used if the menu display window does not fit all items in the current menu)

#define UI_MAXMENUITEMS 9

void registerSodastreamEvent(int p1, int p2, String s1);
void registerVenlafaxinEvent(int p1, int p2, String s1);
void registerCylinderEvent(int p1, int p2, String s1);
void registerTestEvent(int p1, int p2, String s1);
void registerLongTestEvent(int p1, int p2, String s1);
void paperFeed(int p1, int p2, String s1);
void displayMenuHelp(int p1, int p2, String s1);
void printMenuHelp(int p1, int p2, String s1);
void menuPrintRecentEvents(int p1, int p2, String s1);


MenuItem menu[UI_MAXMENUITEMS] = { 
  { .menuid = 0, .itemid = 0, .itemname = "Help",              .commandId = 0, .commandType = MCMD_FUNC, .newMenu = 0, .longParam = 0, .cmdFunction = displayMenuHelp },
  { .menuid = 0, .itemid = 1, .itemname = "Sodastream",        .commandId = 1, .commandType = MCMD_FUNC, .newMenu = 0, .longParam = 1, .cmdFunction = registerSodastreamEvent },
  { .menuid = 0, .itemid = 2, .itemname = "Venlafaxin",        .commandId = 2, .commandType = MCMD_FUNC, .newMenu = 0, .longParam = 2, .cmdFunction = registerVenlafaxinEvent },
  { .menuid = 0, .itemid = 3, .itemname = "Cylinder",          .commandId = 3, .commandType = MCMD_FUNC, .newMenu = 0, .longParam = 3, .cmdFunction = registerCylinderEvent },
  { .menuid = 0, .itemid = 4, .itemname = "Test",              .commandId = 4, .commandType = MCMD_FUNC, .newMenu = 0, .longParam = 4, .cmdFunction = registerTestEvent },
  { .menuid = 0, .itemid = 5, .itemname = "LongTest",          .commandId = 5, .commandType = MCMD_FUNC, .newMenu = 0, .longParam = 5, .cmdFunction = registerLongTestEvent },
  { .menuid = 0, .itemid = 6, .itemname = "paperFeed",         .commandId = 6, .commandType = MCMD_FUNC, .newMenu = 0, .longParam = 6, .cmdFunction = paperFeed },
  { .menuid = 0, .itemid = 7, .itemname = "printFullHelp",     .commandId = 7, .commandType = MCMD_FUNC, .newMenu = 0, .longParam = 7, .cmdFunction = printMenuHelp },
  { .menuid = 0, .itemid = 8, .itemname = "printRecentEvents", .commandId = 7, .commandType = MCMD_FUNC, .newMenu = 0, .longParam = 8, .cmdFunction = menuPrintRecentEvents }
};

void syncDelayMs(int ms) {
  int startms = millis();
  int endmillis = startms+ms;
  while (millis()<endmillis) {
    delay(1);
  }
}

int getHashCollector() {
  return hashCollector;
}

String lsname(byte ls) {
  if ((ls<0) || (ls>MAXLS)) {
    char nam[32];
    sprintf(nam,"%03d",ls);
    return String(nam);
  } else {
    return lsnames[ls];
  }
}

byte getPrintColorCode() {
  return 1;
}

String cstate(char c) {
  char info[200];
  byte cc = (byte(c));
  char dc = ((cc>31)&&(cc<127))?c:'_';
  sprintf(info,"[commandState.lineState=%s.hasDotCommand=%s.PopUpDisplayActive=%s,char=<%c>,charcode=0x%02X,haparam=%d,hashcollector=%d,inputBuffer=\"%40s\"]",
          lsname(lineState).c_str(),(getInputHadDotCommand()?"TRUE":"FALSE"),(isPopUpDisplayActive()?"TRUE":"FALSE"),dc,cc,haParam,hashCollector,inputBuffer.c_str());
  return String(info);
}

void genericPrintLine(String s, byte colorCode);

void printInputLine() {
  if (getInputHadDotCommand()) {
    if (inputBuffer.length()>0) {     // do only print lines with dot commands if there is normal text too
      genericPrintLine(inputBuffer,getPrintColorCode());
    }
    setInputHadDotCommand(false);
  } else {
    genericPrintLine(inputBuffer,getPrintColorCode());
  }
}

/* *****************************************************************************
 *    FUNCTION:     registerTimeMarker
 *    DESCRIPTION:  registers EVENT <item> in CONTEXT "PUCONHOME" at TIMESTAMP <currentTime> with DATA <params>
 *                  this is done by calling REST service "/rest" with "dataname=eventRegister" at EVENTMARKERSERVER and EVENTMARKERPORT
 *                  authentication is done via a fixed "apikey=..."
 *                  JSON response parsing is done with ArduinoJSON, see also https://docs.arduino.cc/libraries/arduinojson/
 *                  the unique event ID returned from the server is displayed with the result code ans the EVENT in the statusLine
 */

void registerTimeMarker(String item, String params) {
  String timenum = getTimeNum();
  if (params.length()<1) {
    params = "NODATA";
  }
  String paramstring = "/rest?dataname=eventRegister&apikey=201708161415441530010&action=register&context=PUCONHOME&event="+item+"&timestamp="+timenum+"&data="+params;
  SERIAL_PRINT("registerTimeMarker.ITEM=\""+item+"\".PARAMS=\""+params+"\".URLSTRING=\""+paramstring+"\"\n");
  char urlcs[1024];
  paramstring.toCharArray(urlcs,1024);
  SERIAL_PRINTF("registerTimeMarker.URLCS=\"%s\"\n",urlcs);
  FlexRestClient restClient = FlexRestClient(EVENTMARKERSERVER,EVENTMARKERPORT);
  restClient.setContentType("text/plain");
  int restStatus = restClient.post(urlcs,"",30000); 
  if (restStatus!=200) {
    statusLine = "regTimMrk.ERROR:";
    statusLine += restStatus;
    displayData(restStatus);
    return;
  }
  SERIAL_PRINTF("restCall.done.restStatus=%d\n",restStatus);
  String responsePayload = restClient.getLastResponsePayload();
  JsonDocument responseDoc;
  SERIAL_PRINTF("ResponsePayload=>>%s<<\n",responsePayload.c_str());
  deserializeJson(responseDoc, responsePayload);
  int respID = responseDoc["ID"];
  String respTIMESTAMP = responseDoc["TIMESTAMP"];
  String respEVENT = responseDoc["EVENT"];
  SERIAL_PRINTF("restResponse.ID=%d.TIMESTAMP=%s\n",respID,respTIMESTAMP.c_str());
  statusLine = ""; 
  statusLine += restStatus;
  statusLine += "/";
  statusLine += respID;
  statusLine += "-";
  statusLine += respEVENT;
  displayData(0);
  String printStatusLine = "";
  printStatusLine += restStatus;
  printStatusLine += "-";
  printStatusLine += respTIMESTAMP;
  printStatusLine += " ";
  printStatusLine += respEVENT;
  printPrinterLine(printStatusLine);
}

void printFolded(String ddata, byte color = 0);

/* *****************************************************************************
 *    FUNCTION:     queryAndPrint
 *    INPUT:        query - query parameters in URLencoded format
 *    DESCRIPTION:  sends query <query> and print result
 */

void queryAndPrint(String query, String responseAttributeName = "") {
  String timenum = getTimeNum();
  String paramstring = "/rest?apikey=201708161415441530010&"+query;
  SERIAL_PRINT("queryAndPrint.QUERY=\""+query+"\".URLSTRING=\""+paramstring+"\"\n");
  char urlcs[1024];
  paramstring.toCharArray(urlcs,1024);
  SERIAL_PRINTF("queryAndPrint.URLCS=\"%s\"\n",urlcs);
  FlexRestClient restClient = FlexRestClient(EVENTMARKERSERVER,EVENTMARKERPORT);
  restClient.setContentType("text/plain");
  int restStatus = restClient.post(urlcs,"",30000); 
  if (restStatus!=200) {
    statusLine = "queryAndPrint.ERROR:";
    statusLine += restStatus;
    displayData(restStatus);
    SERIAL_PRINTF("queryAndPrint.restCall.ERROR.restStatus=%d\n",restStatus);
    String errorPayload = restClient.getLastResponsePayload();
    SERIAL_PRINTF("queryAndPrint.restCall.ERROR.response=%s\n",errorPayload.c_str());
    return;
  }
  SERIAL_PRINTF("queryAndPrint.restCall.done.restStatus=%d\n",restStatus);
  String responsePayload = restClient.getLastResponsePayload();
  if (responseAttributeName!="") {
    JsonDocument responseDoc;
    SERIAL_PRINTF("ResponsePayload=>>%s<<\n",responsePayload.c_str());
    deserializeJson(responseDoc, responsePayload);
    JsonArray printlist = responseDoc[responseAttributeName];
    size_t listlength = printlist.size();
    SERIAL_PRINTF("queryAndPrint.restResponse.%s[%d]\n",responseAttributeName,listlength);
    for (size_t i = 0; i<listlength; i++) {
      String printline = printlist[i];
      SERIAL_PRINTF("queryAndPrint.printline#%d,%s",i,printline.c_str());
      printPrinterLine(printline);
      syncDelayMs(500);
    }
  } else {
    printFolded(responsePayload);
  }
}

/* **********************************************************
 *    FUNCTION:   genericPrintLine
 */
void genericPrintLine(String s, byte color = 0) {
  printPrinterLine(s);
}

void printLineFolded(String pdata, byte color) {
  // SERIAL_PRINTF("printLineFolded.LEN=%03d\n",pdata.length());
  while (pdata.length()>CHARSPERPRINTLINE) {
    // SERIAL_PRINT("...part=\"" + pdata.substring(0,CHARSPERPRINTLINE) + "\"\n");
    genericPrintLine(pdata.substring(0,CHARSPERPRINTLINE),color);
    // syncDelay(2000);
    pdata = pdata.substring(CHARSPERPRINTLINE);
    // SERIAL_PRINT("...rest=\"" + pdata + "\"\n");
  }
  // SERIAL_PRINT("...last=\"" + pdata + "\"\n");
  genericPrintLine(pdata,color);
  // syncDelay(2000);
}

/* *******************************************************
 *    FUNCTION      printFolded
 *    INPUT:        data - string to be printed
 *    DESCRIPTION:  print <data>, fold at newlines or at print-line(CHARSPERPRINTLINE)-character chunks 
 *                  to fit on print line
 */
void printFolded(String ddata, byte color) {
  int lmax = CHARSPERPRINTLINE;
  String lbuf = "";
  int spos = 0;
  while (spos<ddata.length()) {
    if (lbuf.length()>lmax) {
      genericPrintLine(lbuf,color);
      lbuf = "";
    }
    if (ddata.charAt(spos)=='\n') {
      genericPrintLine(lbuf,color);
      lbuf = "";
    } else {
      lbuf.concat(ddata.charAt(spos));
    }
    spos++;
  }
  if (lbuf.length()>0) genericPrintLine(lbuf,color);
}

//
// get the overall state header data
//
String getDataHeader() {
  String htxt = "";
  htxt += localShortTimeString() + "\n";
  htxt += "STA: " + lsname(lineState) + "\n";
  htxt += "hColl: "+String(getHashCollector())+", haPar: "+String(haParam) + "\n";
  htxt += inputBuffer.substring(0,CHARSPERDISPLINE) + "\n";
  htxt += statusLine.substring(0,CHARSPERDISPLINE) + "\n";
#if defined(KBD_GEN)
  htxt += "press *#0 for help\n";
#elif defined(KBD_RBPXPLR)
  htxt += "------------------\n";
#else 
  htxt += "unrec KBD for displayData()\n";
#endif
  return htxt;
}

int getDataHeaderLength() {
  int linecount = 5;
#if defined(KBD_GEN)
  linecount++;
#elif defined(KBD_RBPXPLR)
  linecount++;
#else 
  linecount++;
#endif
  return linecount;
}

//
// display the overall state data
//
void displayData(int dpar) {
  if (isPopUpDisplayActive()) return;
  if (!isDisplayEnabled()) return;
  String htxt = getDataHeader();
  displayFolded(htxt);
}

//
// find menu Item in menu array
//
int findMenuItemById(int theMenuId, int theItemId) {
  for (int menuindex = 0; menuindex<UI_MAXMENUITEMS; menuindex++) {
    if ((menu[menuindex].menuid==theMenuId) && (menu[menuindex].itemid==theItemId)) {
      return menuindex;
    }
  }
  return -1;
}

//
// display the menu
//
void displayMenu(int param) {
  int linecount = getDataHeaderLength();
  int menuindex = 0;
  char marker;
  String htxt = getDataHeader();
  while (linecount<DSP_MAXROW && menuindex<UI_MAXMENUITEMS) {
    if (menu[menuindex].menuid==menuId) {
      if (menu[menuindex].itemid>=itemWindowOffset) {
        if (menu[menuindex].itemid==itemId) {
          marker = '>';
        } else {
          marker = ' ';
        }
        htxt += marker + menu[menuindex].itemname + "\n";
        linecount++;
      }
    }
    menuindex++;
  }
  while (linecount<DSP_MAXROW) {
    htxt += "\n";
    linecount++;
  }
  displayFolded(htxt);
}

//
// menu UP button: select previous menu item
//
void handleMenuUp() {
  SERIAL_PRINTF("handleMenuUp.menuId=%d.itemid=%d",menuId,itemId);
  if (itemId<1) {
    tone(BUZZER_PIN,1000,100);
    return;
  }
  itemId--;
  displayMenu(0);
}

//
// menu DOWN button: select next menu item
//
void handleMenuDown() {
  SERIAL_PRINTF("handleMenuDown.menuId=%d.itemid=%d",menuId,itemId);
  if (findMenuItemById(menuId,itemId+1)<0) {
    tone(BUZZER_PIN,1000,100);
    return;
  }
  itemId++;
  displayMenu(0);
}

//
// menu LEFT button: return to parent menu
//
void handleMenuLeft() {
  SERIAL_PRINTF("handleMenuLeft.menuId=%d.itemid=%d",menuId,itemId);
  if (menuId<1) {
    tone(BUZZER_PIN,1000,100);
    return;
  }
  menuId--;
  displayMenu(0);
}

//
// menu RIGHT button: execute selected item 

void handleMenuRight() {
  SERIAL_PRINTF("handleMenuRight.menuId=%d.itemid=%d",menuId,itemId);
  int menuindex;
  if ((menuindex = findMenuItemById(menuId,itemId))<0) {
    tone(BUZZER_PIN,1000,100);
    return;
  }
  switch (menu[menuindex].commandType) {
    case MCMD_FUNC:     menu[menuindex].cmdFunction(menuId,itemId,menu[menuindex].itemname);    break;
    case MCMD_MENU:     menuId = menu[menuindex].newMenu; itemId = 0; itemWindowOffset = 0;     break;
  }
}

void displayHelp() {
  activateDisplay();
  String htxt = "";
#if defined(KBD_GEN)
  htxt +=       "A..Sodastream       \n";
  htxt +=       "B1,2,3,4 SS,VF,PC,SC\n";
  htxt +=       "#<num>#..charCode   \n";
  htxt +=       "#**.., ##..# **..*  \n";
  htxt +=       "B5..print Help      \n";
  htxt +=       "IP: " + netLocalAddress();
#elif defined(KBD_RBPXPLR)
#if defined (UI_MENU)
  htxt +=       "^ ... previous menu item\n";
  htxt +=       "< ... return to previous menu\n";
  htxt +=       "> ... select current menu item\n";
  htxt +=       "v ... next menu item\n";
  htxt +=       "\"printFullHelp\" for more\n";
  htxt +=       "IP: " + netLocalAddress();
#elif defined (UI_TTY)
  htxt +=       "^ .. Sodastream     \n";
  htxt +=       "> .. .              \n";
  htxt +=       "v .. *              \n";
  htxt +=       "< .. #              \n";
  htxt +=       "*#.#. ... Venlafaxin\n";
  htxt +=       "IP: " + netLocalAddress();
#else
#error unrecognized UI method defined for KBD_RBPXPLR in displayHelp
#endif
#else
#error unrec KBD for displayHelp()
#endif
  displayPopUp(htxt);
}

void displayMenuHelp(int p1, int p2, String s1) {
  displayHelp();
}

String getBdotCommandHelpString() {
  String htxt = "";
  htxt += ".1 ..... register \"Sodastream\" event\n";
  htxt += ".2 ..... register \"Venflaxin\" event\n";
  htxt += ".3 ..... reg. <input> \"srv06pwr\" event\n";
  htxt += ".4 ..... reg. \"SodastreamCylinder\" event\n";
  htxt += ".5 ..... print this Help Information\n";
  htxt += ".6 ..... enable background display\n";
  htxt += ".7 ..... register <input> event\n";
  htxt += ".8 ..... reg. <haParam> <input> event\n";
  htxt += ".9 ..... print empty Line\n";
  htxt += ".0 ..... print short dot command help\n";
  return htxt;
}

String getDotCommandHelpString() {
  String htxt = " --- DOT commands ----------------------\n";
  htxt += ".- ..... decrement repeatCount\n";
  htxt += ".+ ..... increment repeatCount\n";                  
  htxt += ".= ..... set repeatCount to haParam\n";
  htxt += getBdotCommandHelpString();
  htxt += ".D ..... clear log buffer\n";
  htxt += ".e ..... toggle ECHO mode\n";
  htxt += ".g ..... toggle machine logging\n";
  htxt += ".h ..... dump this help information to serial console\n";
  htxt += ".i ..... show input as info on display\n";
  htxt += ".L ..... dump log buffer to serial \n";
  htxt += ".p ..... dump print buffer serial\n";
  htxt += ".t ..... show loop microseconds\n";
  htxt += ".u ..... show current micros()\n";
  htxt += ".v ..... show software version\n";
  htxt += ".w ..... initialize display\n";
  htxt += ".Y ..... turn LED on\n";
  htxt += ".Y ..... turn LED off\n";
  return htxt;
}

String getLongHelpString() {
  String htxt = "---- RBPXPLRTERM -----------\n";
  htxt += softwareInfo();
#if defined(KBD_GEN)
  htxt += "A ... register \"Sodastream\" event\n";
  htxt += "B1 .. register \"Sodastream\" event\n";
  htxt += "B2 .. register \"Venlafaxin\" event\n";
  htxt += "B3 .. register <input> \"srv06pwr\" event\n";
  htxt += "B4 .. register \"SodaStreamCylinder\" event\n";
  htxt += "# ... enter HASH mode\n";
  htxt += "#<num># ... character with code <num>\n";
  htxt += "## ........ # sign\n";
  htxt += "* ......... enter AST mode\n";
  htxt += "** ........ * character\n";
  htxt += "#* .... enter HASHAST mode\n";
  htxt += "#** ....... , character\n";
  htxt += "#*# ....... back to CHAR mode\n";
  htxt += "#*C ....... clear haParam\n";
  htxt += "#*. .... enter DOT mode\n";
#elif defined(KBD_RBPXPLR)
#if defined(UI_TTY)
  htxt += "<UP> ..... register \"Sodastream\" event\n";
  htxt += "<RIGHT> .. . character\n";
  htxt += "<DOWN> ... * character\n";
  htxt += "<LEFT> ... # character\n";
#elif defined(UI_MENU)
  htxt += "<UP> ..... goto previous menu item\n";
  htxt += "<RIGHT> .. select current menu item\n";
  htxt += "<DOWN> ... goto nex menu item\n";
  htxt += "<LEFT> ... return to previous menu\n";
#else
#error unrecognized UI at KBD_RBPXPLR for getLongHelpString
#endif
#else
#error unrecognized KBD for getLongHelpString()
#endif
  htxt += getDotCommandHelpString();
  htxt += "========================================\n";
  htxt += "\n";
  return htxt;
}

void printHelp() {
  printFolded(getLongHelpString());
}

void printMenuHelp(int p1, int p2, String s1) {
  printHelp();
}

void showInfo(String idata) {
  SERIAL_PRINTLN(idata);
  displayPopUp(idata);
}

void registerSodastreamEvent(int p1, int p2, String s1) {
  registerTimeMarker("Sodastream","");
}

void registerVenlafaxinEvent(int p1, int p2, String s1) {
  registerTimeMarker("Venlafaxin","");
}

void registerCylinderEvent(int p1, int p2, String s1) {
  registerTimeMarker("SodastreamCylinder","");
}

void registerTestEvent(int p1, int p2, String s1) {
  registerTimeMarker("Test","TESTDATA");
}

void registerLongTestEvent(int p1, int p2, String s1) {
  registerTimeMarker("LongTestEventCodeForPrinterTest","TESTDATA");
}

void paperFeed(int p1, int p2, String s1) {
  printerPaperFeed();
}

void menuPrintRecentEvents(int p1, int p2, String s1) {
  queryAndPrint("dataname=eventRegister&action=printRecentEvents&context=*&event=*&timestamp=*&data=*","printlist");
}

void handleCharCommand(char c) {
  int n;
  String info;
  switch (c) {
    case '-':                                     // .- ... decrement repeatCount
      decrementRepeatCount();
      showInfo("Repeat Count is now " + getRepeatCount());
      break;
    case '+':                                     // .+ ... increment repeat count
      incrementRepeatCount();
      showInfo("Repeat Count is now "+getRepeatCount());
      break;
    case '=':                                     // .= ... set repeatCount to the haParam value 
      n = haParam;
      if (n>0) {
        setRepeatCount(n);
        showInfo("Repeat Count is now " + n);
      } else {
        showInfo(String("Repeat Count not changed, is now ") + getRepeatCount() + String(", haParam was 0, set repeat Count by first setting haParam by #*<digits>.="));
      }
      break;
    case '1':                                     // .1 ... register "Sodastream" event
      registerTimeMarker("Sodastream","");
      break;
    case '2':                                     // .2 ... register "Venlafaxin" event
      registerTimeMarker("Venlafaxin","");
      break;
    case '3':                                     // .3 ... register "srv06pwr" event with <inputBuffer> kWh data
      registerTimeMarker("srv06pwr","{\"pwr\":\"" + inputBuffer + "\",\"unit\":\"kWh\"}");
      inputBuffer = "";
      break;
    case '4':                                     // .4 ... register "SodastreamCyclinder" events
      registerTimeMarker("SodastreamCylinder","");
      break;
    case '7':                                     // .7 ... register <inputBuffer> event with no data
      registerTimeMarker(inputBuffer,"");
      break;
    case '8':                                     // .8 ... register <inputBuffer> event with <haParam> data
      registerTimeMarker(inputBuffer,"{\"value\":\"" + String(haParam) + "\"}");
      break;
    case 'D':                                     // .D ... clear log buffer
      clearLogBuffer();
      showInfo("Log Buffer cleared");
      break;
    case 'e':
      setEcho(!getEcho());
      showInfo(String("Echo is now ") + (getEcho()?String("enabled"):String("disabled")));
      break;
    case 'h':                  // .h ... long help text to console
      SERIAL_PRINT(getLongHelpString());
      break; 
    case 'i':
      showInfo(inputBuffer);
      inputBuffer = "";
      break;
    case 'L':                                     // .L ... dump the log buffer
      dumpLogBuffer();
      break;
    case 't':
      showInfo(String("loop microseconds = ") + getLoopMicros() + String(", ") + localTimeString());
      break;
    case 'u':                                     // .u ... show micros()
      showInfo(String("MICROS:\n") + microsfs());
      break;
    case 'v':                                     // .v ... show software version
      serialPrintSoftwareInfo();
      break;
    case 'w':                                     // .w ... initialize Display
      initDisplay();
      break;
    case 'Y':                                     // .Y ... turn LED on
      setLED(true);
      break;
    case 'y':                                     // .y ... turn LED off
      setLED(false);
      break;
    case '.':                                     // .. ... one dot for print line
      inputBuffer += '.';
      lineState = LS_CHAR;
      break;
    case '\n':
    case '\r':
      inputBuffer += '.';
      printInputLine();
      inputBuffer = "";
      lineState = LS_CHAR;
      break;
    default:
      lineState = LS_CHAR;
  }
}

byte savedState;

void handleInputChar(char c) {
    SERIAL_PRINTF("handleInputChar.START.popUpDisplayActive=%s\n",isPopUpDisplayActive()?"true":"false");
    activateDisplay();
    lastChar = c;
    SERIAL_PRINTF("handleInputChar.BEG(cstate=\"%s\")\n",cstate(c).c_str());
    if (lineState!=savedState) {
      SERIAL_PRINTF("handleInputChar: lineState=%04d != savedState=%04d, will restore from savedState\n",lineState,savedState);
      lineState = savedState;
    }
    switch (lineState) {
    case LS_CHAR:
      switch (c) {
        case '.':     setInputHadDotCommand(true);
                      lineState = LS_DOT;
                      break;
        case '\\':    lineState = LS_ESC;
                      break;
        case '#':     lineState = LS_HASH;
                      hashCollector = 0;
                      break;
        case '*':     lineState = LS_AST;
                      break;
        case '\n':
        case '\r':    //
                      inputBuffer = "";
                      break;
        default:      inputBuffer += c;
      }
      break;
    case LS_ESC:      switch (c) {
                      case 'n':   printInputLine();
                                  inputBuffer = "";
                                  lineState = LS_CHAR;
                                  break;
                      case '\\':  inputBuffer += c;
                                  lineState = LS_CHAR;
                                  break;
                      case '\n':
                      case '\r':  printInputLine();
                                  inputBuffer = "";
                                  lineState = LS_CHAR;
                                  break;
                      default:    inputBuffer += c;
                                  lineState = LS_CHAR;
                      }
                      break;
    case LS_DOT:      switch(c) {
                      case '*':   lineState = LS_DOTAST;
                                  savedState = LS_DOTAST;
                                  break;
                      case '#':   lineState = LS_DOTHASH;
                                  savedState = LS_DOTHASH;
                                  break;
                      default:    handleCharCommand(c);
                                  lineState = LS_CHAR;
                      }
                      break;
    case LS_DOTAST:   switch (c) {
                      case '#':   lineState = LS_DOTASTHASH;
                                  savedState = LS_DOTASTHASH;
                                  break;
                      case '.':   lineState = LS_CHAR;
                                  savedState = LS_CHAR;
                                  displayHelp();
                                  break;
                      }
                      break; 
    case LS_DOTASTHASH: switch(c) {
                      default:    lineState = LS_CHAR;
                                  savedState = LS_CHAR;
                      }
    case LS_DOTHASH:  switch(c) {
                      default:    lineState = LS_CHAR;
                                  savedState = LS_CHAR;
                      }
    case LS_HASH:     switch (c) {
                      case '#':     lineState = LS_CHAR;
                                    savedState = LS_CHAR;
                                    c = (char)hashCollector;
                                    hashCollector = 0;
                                    handleInputChar(c);
                                    break;
                      case '*':     lineState = LS_HASHAST;
                                    haParam = 0;
                                    break;
                      default:      if (c>='0' && c<='9') {
                                      hashCollector = (hashCollector*10 + (c-'0')) % 1000;
                                    }
                      }
                      break;
    case LS_HASHAST:  switch (c) {
                      case '*':   lineState = LS_CHAR;
                                  savedState = LS_CHAR;
                                  handleInputChar(',');
                                  break;
                      case '#':   lineState = LS_CHAR;
                                  break;
                      case 'C':   haParam = 0;
                                  break;
                      case '.':   lineState = LS_DOT;
                                  break;
                      default:    if (c>='0' && c<='9') {
                                    haParam = ((haParam*10) + (c-'0'));
                                    while (haParam>=100000000) haParam -= 100000000;
                                  }
                                  break;
                      }
                      break;
    case LS_AST:      switch (c) {
                      case '1':   
                      case '2':   
                      case '3':   
                      case '4':   
                      case '5':   
                      case '6':
                      case '7':
                      case '8':
                      case '9':       lineState = LS_CHAR;
                                      savedState = LS_CHAR;
                                      handleInputChar('.');
                                      handleInputChar(c);
                                      handleInputChar('\r');
                                      break;
                      case '*':       lineState = LS_CHAR;
                                      savedState = LS_CHAR;
                                      handleInputChar('*');
                                      break;
                      case '#':       lineState = LS_ASTHASH;
                                      break;
                      }
                      break;
    case LS_ASTHASH:  switch (c) {
                      case '#':     lineState = LS_CHAR;
                                    savedState = LS_CHAR;
                                    handleInputChar('#');
                                    break;
                      case '.':     lineState = LS_ASTHASHDOT;
                                    savedState = LS_ASTHASHDOT;
                                    haParam = 0;
                                    break;
                      case '*':     lineState = LS_CHAR;
                                    savedState = LS_CHAR;
                                    handleInputChar('*');
                                    break;
                      case '0':     lineState = LS_CHAR;
                                    savedState = LS_CHAR;
                                    displayHelp();
                                    break;
                      }
                      break;
    case LS_ASTHASHDOT: switch(c) {
                        case '#':   haParam++;
                                    if (haParam>CMD_MAXAHDOPT) {
                                      haParam = 0;
                                    }
                                    break;
                        case '*':   lineState = LS_ASTHASHDOTAST;
                                    savedState = LS_ASTHASHDOTAST;
                                    break;
                        case '.':   lineState = LS_CHAR;
                                    savedState = LS_CHAR;
                                    handleInputChar((char)(haParam+((byte)CMD_MINAHDOPTCHAR)));
                                    handleInputChar('\n');
                                    break;
                        default:    SERIAL_PRINTF("handleInputChar: illegal combination of line state %d, event %d\n",lineState,(byte)c);
                        }
                        break;
    case LS_ASTHASHDOTAST: switch(c) {
                        case '*':   haParam--;
                                    if (haParam<0) {
                                      haParam = CMD_MAXAHDOPT;
                                    }
                                    lineState = LS_ASTHASHDOT;
                                    savedState = LS_ASTHASHDOT;
                                    break;
                        case '#':   lineState = LS_ASTHASH;
                                    savedState = LS_ASTHASH;
                                    break;
                        case '.':   lineState = LS_CHAR;
                                    savedState = LS_CHAR;
                                    handleInputChar((char)(haParam+((byte)CMD_MINAHDOPTCHAR)));
                                    handleInputChar('\n');
                                    break;
                        default:    SERIAL_PRINTF("handleInputChar: illegal combination of line state %d, event %d\n",lineState,(byte)c);
                        }
                        break;
    default:          SERIAL_PRINTF("handleInputChar: illegal line state %d, event %d\n",lineState,(byte)c);
    } 
    if (lineState==111) {
      SERIAL_PRINTF("ATTENTION: lineState is now ???? = 111, will repair to CHAR = %04d\n",LS_CHAR);
      lineState = LS_CHAR;
    }
    SERIAL_PRINTF("handleInputChar.END(cstate=\"%s\")\n",cstate(c).c_str());
    savedState = lineState;
    displayData(0);
}

//
// ---------- key matrix special function(s) --------------------
//

void kfCHAR(int p) {
  if (p<=127) {
    // character entry
    handleInputChar((char)p);
    SERIAL_PRINTF("Input Buffer is now: %s\n",inputBuffer);
  } else {
    SERIAL_PRINTF("illegal character code %d entered.\n",p);
  }
}

#if defined(BUTTON_EVENT)

void keyboardEventHandler(int event, int param, String data) {
  activateDisplay();
  if (param>=KBD_RELEASEOFFSET) return;     // ignore key release events for the time being 
  SERIAL_PRINTF("keyboardEventHandler: lineState=%s, event=%d, param=scancode=%d\n",lsname(lineState).c_str(),event,param);
  if (lineState==LS_MENU) {
    if (param==KBD_BUTTON_UP) {
      handleMenuUp();
    } else if (param==KBD_BUTTON_RIGHT) {
      handleMenuRight();
    } else if (param==KBD_BUTTON_DOWN) {
      handleMenuDown();
    } else if (param==KBD_BUTTON_LEFT) {
      handleMenuLeft();
    } else {
      tone(BUZZER_PIN,2000,50);
    }
    return;
  }
  if (param>=KBD_RELEASEOFFSET) return;     // ignore key release events for the time being 
  if (param==KBD_BUTTON_UP) {               // keypad button UP    - generate "." and then "1" character == register "Sodastream" marker
    handleInputChar('.');
    handleInputChar('1');
    handleInputChar('\r');
  } else if (param==KBD_BUTTON_RIGHT) {     // keypad button RIGHT - generate "." character == lead in for dot-command
    handleInputChar('.');
  } else if (param==KBD_BUTTON_DOWN) {      // keypad button DOWN  - generate "*" character
    handleInputChar('*');
  } else if (param==KBD_BUTTON_LEFT) {      // keypad button LEFT  - generate "#" character
    handleInputChar('#');
  } else {
    SERIAL_PRINTLN("Unrecognized keypress function code param="+String(param));
  }
}

#else

void kfAD(int p) {
  SERIAL_PRINTF("key function (AD): %d in lineState %s\n",p,lsname(lineState).c_str());
  if (lineState==LS_MENU) {
    if (p==KBD_UP) {
      handleMenuUp();
    } else if (p==KBD_RIGHT) {
      handleMenuRight()
    } else if (p==KBD_DOWN) {
      handleMenuDown();
    } else if (p==KBD_LEFT) {
      handleMenuLeft();
    } else {
      tone(BUZZER_PIN,1000,100);
    }
    return
  }
  if (p<=127) {
    handleInputChar((char)p);
    // character entry
  } else if (p==KBD_UP) {     // keypad button UP    - generate "." and then "1" character == register "Sodastream" marker
    handleInputChar('.');
    handleInputChar('1');
    handleInputChar('\r');
  } else if (p==KBD_RIGHT) {  // keypad button RIGHT - generate "." character == lead in for dot-command
    handleInputChar('.');
  } else if (p==KBD_DOWN) {   // keypad button DOWN  - generate "*" character
    handleInputChar('*');
  } else if (p==KBD_LEFT) {   // keypad button LEFT  - generate "#" character
    handleInputChar('#');
  } else {
    SERIAL_PRINTLN("Unrecognized keypress function code p="+String(p));
  }
}

#endif

void initCommands() {
  hashCollector = 0;
  haParam = 0;
  inputBuffer = "";
  statusLine = "";
  lineState = LS_CHAR;
  initKeyboard();
#if defined(KBD_GEN)
#if defined(BUTTON_EVENT)
  addEventHandler(BUTTON_EVENT,keyboardEventHandler,0,"keyboardEventHandler");
#else
  setKeyFunction(0,kfCHAR,0,"normal character keys");
  setKeyFunction(1,kfAD,FUNC_A,"key A");
  setKeyFunction(2,kfAD,FUNC_B,"key B");
  setKeyFunction(3,kfAD,FUNC_C,"key C");
  setKeyFunction(4,kfAD,FUNC_D,"key D");
#endif
#elif defined(KBD_RBPXPLR)
#if defined(BUTTON_EVENT)
  addEventHandler(BUTTON_EVENT,keyboardEventHandler,0,"keyboardEventHandler");
#else
  setKeyFunction(KBD_BUTTON_UP,    kfAD, KBD_UP,    "key button UP");
  setKeyFunction(KBD_BUTTON_RIGHT, kfAD, KBD_RIGHT, "key button RIGHT");
  setKeyFunction(KBD_BUTTON_DOWN,  kfAD, KBD_DOWN,  "key button DOWN");
  setKeyFunction(KBD_BUTTON_LEFT,  kfAD, KBD_LEFT,  "key button LEFT");
#endif
#else
#error unrecognized KBD for setup key functions in initCommands
#endif
setInputHadDotCommand(false);
activateDisplay();
#if defined(UI_MENU)
  lineState = LS_MENU;
  menuId = 0;
  itemId = 0;
  itemWindowOffset = 0;
  displayMenu(0);
  addLoopScanner(5000,displayMenu,0,"backgroundMenuDisplay");
#elif defined(UI_TTY)
  lineState = LS_CHAR;
  addLoopScanner(5000,displayData,0,"backgroundDataDisplay");
#else
#error unrecognized UI for initCommands
#endif
}
