/* *****************************************************************************
 * File:      cli.c
 * Date:      9 Jul 2024
 * Author:    Andrew Levido
 *
 * Copyright 2024 Imbrius Pty Ltd - All Rights Reserved
 *
 ******************************************************************************/

/* Includes ------------------------------------------------------------------*/
#include "cli.h"
#include "main.h"
#include <stdarg.h>

/* Defines -------------------------------------------------------------------*/
// Definitions for CLI sizes
#define CLI_BUFFER_SIZE                 2048   // Pool of memory
#define CLI_RX_BUFFER_SIZE              256   // Store chars awaiting processing
#define CLI_CMD_BUFFER_SIZE             256   // Max length of a command
#define CLI_HISTORY_SIZE                64  // Length of cli history buffer
#define CLI_MAX_BINDING_COUNT           32   // Max number of commands

#define CLI_PRINT_BUFFER_SIZE           256  // Print string max length

/* Typedefs & Enumerations ---------------------------------------------------*/

/* Global Variables ----------------------------------------------------------*/
static struct {
  EmbeddedCli * instance;
  char out_buffer[CLI_PRINT_BUFFER_SIZE];
  bool started;
  void (*put_c)(char c);
  CLI_UINT buffer[BYTES_TO_CLI_UINTS(CLI_BUFFER_SIZE)];
}cli; //  CLI_LARGE_BUFFER;

/* Private Functions ---------------------------------------------------------*/
// Default command to clear the screen 
void cli_clear_cmd(char *args) {
  UNUSED(args);
  cli_printf("\33[2J");
}

//Wrapper function for CLI write char callback. This callback is registered 
// with embeddedCLI and it wraps the simpler user callback
void cli_putc_wrapper(EmbeddedCli* instance, char c)
{
  UNUSED(instance);
  cli.put_c(c);
}

// Wrapper function called every time a command callback is invoked. The user
// callback function pointer is in the context variable so we call that 
void cli_cmd_callback_wrapper(EmbeddedCli *cli, char *args, void *context)
{
  void(*func)(char* args);

  UNUSED(cli);
  func = (void (*)(char*))context;
  func(args);
}

/* Public Functions ----------------------------------------------------------*/

bool cli_init(void (*cli_putc)(char c))
{
  // Initialize the CLI configuration settings 
  EmbeddedCliConfig *config = embeddedCliDefaultConfig();
  config->cliBuffer = cli.buffer;
  config->cliBufferSize = CLI_BUFFER_SIZE;
  config->rxBufferSize = CLI_RX_BUFFER_SIZE;
  config->cmdBufferSize = CLI_CMD_BUFFER_SIZE;
  config->historyBufferSize = CLI_HISTORY_SIZE;
  config->maxBindingCount = CLI_MAX_BINDING_COUNT;

  // Create new CLI instance
  cli.instance = embeddedCliNew(config);
  if(cli.instance == NULL) {
    return(false);
  }
  // Assign character write function - register the user callback in the 
  // data structure and register the wrapper with embeddedCLI
  cli.put_c = cli_putc;
  cli.instance->writeChar = cli_putc_wrapper;

  // Register the default clear screen command
  if(cli_register_command("clear", "Clears the screen", true, cli_clear_cmd) == false) {
    return(false);
  }

  // CLI has now been initialized, set bool to true, and we can start writing
  cli.started = true;
  return(true);
}

// This function wraps the embeddedCLI charater recieve function
void cli_getc(char c)
{
  embeddedCliReceiveChar(cli.instance, c);
}

// Wraps the embeddedCLI command binding function. Note a single callback is 
// used for of all functions - the user callback pointer is pushed into the
// context variable
bool cli_register_command(char* cmd, const char* help, bool tokenise, void(*callback)(char*args))
{
  CliCommandBinding cfg;
  cfg.name = cmd;
  cfg.help = help;
  cfg.context = (void*)callback;
  cfg.tokenizeArgs = tokenise;
  cfg.binding = cli_cmd_callback_wrapper;
  return(embeddedCliAddBinding(cli.instance, cfg));
}

// Wraps embeddedCLI get token function
char* cli_get_token(char *string, uint16_t pos)
{
   return((char*)embeddedCliGetToken((const char*)string, pos));
}

// Wraps embeddedCLI process function - this manages the command line
// so messages dont screw up input in progress
void cli_process(void)
{
  if(cli.started) {
    embeddedCliProcess(cli.instance);
  }
}

void cli_write_line (char *string)
{
  if(cli.started) {
    embeddedCliPrint(cli.instance, string);
  }
}



void cli_printf(const char *format, ...) {
  if(cli.started) {
 
    // Format the string using snprintf
    va_list args;
    va_start(args, format);
    int length = vsnprintf(cli.out_buffer, CLI_PRINT_BUFFER_SIZE, format, args);
    va_end(args);

    // print if string fitted in buffer 
    if (length >= 0) {
      embeddedCliPrint(cli.instance, cli.out_buffer);
    }
  }
}

void cli_printf_no_lf(const char * format, ...)
{
    if(cli.started) {
 
    // Format the string using snprintf
    va_list args;
    va_start(args, format);
    int length = vsnprintf(cli.out_buffer, CLI_PRINT_BUFFER_SIZE, format, args);
    va_end(args);

    // print if string fitted in buffer 
    if (length >= 0) {
      embeddedCliPrintNoLF(cli.instance, cli.out_buffer);
    }
  }
}

void cli_msg (char *str)
{
  if(cli.started) {
    strcpy (cli.out_buffer, "MSG: ");
    strncat (cli.out_buffer, str, CLI_PRINT_BUFFER_SIZE - 7);
    embeddedCliPrint(cli.instance, cli.out_buffer);
  }
}

void cli_wrn (char *str)
{
  #ifdef CLI_USE_WARNING
  if(cli.started) {
    strcpy (cli.out_buffer, "\x1b[33mWRN: \x1b[0m");
    strncat (cli.out_buffer, str, CLI_PRINT_BUFFER_SIZE - 7);
    embeddedCliPrint(cli.instance, cli.out_buffer);
  }
  #endif
}

void cli_err (char *str)
{
  if(cli.started) {
    strcpy (cli.out_buffer, "\x1b[31mERR: \x1b[0m");
    strncat (cli.out_buffer, str, CLI_PRINT_BUFFER_SIZE - 7);
    //strncat (cli.out_buffer, "\07", 2); Bell disabled
    embeddedCliPrint(cli.instance, cli.out_buffer);
  }
}
 
//
void cli_dbg (char *str)
{
  #ifdef CLI_USE_DEBUG
  if(cli.started) {
    strcpy (cli.out_buffer, "\x1b[36mDBG: \x1b[0m");
    strncat (cli.out_buffer, str, CLI_PRINT_BUFFER_SIZE - 7);
    embeddedCliPrint(cli.instance, cli.out_buffer);
  }
  #endif
}

/* End cli */