/*
 * Copyright (c) 2019-2022, Erich Styger
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "platform.h"
#include "Shell.h"
#include "McuShell.h"
#include "McuRTT.h"
#include "McuArmTools.h"
#include "McuShellUart.h"
#include "McuLog.h"
#include "McuUtility.h"
#include "MotOffsetTable.h"
#include "minIni/McuMinINI.h"
#include "minIni/McuFlash.h"
#include "nvmc.h"


#define SHELL_CMD_ELEM_SIZE	(sizeof(Shell_cmd_s))

#if configUSE_SHELL
static uint8_t PrintHelp(const McuShell_StdIOType *io) {
	McuShell_SendHelpStr((unsigned char*)"SplitFlap", (unsigned char*)"Group of McuRTOS commands\r\n", io->stdOut);
	McuShell_SendHelpStr((unsigned char*)"  help", (unsigned char*)"Print help or status information\r\n", io->stdOut);
	McuShell_SendHelpStr((unsigned char*)"  setId <SetupId> <hwId>", (const unsigned char*)"sets the position (setupId) of the sf and its id (hwId)\r\n", io->stdOut);
	McuShell_SendHelpStr((unsigned char*)"  initAll", (const unsigned char*)"init all Splitflaps\r\n", io->stdOut);
	McuShell_SendHelpStr((unsigned char*)"  Display <string>", (const unsigned char*)"displays as many chars of the string as sf are available\r\n", io->stdOut);
	return ERR_OK;
}
#endif

static QueueHandle_t shellCmdQueue;

static void vQueueShellCmd(Shell_cmd_s shellCmd){
	if(xQueueSendToBack(shellCmdQueue, &shellCmd, pdMS_TO_TICKS(100)) != pdPASS){
		McuLog_warn("Failed adding shell cmd to queue, maybe full?");
	}
}

static uint8_t ParseCommand(const uint8_t *cmd, bool *handled, McuShell_ConstStdIOType *io){
	if (McuUtility_strcmp((char*)cmd, McuShell_CMD_HELP)==0 || McuUtility_strcmp((char*)cmd, "SplitFlap help")==0) {
		*handled = TRUE;
		return PrintHelp(io);
	  } else if(McuUtility_strncmp((char*)cmd, SHELL_CMD_IDENTIFY_SF, sizeof(SHELL_CMD_IDENTIFY_SF)-1) == 0){
		int32_t hwId = 0;
		int32_t setupId = 0;
		*handled = TRUE;
		cmd += sizeof(SHELL_CMD_IDENTIFY_SF)-1;
		if(McuUtility_xatoi(&cmd, &setupId) == ERR_OK){
			// validate param
			if(setupId > NUM_FLAPS){
				return ERR_FAILED;
			}
		}else{
			return ERR_FAILED;
		}
		// second param
		cmd++;
		if(McuUtility_xatoi(&cmd, &hwId) == ERR_OK){
			// validate param
			int32_t offset = 0;
			if(MotOffset_Get(hwId, &offset) != ERR_OK){
				return ERR_FAILED;
			}
		}else{
			return ERR_FAILED;
		}
		Shell_cmd_s shellCmd = {.shellCmd = Shell_Identify_SF, .numberOfParams = 2, .params = {hwId,setupId}};
		//Shell_cmd_s shellCmd = {.shellCmd = Shell_Identify_SF, .value1 = setupId, .value2 = hwId};
		vQueueShellCmd(shellCmd);
	  } else if(McuUtility_strcmp((char*)cmd, SHELL_CMD_INIT_ALL_SF) == 0){
		  *handled = TRUE;
		  Shell_cmd_s shellCmd = {.shellCmd = Shell_Init_All_SF, .numberOfParams = 0};
		  vQueueShellCmd(shellCmd);
	  }else if(McuUtility_strncmp((char*)cmd,SHELL_CMD_DISPLAY_STRING_SF,sizeof(SHELL_CMD_DISPLAY_STRING_SF)-1) == 0){
		  *handled = TRUE;
		  Shell_cmd_s shellCmd = {.shellCmd = Shell_Display_String_SF, .numberOfParams = NUM_FLAPS, .params = {0}};
		  cmd += sizeof(SHELL_CMD_DISPLAY_STRING_SF)-1;
		  if(SHELL_stringToInt32ArrayForSFCMD(&cmd, shellCmd.params, NUM_FLAPS) != ERR_OK){
			  return ERR_FAILED;
		  }
		  vQueueShellCmd(shellCmd);
	  }
	return ERR_OK;

}

uint8_t SHELL_stringToInt32ArrayForSFCMD(const unsigned char **str, int32_t* res, int8_t sizeRes){
	uint8_t c = 0;
	while(**str == ' '){
		(*str)++; // skip leading spaces
	}
	c = **str;
	while(c != '\0' && sizeRes>0){
		sizeRes--;
		if(c <= '9' && c >= '0'){
			// its a number
			*res = (int32_t)c;
			res++;
		}else if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')){
			// its a char
			if(c >= 'a' && c <= 'z'){
				*res = (int32_t)c-0x20;
			}else{
				*res = (int32_t)c;
			}
			res++;
		} else if(c == '!' || c == '?' || c == ':' || c == ' '){
			// special character allowed by SF
			*res = (int32_t)c;
			res++;
		} else{
			return ERR_RANGE;
		}
		c = *(++(*str));
	}
	return ERR_OK;
}

uint8_t SHELL_Int32ArrayToStringForSF(int32_t* intArray, int8_t size, char *str){
	for(uint8_t i = 0; i < size; i++){
		if(intArray[i] > 127 || intArray[i] < 0){
			return ERR_RANGE;
		}
		*str = (char)intArray[i];
		if(!((*str <= '9' && *str >= '0') || (*str >= 'A' && *str <= 'Z') ||
				*str == '!' || *str == '?' || *str == ':' || *str == ' ')){
			return ERR_RANGE;
		}
		str++;
	}
	return ERR_OK;
}

static const McuShell_ParseCommandCallback CmdParserTable[] =
{
  McuShell_ParseCommand,
  ParseCommand,
  McuRTOS_ParseCommand,
  McuFlash_ParseCommand,
  ini_ParseCommand,
  McuMinINI_ParseCommand,
  NVMC_ParseCommand,
  NULL /* Sentinel */
};

typedef struct {
  McuShell_ConstStdIOType *stdio;
  unsigned char *buf;
  size_t bufSize;
} SHELL_IODesc;

static const SHELL_IODesc ios[] =
{
  {&McuShellUart_stdio,  McuShellUart_DefaultShellBuffer,  sizeof(McuShellUart_DefaultShellBuffer)},
  {&McuRTT_stdio,  McuRTT_DefaultShellBuffer,  sizeof(McuRTT_DefaultShellBuffer)},
//  {&USB_CdcStdio,  USB_CdcDefaultShellBuffer,  sizeof(USB_CdcDefaultShellBuffer)},
};

QueueHandle_t SHELL_GetShellCmdQueueHandle() {
	return shellCmdQueue;
}

void SHELL_SendChar(unsigned char ch) {
  for(int i=0;i<sizeof(ios)/sizeof(ios[0]);i++) {
    McuShell_SendCh(ch, ios[i].stdio->stdOut);
  }
}

uint8_t SHELL_ParseCommandIO(const unsigned char *command, McuShell_ConstStdIOType *io, bool silent) {
  if (io==NULL) { /* use a default */
    io = McuShell_GetStdio();
  }
  return McuShell_ParseWithCommandTableExt(command, io, CmdParserTable, silent);
}

void SHELL_SendString(const unsigned char *str) {
  for(int i=0;i<sizeof(ios)/sizeof(ios[0]);i++) {
    McuShell_SendStr(str, ios[i].stdio->stdOut);
  }
}

static void ShellTask(void *pv) {
  int i;

  McuLog_info("Shell task started");
  for(i=0;i<sizeof(ios)/sizeof(ios[0]);i++) {
    ios[i].buf[0] = '\0';
  }
  for(;;) {
    /* process all I/Os */
    for(i=0;i<sizeof(ios)/sizeof(ios[0]);i++) {
      (void)McuShell_ReadAndParseWithCommandTable(ios[i].buf, ios[i].bufSize, ios[i].stdio, CmdParserTable);
    }
    vTaskDelay(pdMS_TO_TICKS(20));
  }
}

void SHELL_Init(void) {
  if (xTaskCreate(
      ShellTask,  /* pointer to the task */
      "Shell", /* task name for kernel awareness debugging */
      800/sizeof(StackType_t), /* task stack size */
      (void*)NULL, /* optional task startup argument */
      tskIDLE_PRIORITY+2,  /* initial priority */
      (TaskHandle_t*)NULL /* optional task handle to create */
    ) != pdPASS)
  {
    McuLog_fatal("failed creating Shell task");
    for(;;){} /* error! probably out of memory */
  }
//  McuShell_SetStdio(McuRTT_GetStdio()); /* use RTT as the default */
  if(McuShell_SetStdio(&McuShellUart_stdio) != ERR_OK){
	  // couldnt set uart as stdio, try set RTT
	  McuLog_warn("Failed to choose UART for shell, try RTT");
	  if(McuShell_SetStdio(McuRTT_GetStdio())!= ERR_OK){ /* use RTT as the default */
		  //couldnt set rtt!!
		  McuLog_fatal("failed to set rtt as default for shell");
		     for(;;){}
	  }else{
		  McuLog_info("RTT set for shell");
	  }
  }else{
	  McuLog_info("UART set for shell");
  }
  shellCmdQueue = xQueueCreate(SHELL_CMD_QUEUE_LENGTH,SHELL_CMD_ELEM_SIZE);
  if(shellCmdQueue == NULL){
	  McuLog_fatal("failed to create shell cmd queue!");
	  for(;;){}
  }
  vQueueAddToRegistry(shellCmdQueue, "Shell CMD Queue");
}

void SHELL_Deinit(void) {}

