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

#include "platform.h"
#if PL_CONFIG_USE_SHELL
#include "Shell.h"
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#if PL_CONFIG_USE_BLINKY
  #include "led.h"
#endif
#if PL_CONFIG_USE_WIFI
  #include "WiFi.h"
#endif
#if PL_CONFIG_USE_PING
  #include "ping_shell.h"
#endif
#if PL_CONFIG_USE_UDP_SERVER
  #include "udp_server_shell.h"
#endif
#if PL_CONFIG_USE_UDP_CLIENT
  #include "udp_client.h"
#endif
#if PL_CONFIG_USE_RS485
  #include "rs485.h"
  #include "McuUart485.h"
#endif
#if PL_CONFIG_USE_ROBO_REMOTE
  #include "robot.h"
#endif
#include "McuShell.h"
#include "McuUtility.h"
#include "McuRTOS.h"
#include "McuTimeDate.h"
#include "McuLog.h"
#include "driver/uart.h"
#include "driver/gpio.h"

#define SHELL_ESP32_UART_DEVICE   (UART_NUM_0) /* Uart for bootloader and connection to robot */

static TaskHandle_t SHELL_taskHandle;
static SemaphoreHandle_t SHELL_stdioMutex; /* mutex to protect access to ESP32 standard I/O */

static const McuShell_ParseCommandCallback CmdParserTable[] =
{
  McuShell_ParseCommand,
  McuRTOS_ParseCommand,
#if PL_CONFIG_USE_WIFI
  WiFi_ParseCommand,
#endif
#if PL_CONFIG_USE_UDP_CLIENT
  UDP_Client_ParseCommand,
#endif
#if PL_CONFIG_USE_PING
  PING_ParseCommand,
#endif
#if PL_CONFIG_USE_UDP_SERVER
  UDP_Server_ParseCommand,
#endif
#if PL_CONFIG_USE_RS485
  RS485_ParseCommand,
  McuUart485_ParseCommand,
#endif
#if PL_CONFIG_USE_ROBO_REMOTE
  ROBOT_ParseCommand,
#endif
  McuLog_ParseCommand,
  McuTimeDate_ParseCommand,
#if PL_CONFIG_USE_BLINKY
  LED_ParseCommand,
#endif
  NULL /* Sentinel */
};

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

static void Uart_SendString(const unsigned char *str) {
  size_t len;
  int written;

  len = strlen((const char*)str);
  written = uart_write_bytes(SHELL_ESP32_UART_DEVICE, (const char*)str, len);
  if (written!=len) {
    McuLog_error("failed sending uart bytes");
  }
}

static void Uart_SendChar(unsigned char ch) {
  uart_write_bytes(SHELL_ESP32_UART_DEVICE, &ch, 1);
}

static void Uart_ReadChar(uint8_t *c) {
  unsigned char ch = '\0';
  int len = 0;

  if (xSemaphoreTakeRecursive(SHELL_stdioMutex, portMAX_DELAY)==pdPASS) { /* take mutex */
    len = uart_read_bytes(SHELL_ESP32_UART_DEVICE, &ch, 1, 0);
    (void)xSemaphoreGiveRecursive(SHELL_stdioMutex); /* give back mutex */
  }
  if (len==0) {
    *c = '\0';
  } else {
    *c = ch;
  }
}

static bool Uart_CharPresent(void) {
  size_t size=0;

  if (xSemaphoreTakeRecursive(SHELL_stdioMutex, portMAX_DELAY)==pdPASS) { /* take mutex */
    uart_get_buffered_data_len(SHELL_ESP32_UART_DEVICE, &size);
    (void)xSemaphoreGiveRecursive(SHELL_stdioMutex); /* give back mutex */
  }
  return size!=0;
}

static McuShell_ConstStdIOType Uart_stdio = {
    .stdIn = (McuShell_StdIO_In_FctType)Uart_ReadChar,
    .stdOut = (McuShell_StdIO_OutErr_FctType)Uart_SendChar,
    .stdErr = (McuShell_StdIO_OutErr_FctType)Uart_SendChar,
    .keyPressed = Uart_CharPresent, /* if input is not empty */
  #if McuShell_CONFIG_ECHO_ENABLED
   .echoEnabled = true,
  #endif
  };

static uint8_t Uart_DefaultShellBuffer[McuShell_DEFAULT_SHELL_BUFFER_SIZE]; /* default buffer which can be used by the application */

static const SHELL_IODesc ios[] =
{
  {&Uart_stdio,  Uart_DefaultShellBuffer,  sizeof(Uart_DefaultShellBuffer)},
};

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_ParseCommand(unsigned char *cmd) {
  return McuShell_ParseWithCommandTable(cmd, McuShell_GetStdio(), CmdParserTable);
}

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) {
#if 0
  for(int i=0;i<sizeof(ios)/sizeof(ios[0]);i++) {
    McuShell_SendStr(str, ios[i].stdio->stdOut);
  }
#else /* need to improve write speed, as writing character by character is too slow */
  Uart_SendString(str);
#endif
}

void SHELL_SendStringToIO(const unsigned char *str, McuShell_ConstStdIOType *io) {
  if (io->stdOut == Uart_SendChar) { /* ESP32 UART? */
    /* if out channel is ESP32 UART: speed it up by sending whole buffer */
    Uart_SendString(str);
  } else { /* send it char by char */
    McuShell_SendStr(str, io->stdOut);
  }
}

/* ----------------- buffer handling for shell messages sent to ESP32 */
static unsigned char *esp_io_buf; /* pointer to buffer */
static size_t esp_io_buf_size; /* size of buffer */

static void esp_io_buf_SendChar(unsigned char ch) {
  McuUtility_chcat(esp_io_buf, esp_io_buf_size, ch);
}

static void esp_io_buf_ReadChar(uint8_t *c) {
  *c = '\0';
}

static bool esp_io_buf_CharPresent(void) {
  return false;
}

static McuShell_ConstStdIOType esp_stdio = {
    .stdIn = (McuShell_StdIO_In_FctType)esp_io_buf_ReadChar,
    .stdOut = (McuShell_StdIO_OutErr_FctType)esp_io_buf_SendChar,
    .stdErr = (McuShell_StdIO_OutErr_FctType)esp_io_buf_SendChar,
    .keyPressed = esp_io_buf_CharPresent, /* if input is not empty */
  #if McuShell_CONFIG_ECHO_ENABLED
    .echoEnabled = false, /* echo enabled for idf.py monitor */
  #endif
  };

void SHELL_SendToESPAndGetResponse(const unsigned char *msg, unsigned char *response, size_t responseSize) {
  esp_io_buf = response;
  esp_io_buf_size = responseSize;
  esp_io_buf[0] = '\0'; /* initialize buffer */
  McuLog_info("Sending to ESP Shell: %s", msg);
  McuShell_ParseWithCommandTableExt(msg, &esp_stdio, CmdParserTable, true); /* send to ESP32 shell */
}
/* ----------------------------------------------------------------------*/
void SHELL_SendToRobotAndGetResponse(const unsigned char *send, unsigned char *response, size_t responseSize) {
  unsigned char buffer[128]; /* buffer for sending command to robot */

  /* build a frame around the message: that way the robot is able to recognize it */
  McuUtility_strcpy(buffer, sizeof(buffer), (unsigned char*)"@robot:cmd ");
  McuUtility_strcat(buffer, sizeof(buffer), send);
  McuUtility_strcat(buffer, sizeof(buffer), (unsigned char*)"!\r\n");
  SHELL_SendString(buffer); /* send to UART, which is read by the robot */
  /* get response */
#if 1
  /* Important: this consumes directly all characters coming from the robot. That way the ESP32 shell does not get it.
   * A mutex is used to block the shell from getting the UART stream.
   */
  #define TIMEOUT_MS  (500) /* stop if we don't get new input after this timeout */
  int timeoutMs = TIMEOUT_MS;

  *response = '\0';
  if (xSemaphoreTakeRecursive(SHELL_stdioMutex, portMAX_DELAY)==pdPASS) { /* take mutex */
    while (true) { /* breaks after timeout */
      if (!Uart_stdio.keyPressed()) { /* no input: wait for timeout */
        timeoutMs -= 50;
        if (timeoutMs<=0) {
          break; /* timeout */
        }
        vTaskDelay(pdMS_TO_TICKS(50));
      } else { /* character available */
        unsigned char ch;
        Uart_stdio.stdIn(&ch);
        if (ch!='\r') { /* filter out '\r' in "\r\n" */
          McuUtility_chcat(response, responseSize, ch);
        }
        timeoutMs = TIMEOUT_MS; /* reset timeout */
      } /* if */
    } /* while */
    (void)xSemaphoreGiveRecursive(SHELL_stdioMutex); /* give back mutex */
  }
  if (*response=='\0') { /* if response is empty, send back at least an acknowledgment */
    McuUtility_strcpy(response, responseSize, (unsigned char*)"OK"); /* default response */
  }
#else
  McuUtility_strcpy(response, responseSize, (unsigned char*)"OK"); /* default response */
#endif
}
/* ----------------------------------------------------------------------*/
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'; /* initialize I/O buffers */
  }
  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));
  }
}

static void InitUart(void) {
#define ESP32_UART_BUF_SIZE  512
  uart_config_t uart_config = {
      .baud_rate = 115200,
      .data_bits = UART_DATA_8_BITS,
      .parity = UART_PARITY_DISABLE,
      .stop_bits = UART_STOP_BITS_1,
      .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
      .rx_flow_ctrl_thresh = 0,
  };

  /* Configure UART parameters */
  uart_param_config(SHELL_ESP32_UART_DEVICE, &uart_config);
  uart_set_pin(SHELL_ESP32_UART_DEVICE, GPIO_NUM_1, GPIO_NUM_3, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);

  /* Install UART driver (we don't need an event queue here) */
  uart_driver_install(SHELL_ESP32_UART_DEVICE, ESP32_UART_BUF_SIZE*2, ESP32_UART_BUF_SIZE*2, 0, NULL, 0);
  uart_set_mode(SHELL_ESP32_UART_DEVICE, UART_MODE_UART);
}

void SHELL_Init(void) {
  BaseType_t res;

  InitUart();
  McuLog_set_console(&Uart_stdio, 0);
  McuShell_SetStdio(&Uart_stdio);

  SHELL_stdioMutex = xSemaphoreCreateRecursiveMutex();
  if (SHELL_stdioMutex==NULL) { /* creation failed? */
    McuLog_fatal("Failed creating mutex");
    for(;;);
  }
  vQueueAddToRegistry(SHELL_stdioMutex, "ShellStdIoMutex");

  McuLog_info("Creating Shell task");
  res = xTaskCreate(ShellTask, "ShellTask", 4*1024/sizeof(StackType_t), NULL, tskIDLE_PRIORITY+4, &SHELL_taskHandle);
  if (res!=pdPASS) {
    McuLog_fatal("creating ShellTask task failed!");
  }
}

void SHELL_Deinit(void) {
  /* nothing needed */
}

#endif /* PL_CONFIG_USE_SHELL */
