/** @file ash-host-ui.c
 *  @brief  EZSP ASH Host user interface functions
 *
 *  This includes command option parsing, trace and counter functions.
 *
 * <!-- Copyright 2009 by Ember Corporation. All rights reserved.       *80*-->
 */

#include PLATFORM_HEADER
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include "stack/include/ember-types.h"
#include "hal/micro/generic/ash-protocol.h"
#include "hal/micro/generic/ash-common.h"
#include "app/ezsp-host/ash/ash-host.h"
#include "app/ezsp-host/ezsp-host-priv.h"
#include "app/ezsp-host/ezsp-host-io.h"
#include "app/ezsp-host/ezsp-host-queues.h"
#include "app/ezsp-host/ezsp-host-ui.h"
#include "app/util/gateway/backchannel.h"
#include "app/ezsp-host/ash/ash-host-ui.h"

//------------------------------------------------------------------------------
// Preprocessor definitions

#define ERR_LEN               128   // max length error message

#define txControl (txBuffer[0])     // more descriptive aliases
#define rxControl (rxBuffer[0])

static const char options[] = "b:f:hv::i:n:o:p:r:s:t:x:";
bool checkSerialPort(const char* portString);


bool ezspInternalProcessCommandOptions(int argc, char *argv[], char *errStr)
{
	int c;
  char port[ASH_PORT_LEN];
  char devport[ASH_PORT_LEN];
  uint32_t baud;
  uint8_t stops;
  uint8_t trace;
  uint8_t enable;
  uint8_t portnum;
  uint8_t cfg;
  int blksize;
	int optionCount = 0;

	if (!argv || !errStr) {
		snprintf(errStr, ERR_LEN, "Error: argv[] is %s and errStr is %s.\n",
				argv ? "valid" : "NULL",
				errStr ? "valid" : "NULL");
		return false;
	}

	while (true) {
		c = getopt(argc, argv, options);
		if (c == -1) {
			if (optind != argc ) {
				snprintf(errStr, ERR_LEN, "Invalid option %s.\n", argv[optind]);
			}
			break;
		}

		optionCount++;

	  switch (c) {
	    case 'b':
	      if (!optarg || (sscanf(optarg, "%u", &baud) != 1)) {
	        snprintf(errStr, ERR_LEN, "Invalid baud rate %s.\n",
	                 optarg ? optarg : "NULL");
	      } else {
	        ashWriteConfig(baudRate, baud);
	      }
	      break;
	    case 'f':
			if (!optarg) {
			  snprintf(errStr, ERR_LEN, "Invalid flow control choice NULL.\n");
			  break;
			}
			switch (*optarg) {
				case 'r':
					ashWriteConfig(rtsCts, true);
					break;
				case 'x':
					ashWriteConfig(rtsCts, false);
					break;
				default:
					snprintf(errStr, ERR_LEN, "Invalid flow control choice %s.\n", optarg);
			}
	      break;
	    case 'h':
	    case '?':
	      snprintf(errStr, ERR_LEN, "\n");
	      break;
	    case 'i':
	      if (!optarg || (sscanf(optarg, "%hhu", &enable) != 1) || (enable > 1)) {
	        snprintf(errStr, ERR_LEN, "Invalid input buffer choice %s.\n",
	                 optarg ? optarg : "NULL");
	      } else {
	        blksize = enable ? 256 : 1;
	        ashWriteConfig(inBlockLen, blksize);
	      }
	      break;
	    case 'n':
	      if (optionCount != 1) {
	        snprintf(errStr, ERR_LEN, "NCP option, if present, must be first.\n");
	      } else if (!optarg || (sscanf(optarg, "%hhu", &cfg) != 1) || (cfg > 1)) {
	        snprintf(errStr, ERR_LEN, "Invalid NCP config choice %s.\n",
	                 optarg ? optarg : "NULL");
	      } else {
	        ashSelectHostConfig(cfg);
	      }
	      break;
	    case 'o':
	      if (!optarg || (sscanf(optarg, "%hhu", &enable) != 1) || (enable > 1)) {
	        snprintf(errStr, ERR_LEN, "Invalid output buffer choice %s.\n",
	                 optarg ? optarg : "NULL");
	      } else {
	        blksize = enable ? 256 : 1;
	        ashWriteConfig(outBlockLen, blksize);
	      }
	      break;
	    case 'p':
	      if (!optarg || (sscanf(optarg, "%39s", port) <= 0)) {
	        snprintf(errStr, ERR_LEN, "Invalid serial port name %s.\n",
	                 optarg ? optarg : "NULL");
	      } else if (strlen(port) >= ASH_PORT_LEN - 1) {
	        snprintf(errStr, ERR_LEN, "Serial port name %s too long.\n", port);
	      } else {
	        // Handle some common variations specifying a serial port
	        strncpy(devport, "/dev/", 5);
	        devport[5] = '\0';
	        if ( strncmp(devport, port, 5) == 0) {
	          strncat(devport, port + 5, ASH_PORT_LEN - 5 - 1);
#ifdef __CYGWIN__
	        } else if ( ((strncmp("COM", port, 3) == 0)
	                     || (strncmp("com", port, 3) == 0) )
	                    && (sscanf(port + 3, "%hhu", &portnum) == 1)
	                    && (portnum > 0) ) {
	          snprintf(devport, ASH_PORT_LEN, "/dev/ttyS%hhu", portnum - 1);
	        } else if ( (sscanf(port, "%hhu", &portnum) == 1) && portnum ) {
	          snprintf(devport, ASH_PORT_LEN, "/dev/ttyS%hhu", portnum - 1);
#else
	        } else if (sscanf(port, "%hhu", &portnum) == 1) {
	          snprintf(devport, ASH_PORT_LEN, "/dev/ttyS%hhu", portnum);
#endif
	        } else {
	          strncat(devport, port, ASH_PORT_LEN - 1);
	        }
	        strncpy(ashHostConfig.serialPort, devport, ASH_PORT_LEN - 1);
	        ashHostConfig.serialPort[ASH_PORT_LEN - 1] = '\0';
	        if (!checkSerialPort(ashHostConfig.serialPort)) {
	          return false;
	        }
	      }
	      break;
	    case 'r':
	      if (!optarg) {
	        snprintf(errStr, ERR_LEN, "Invalid reset method NULL.\n");
	        break;
	      }
	      switch (*optarg) {
	        case 'r':
	          ashWriteConfig(resetMethod, ASH_RESET_METHOD_RST);
	          break;
	        case 'd':
	          ashWriteConfig(resetMethod, ASH_RESET_METHOD_DTR);
	          break;
	        case 'c':
	          ashWriteConfig(resetMethod, ASH_RESET_METHOD_CUSTOM);
	          break;
	        default:
	          snprintf(errStr, ERR_LEN, "Invalid reset method %s.\n", optarg);
	      }
	      break;
	    case 's':
	      if (!optarg || (sscanf(optarg, "%hhu", &stops) != 1) || (stops < 1)
	          || (stops > 2)) {
	        snprintf(errStr, ERR_LEN, "Invalid number of stop bits %s.\n",
	                 optarg ? optarg : "NULL");
	      } else {
	        ashWriteConfig(stopBits, stops);
	      }
	      break;
	    case 't':
	      if (!optarg || (sscanf(optarg, "%hhu", &trace) != 1)) {
	        snprintf(errStr, ERR_LEN, "Invalid trace flag value %s.\n",
	                 optarg ? optarg : "NULL");
	      } else {
	        ashWriteConfig(traceFlags, trace);
	      }
	      break;
	    case 'v':
	      if (!backchannelSupported) {
	        fprintf(stderr, "Error: Backchannel support not compiled into this application.\n");
	        exit(1);
	      }
	      backchannelEnable = true;
	      if (optarg) {
	        int port = atoi(optarg);
	        if (port == 0 || port > 65535) {
	          snprintf(errStr, ERR_LEN, "Invalid virtual ISA port number '%d'.\n", port);
	        }
	        backchannelSerialPortOffset = port;
	      }
	      break;
	    case 'x':
	      if (!optarg || (sscanf(optarg, "%hhu", &enable) != 1) || (enable > 1)) {
	        snprintf(errStr, ERR_LEN, "Invalid randomization choice %s.\n",
	                 optarg ? optarg : "NULL");
	      } else {
	        ashWriteConfig(randomize, enable);
	      }
	      break;
	    default:
	      assert(1);
	      break;
	  }   // end of switch (c)
	} //end while
	return true;
}

//------------------------------------------------------------------------------
// Counter functions

void ashPrintCounters(AshCount *counters, bool clear)
{
  AshCount a = *counters;

  if (clear) {
    ashClearCounters(counters);
  }
  printf("Host Counts        Received Transmitted\n");
  printf("Total bytes      %10d  %10d\n", a.rxBytes, a.txBytes);
  printf("DATA bytes       %10d  %10d\n", a.rxData, a.txData);
  printf("I/O blocks       %10d  %10d\n\n", a.rxBlocks, a.txBlocks);
  printf("Total frames     %10d  %10d\n", a.rxAllFrames, a.txAllFrames);
  printf("DATA frames      %10d  %10d\n", a.rxDataFrames, a.txDataFrames);
  printf("ACK frames       %10d  %10d\n", a.rxAckFrames, a.txAckFrames);
  printf("NAK frames       %10d  %10d\n", a.rxNakFrames, a.txNakFrames);
  printf("Retry frames     %10d  %10d\n", a.rxReDataFrames, a.txReDataFrames);
  printf("Cancelled        %10d  %10d\n", a.rxCancelled, a.txCancelled);
  printf("nRdy frames      %10d  %10d\n", a.rxN1Frames, a.txN1Frames);

  printf("\nHost Receive Errors\n");
  printf("CRC errors       %10d\n", a.rxCrcErrors);
  printf("Comm errors      %10d\n", a.rxCommErrors);
  printf("Length < minimum %10d\n", a.rxTooShort);
  printf("Length > maximum %10d\n", a.rxTooLong);
  printf("Bad controls     %10d\n", a.rxBadControl);
  printf("Bad lengths      %10d\n", a.rxBadLength);
  printf("Bad ACK numbers  %10d\n", a.rxBadAckNumber);
  printf("Out of buffers   %10d\n", a.rxNoBuffer);
  printf("Retry dupes      %10d\n", a.rxDuplicates);
  printf("Out of sequence  %10d\n", a.rxOutOfSequence);
  printf("ACK timeouts     %10d\n", a.rxAckTimeouts);
}

void ashCountFrame(bool sent)
{
  uint8_t control;

  if (sent) {
    control = readTxControl();
    BUMP_HOST_COUNTER(txAllFrames);
  } else {
    control = readRxControl();
    BUMP_HOST_COUNTER(rxAllFrames);
  }
  if ( (control & ASH_DFRAME_MASK) == ASH_CONTROL_DATA) {
    if (sent) {
      if (control & ASH_RFLAG_MASK) {
        BUMP_HOST_COUNTER(txReDataFrames);
      } else {
        BUMP_HOST_COUNTER(txDataFrames);
      }
    } else {
      if (control & ASH_RFLAG_MASK) {
        BUMP_HOST_COUNTER(rxReDataFrames);
      } else {
        BUMP_HOST_COUNTER(rxDataFrames);
      }
    }
  } else if ( (control & ASH_SHFRAME_MASK) == ASH_CONTROL_ACK) {
    if (sent) {
      BUMP_HOST_COUNTER(txAckFrames);
      if (control & ASH_NFLAG_MASK) {
        BUMP_HOST_COUNTER(txN1Frames);
      } else {
        BUMP_HOST_COUNTER(txN0Frames);
      }
    } else {
      BUMP_HOST_COUNTER(rxAckFrames);
      if (control & ASH_NFLAG_MASK) {
        BUMP_HOST_COUNTER(rxN1Frames);
      } else {
        BUMP_HOST_COUNTER(rxN0Frames);
      }
    }
  } else if ( (control & ASH_SHFRAME_MASK) == ASH_CONTROL_NAK) {
    if (sent) {
      BUMP_HOST_COUNTER(txNakFrames);
      if (control & ASH_NFLAG_MASK) {
        BUMP_HOST_COUNTER(txN1Frames);
      } else {
        BUMP_HOST_COUNTER(txN0Frames);
      }
    } else {
      BUMP_HOST_COUNTER(rxNakFrames);
      if (control & ASH_NFLAG_MASK) {
        BUMP_HOST_COUNTER(rxN1Frames);
      } else {
        BUMP_HOST_COUNTER(rxN0Frames);
      }
    }
  }
}

void ashClearCounters(AshCount *counters)
{
  *counters = zeroAshCount;
}

//------------------------------------------------------------------------------
// Trace output functions

static void ashPrintFrame(uint8_t c)
{
  if ((c & ASH_DFRAME_MASK) == ASH_CONTROL_DATA) {
    if (c & ASH_RFLAG_MASK) {
      ezspDebugPrintf("DATA(%d,%d)", ASH_GET_FRMNUM(c), ASH_GET_ACKNUM(c));
    } else {
      ezspDebugPrintf("data(%d,%d)", ASH_GET_FRMNUM(c), ASH_GET_ACKNUM(c));
    }
  } else if ((c & ASH_SHFRAME_MASK) == ASH_CONTROL_ACK) {
    if (ASH_GET_NFLAG(c)) {
      ezspDebugPrintf("ack(%d)-  ", ASH_GET_ACKNUM(c));
    } else {
      ezspDebugPrintf("ack(%d)+  ", ASH_GET_ACKNUM(c));
    }
  } else if ((c & ASH_SHFRAME_MASK) == ASH_CONTROL_NAK) {
    if (ASH_GET_NFLAG(c)) {
      ezspDebugPrintf("NAK(%d)-  ", ASH_GET_ACKNUM(c));
    } else {
      ezspDebugPrintf("NAK(%d)+  ", ASH_GET_ACKNUM(c));
    }
  } else if (c == ASH_CONTROL_RST) {
    ezspDebugPrintf("RST      ");
  } else if (c == ASH_CONTROL_RSTACK) {
    ezspDebugPrintf("RSTACK   ");
  } else if (c == ASH_CONTROL_ERROR) {
    ezspDebugPrintf("ERROR    ");
  } else {
    ezspDebugPrintf("???? 0x%02X", c);
  }
}

static void ashPrintStatus(void)
{
  ezspDebugPrintf(" Ar=%d At=%d Ft=%d Fr=%d Frt=%d Ql=%d To=%d Tp=%d Bf=%d",
                  readAckRx(), readAckTx(), readFrmTx(), readFrmRx(), readFrmReTx(),
                  ezspQueueLength(&reTxQueue),
                  readAshTimeouts(), ashGetAckPeriod(), ezspFreeListLength(&rxFree) );
}

void ashTraceFrame(bool sent)
{
  uint8_t flags;

  ashCountFrame(sent);
  flags = ashReadConfig(traceFlags);
  if (flags & (TRACE_FRAMES_BASIC | TRACE_FRAMES_VERBOSE)) {
    ezspPrintElapsedTime();
    if (sent) {
      ezspDebugPrintf("Tx ");
      ashPrintFrame(readTxControl());
      if (flags & TRACE_FRAMES_VERBOSE) {
        ashPrintStatus();
      }
      ezspDebugPrintf("   \r\n");
    } else {
      ezspDebugPrintf("   ");
      ashPrintFrame(readRxControl());
      if (flags & TRACE_FRAMES_VERBOSE) {
        ashPrintStatus();
      }
      ezspDebugPrintf(" Rx\r\n");
    }
  }
}

void ashTraceEventRecdFrame(const char *string)
{
  if (ashReadConfig(traceFlags) & TRACE_EVENTS) {
    ezspPrintElapsedTime();
    ezspDebugPrintf("Rec'd frame: ");
    ezspDebugPrintf("%s", string);
    ezspDebugPrintf("\r\n");
  }
}

void ashTraceEventTime(const char *string)
{
  if (ashReadConfig(traceFlags) & TRACE_EVENTS) {
    ezspPrintElapsedTime();
    ezspDebugPrintf("%s", string);
    ezspDebugPrintf("\r\n");
  }
}

void ashTraceDisconnected(uint8_t error)
{
  if (ashReadConfig(traceFlags) & TRACE_EVENTS) {
    ezspPrintElapsedTime();
    ezspDebugPrintf("ASH disconnected: %s\r\n", ezspErrorString(error));
    ezspDebugPrintf("    NCP status: %s\r\n", ezspErrorString(ncpError));
    ezspDebugFlush();
  }
}

void ashTraceArray(uint8_t *name, uint8_t len, uint8_t *data)
{
  if (ashReadConfig(traceFlags) & TRACE_EVENTS) {
    ezspDebugPrintf("%s ", name);
    while (len--) {
      ezspDebugPrintf(" %02X", *data++);
    }
    ezspDebugPrintf("\r\n");
  }
}
