#include "archinc.h"
#include "strutil.h"
#include "cli.h"
#include "clichar.h"

#include "uip/uip.h"

#include <stdio.h>
#include <string.h>

/*
 * The raw character input comes into inputBuf, until a '\n' is encountered at 
 * which point '\0' is substituted. The line is parsed and any cli variables are
 * substituted into the argBuffer. Each argument is counted and an argv[] entry
 * is computed so that each command has an familiar argv[],argc interface. 
 * String parsing is still up to the cli command handler.
 */

typedef struct cliCommand {
  char *cmdName;
  cliCommandHandler handler;
  char * helpText; // help strings should be allocated in text segment
} cliCommand, *pCliCommand;

static char argBuffer[CONFIG_CLI_ARG_BUF_SIZE];
static char * argvArray[CONFIG_CLI_MAX_ARGUMENTS];

cliCommand handlerTable[CONFIG_CLI_MAX_COMMANDS];

static int handlerTableLen;

static int helpHandler(int argc, char * argv[], int flags);


// returns 0 and command structure if found, -1 otherwise
static int lookupCommand(char *lookupName, pCliCommand * ppCmd)
{
  int retVal = -1;
  int i;
    
  for(i=0;i<handlerTableLen;i++) {
    if ( (retVal = strutil_strncasecmp(lookupName, handlerTable[i].cmdName, 16)) == 0) {
      *ppCmd = &(handlerTable[i]);
      retVal = 0;
      break;
      }
    else {
      }
    }
      
  return retVal;
}

#ifdef CONFIG_TOOLCHAIN_IAR
#pragma inline
#endif
static INLINE_KEYWORD int skipWhiteSpace(char **s)
{
  while( ((**s == ' ') || (**s == '\t')) && (**s != '\0')) (*s)++; 
  return ((**s == '\0') ? -1 : 0);
} 

#ifdef CONFIG_TOOLCHAIN_IAR
#pragma inline
#endif
static INLINE_KEYWORD int seekWhiteSpace(char **s)
{
  while( (**s != ' ') && (**s != '\t') && (**s != '\0') ) (*s)++; 
  return ((**s == '\0') ? -1 : 0);
}

// This routine searches for a '#' character and if found, terminates the string at that point
static int processLineComment(char * inputString, int maxInputLen)
{
  int i;
  
  if ((maxInputLen == 0) || (*inputString == '\0')) {
    return -1;
    }
  
  i = 0;  
  while((i<maxInputLen) && (*inputString != '\0')) {
    if (*inputString == '#') {
      *inputString = '\0';
      break;
      }
    i++;
    inputString++;
    }
      
  return 0;
}

// copies *pSource to *pDest until (pSource = pTerm)  or *pSource = '\0'
#ifdef CONFIG_TOOLCHAIN_IAR
#pragma inline
#endif
static INLINE_KEYWORD int dupStr(char *pSource, char *pTerm, char *pDest)
{
  int dupLen = 0;
  int ptrInc;

  if (pSource < pTerm)
    ptrInc = 1;
  else 
    ptrInc = -1; 


  while ((pSource != pTerm) && (*pSource != '\0')){
    *pDest = *pSource;
    pDest += ptrInc;
    pSource += ptrInc;
    dupLen++;
    }
  
  // copy pSource = pTerm (i.e. the null char)
  *pDest = *pSource;
  dupLen++;

  return dupLen;
}

// TODO: check input string length
static int tokenizeString(char * inputString, char *argvArray[], int argvArraySize)
{
  char *pLkup, *pA, *pB, *pC;
  int argvIdx;
  int i;
  
  if (argvArraySize == 0)
    return -1;
    
  // initialize argv[] array
  for(i=0;i<argvArraySize;i++)
    argvArray[i] = NULL;

  // skip any leading whitespace
  if (skipWhiteSpace(&inputString) == -1)
    return -1;
    
  pA = pB = pC = inputString;
  
  // okay, we've got at least one argument
  argvIdx = 0;
  argvArray[argvIdx++] = inputString;

  // grab the rest of 'em...    
  while ((seekWhiteSpace(&inputString) == 0) && (argvIdx < argvArraySize)) {
    *inputString++ = '\0'; // null terminate previous token
#if 1
    skipWhiteSpace(&inputString); // skip to current token
#endif
#if 0
  while ((skipWhite(&pA) == 0) && (i < argvArraySize)) {
    pC = pA;

    // handle cli variables
    if (*pA == '$') {
      pB = ++pA;         // skip past the '$'
      seekWhite(&pB);    // look for variable delimeter
      *pB = '\0';        // null terminate

      // copy pA to pLkup while pA <= pB
      dupStr(pA, pB, pLkup)

      if ((retVal = clivarDoLookup(pLkup)) != -1)
        // TODO: copy pLkupResult to pC
        }
      else {
        // straight copy
        }
#endif

    argvArray[argvIdx++] = inputString; // add current token to argv array
    }
    
  // return the new argc
  return argvIdx;
}
 
int cliInit(void)
{
  handlerTableLen = 0;
  
  // register char accumulator as input event recipient
  cliRegisterCommand("help", helpHandler, "help <command>");
  return 0;
}

int cliRegisterCommand(char * cmdStr, cliCommandHandler handler, char * helpStr)
{
  pCliCommand pCmdEntry;
  
  if (handlerTableLen == CONFIG_CLI_MAX_COMMANDS)
    return -1;
    
  // TODO: search for duplicate commands
  
  // insert the command into the table
  pCmdEntry = &(handlerTable[handlerTableLen]);
  pCmdEntry->cmdName = cmdStr;
  pCmdEntry->handler = handler;
  pCmdEntry->helpText = helpStr;
  
  // bump the command table length
  handlerTableLen++;
  
  return 0;
}

int cliDoCommand(char * pBuffer, int maxBufferLen)
{
  int i;
  int retVal = -1;
  pCliCommand pCmd;
  int argc;
  int flags;
  int bufLen;
  char * pOutBuf;

  pOutBuf = clicharGetOutputBuffer(&bufLen);
  
  // process any comment lines and return -1 if a null line
  if (processLineComment(pBuffer, maxBufferLen) == -1)
    return retVal;
    
  if ((argc = tokenizeString(pBuffer, argvArray, (int)CONFIG_CLI_MAX_ARGUMENTS)) != -1) {
    if (lookupCommand(argvArray[0], &pCmd) == 0) {
      if ((pCmd != NULL) && (pCmd->handler != NULL)) {
        retVal = (pCmd->handler)(argc, argvArray, flags);
        if (retVal != 0) {
          // do something: print error 
          sprintf(pOutBuf, "%s: error, returned [%d]\r\n",argvArray[0],retVal);
          clicharSendOutputBuffer(pOutBuf, bufLen);
          } // (retVal != 0)
        } // (pCmd->handler != NULL)
      }
    else {
      sprintf(pOutBuf, "error, command not found [%s]\r\n",argvArray[0]);
      clicharSendOutputBuffer(pOutBuf, bufLen);
      }
    }

  return retVal;
}

static int helpHandler(int argc, char * argv[], int flags)
{
  int i;
  pCliCommand pCmd;
  int bufLen;
  char * pOutBuf;

  pOutBuf = clicharGetOutputBuffer(&bufLen);
  
  if (argc > 1) {
    if (lookupCommand(argv[1], &pCmd) == 0) {
      if (pCmd->helpText != NULL) {
        sprintf(pOutBuf, "%s: %s\r\n",argv[1],pCmd->helpText);
        clicharSendOutputBuffer(pOutBuf, bufLen);
        }
      }
    }
  else {
    pCmd = &(handlerTable[0]);
    for(i=0;i<handlerTableLen;i++,pCmd++) {
      if (pCmd->helpText != NULL) {
        sprintf(pOutBuf, "%s: %s\r\n", pCmd->cmdName, pCmd->helpText);
        clicharSendOutputBuffer(pOutBuf, bufLen);
        }
      }
    }
    
  return 0;
}

#if UIP_LOGGING == 1

void uip_log(char * msg)
{
  int bufLen;
  char * pOutBuf;

  pOutBuf = clicharGetOutputBuffer(&bufLen);
  strncpy(pOutBuf, msg, (size_t)bufLen);  
  clicharSendOutputBuffer(pOutBuf, bufLen);
}

#endif

