#include "archinc.h"
#include "cli.h"
#include "clichar.h"
#include "uart0.h"
#include "eventq.h"

#include <stdio.h>

/*
 * This module handles low-level character buffering and terminal 
 * operations like backspace. It forwards completed commands to the 
 * higher level parser for further processing. 
 * 
 * For quick reference, here's a list of common special characters and their
 * escape sequences/hex values for C:
 *
 * Embed Character   Dec  Hex  Oct 
 * ----- ----------- --- ---- ----
 *  \b   backspace     8 \x08 \010
 *  \f   form feed    12 \x0C \014
 *  \n   newline      10 \x0A \012
 *  \r   car. return  13 \x0D \015
 *  \t   horiz. tab    9 \x09 \011
 *  \u   denotes hex 117 \x75 \165
 *  \v   vertical tab 11 \x0B \013
 *  \x   denotes hex 120 \x78 \170
 *  \"   "            34 \x22 \042
 *  \'   '            39 \x27 \047
 *  \\   \            92 \x5C \134
 */
 
static const char prompt[] = {'\r','\n','-','>',' ','\0'};

static char inputBuf[CONFIG_CLICHAR_INPUT_BUF_SIZE];
static int currentBufIndex = 0;

static char outputBuf[CONFIG_CLICHAR_OUTPUT_BUF_SIZE];

static char * pCurrentBuffer;

static boolean echoChars;

static void handleEvent(void);

/*
 * yes, these are overkill, but when we get a real RTOS, we can buffer
 *  multiple commands while processing one
 */

static char * getBuffer(void)
{
  currentBufIndex = 0;
  return (char *)&(inputBuf[currentBufIndex]);
}

static void returnBuffer(void)
{
  return;
}

char * clicharGetOutputBuffer(int * pBufLen)
{
  if (pBufLen != NULL) {
    *pBufLen = CONFIG_CLICHAR_OUTPUT_BUF_SIZE;
    }

  return (char *)outputBuf;
}

static void outputString(const char * pStr, int len)
{
  int i = 0;

  if ((*pStr == '\0') || (len <= 0))
    return;

  while((*pStr != '\x00') && (i < len)) {
    if (CLISEND(*pStr) == 0) {
      pStr++; i++;
      }
    }
}

// TODO: this is ugly, fix it
void clicharPuts(char * pStr)
{
  outputString(pStr, 255);
}

void clicharSendOutputBuffer(char * pBuffer, int bufLen)
{
  int i;
  char * pCh;

  if (pBuffer == NULL) {
    pBuffer = (char *)(&outputBuf[0]);
    bufLen = CONFIG_CLICHAR_OUTPUT_BUF_SIZE;
    }

  pCh = &(pBuffer[0]); i = 0;

  while((*pCh != '\0') && (i<bufLen)) {
    if (CLISEND(*pCh) == 0) {
      pCh++; i++;
      }
    }
}


static void putPrompt(void)
{
  clicharSendOutputBuffer((char *)prompt, SIZEOF_ARRAY(prompt));
}

static int accumulateCmd(char inCh)
{
  int retVal = -1;
  
  if (inCh <= 0) {
    return retVal;
    }

  switch (inCh) {
    case 0x03:
      // Control-C
      if (echoChars == true) {
        CLISEND('^');
        CLISEND('C');
        }
      // invalidate the entire line
      currentBufIndex = 0;
      putPrompt();
      break;
    case '\b':
      // handle backspace
      if (currentBufIndex == 0) {
        // at beginning of line: just bark at user
        // this should never be negative since we're circInc'ing
        if (echoChars == true) {
          CLISEND('\a');
          }
        }
      else {
	// clear the char by backing up, writing a space, backing up again
        if (echoChars == true) {
          CLISEND('\b'); CLISEND(' '); CLISEND('\b');
          }
        circDec(currentBufIndex, 0, (CONFIG_CLICHAR_INPUT_BUF_SIZE-1));
        }
      break;
    case '\r':
    case '\n':
      // handle newline: null-term and submit command for processing
      pCurrentBuffer[currentBufIndex] = '\0';
      if (echoChars == true) {
        CLISEND('\r');
        CLISEND('\n');
        }
      retVal = 0;
      break;
    default:
      if (echoChars == true) {
        CLISEND(inCh);
        }
      pCurrentBuffer[currentBufIndex] = inCh;
      /*
       * do the circInc so if the console just stuffs random characters 
       * with no line delimeter down our throat, we'll keep wrapping 
       * around and not scribble all over memory
       */
      circInc(currentBufIndex, 0, (CONFIG_CLICHAR_INPUT_BUF_SIZE-1));
      break;
    } // switch()
    
  return retVal;
}

// for testing...
void clicharEventTrigger(void)
{
  handleEvent();
}

static void handleEvent(void)
{
  char ch;
  int retVal;
  
  /*
   * we start with a buffer, so we should always have one at this point
   */

  while((retVal = CLIGET(ch)) != -1) {
    if ((retVal = accumulateCmd(ch)) != -1) {
      cliDoCommand(pCurrentBuffer, CONFIG_CLICHAR_INPUT_BUF_SIZE);
      returnBuffer();
      pCurrentBuffer = getBuffer();
      putPrompt();
      }
   }
}

int echoHandler(int argc, char * argv[], int flags)
{
  int retVal = -1;
  int bufLen;
  char * pOutBuf;

  if (argc != 2)
    return -1;

  pOutBuf = clicharGetOutputBuffer(&bufLen);

  switch(*argv[1]) {
    case 'n':
    case 'N':
    case '0':
      echoChars = false;
      sprintf(pOutBuf, "%s: echo is off\r\n",argv[0]);
      retVal = 0;
      break;
    case 'y':
    case 'Y':
    case '1':
      echoChars = true;
      sprintf(pOutBuf, "%s: echo is on\r\n",argv[0]);
      retVal = 0;
      break;
    }

  if (retVal == 0) {
    clicharSendOutputBuffer(pOutBuf, bufLen);
    }

  return retVal;
}

int clicharInit(void)
{
  char * pCh;
  int i;

  // echo characters to start
  echoChars = true;

  // get a buffer and clear it
  pCurrentBuffer = getBuffer();
  for(pCh=pCurrentBuffer, i=0;i<CONFIG_CLICHAR_INPUT_BUF_SIZE;i++, pCh++)
    pCh = '\0';

  // connect us to the console
  eventqRegisterHandler(CONSOLE_RXCHAR, handleEvent);

  // lastly, tell the user we're ready...
  putPrompt();

  cliRegisterCommand("echo", echoHandler, "echo [{0 | n(o) | N(o)} {1 | y(es) | Y(es)}]");

  return 0;
}
