/** 
 * @file    cli.c
 * @brief   Command line interface
 * @author  Kenny Tran
 * @date    Apr 25 2023
 * @version 0.0.1
 *
 * @section LICENSES
 * Copyright (C) Morgan Advanced Programmable Systems, Inc.
 */


/*--------------------------------------------------------------------------------------------------
 * INCLUDE FILES
 *------------------------------------------------------------------------------------------------*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>

#include <zephyr/sys/printk.h>

#include "../wrappers/uart.h"

#include "cli.h"


/*--------------------------------------------------------------------------------------------------
 * MACROS & CONSTANTS
 *------------------------------------------------------------------------------------------------*/
#define _CLI_CR                 ('\r')
#define _CLI_LF                 ('\n')
#define _CLI_BS                 ('\b')
#define _CLI_NULL               ('\0')
#define _CLI_SPACE              (' ')


/*--------------------------------------------------------------------------------------------------
 * THREAD DEFINITIONS
 *------------------------------------------------------------------------------------------------*/


/*--------------------------------------------------------------------------------------------------
 * TYPE DEFINITIONS & ENUMERATIONS
 *------------------------------------------------------------------------------------------------*/
typedef struct _cli
{
    char  *cmd_str;
    void (*cmd) (int32_t argc, char **argv);
    bool  debug;
} _cli_s;


/*--------------------------------------------------------------------------------------------------
 * PRIVATE CONSTANTS & VARIABLES
 *------------------------------------------------------------------------------------------------*/
static bool      _cli_init = false;
static _cli_s    _cli_list[CLI_MAX_COMMANDS+3];      /* +3 for "help", "?", and "debug" */
static uint8_t   _cli_list_len = 0;
static char     *_cli_tokens[CLI_MAX_ARGS];
static char      _cli_buf[CLI_MAX_BUF_LEN];
static CLI_cmd_e _cli_cmd_mode = CLI_CMD_APP;


/*--------------------------------------------------------------------------------------------------
 * PRIVATE FUNCTION PROTOTYPES
 *------------------------------------------------------------------------------------------------*/
static void    _cli_line_clear (uint8_t n_chars);
static uint8_t _cli_tokens_parse (char *p_buf);
static bool    _cli_chars_parse (void);
static void    _cli_cmd_execute (void);
static void    _cli_cmd_help (int32_t argc, char **argv);
static void    _cli_cmd_debug (int32_t argc, char **argv);


/*--------------------------------------------------------------------------------------------------
 * PUBLIC FUNCTION DEFINITIONS
 *------------------------------------------------------------------------------------------------*/
ERRNO_e CLI_init (void)
{
    if (_cli_init)
    {
        return ERRNO_ALREADY_INITIALIZED;
    }

    /* Initialize UART for use with CLI. */
    if (UART_init () < ERRNO_SUCCESS)
    {
        return ERRNO_UART_FAILURE;
    }

    /* Set init flag so we this doesn't get called more than once. */
    _cli_init = true;

    /* Register CLI help commands + debug. */
    CLI_cmd_register (CLI_CMD_APP, (char *) "help", _cli_cmd_help);
    CLI_cmd_register (CLI_CMD_APP, (char *) "?", _cli_cmd_help);
    CLI_cmd_register (CLI_CMD_APP, (char *) "debug", _cli_cmd_debug);

    return ERRNO_SUCCESS;
}

void CLI_task (void)
{
    /* Check for command. */
    if (_cli_chars_parse ())
    {
        /* Execute command. */
        _cli_cmd_execute ();
    
        printk (">>");
    }
}

ERRNO_e CLI_cmd_register (CLI_cmd_e type, char *cmd_str, void (*cmd) (int32_t argc, char **argv))
{
    if (!_cli_init)
    {
        return ERRNO_NOT_INITIALIZED;
    }
    else if (cmd_str == NULL)
    {
        return ERRNO_BAD_POINTER;
    }
    else if (type > CLI_CMD_DEBUG)
    {
        return ERRNO_INVALID_PARAM;
    }
    /* +3 for "help", "?", and "debug" */
    else if (_cli_list_len >= CLI_MAX_COMMANDS+3)
    {
        return ERRNO_LIMIT_REACHED;
    }

    /* Append new command to the list. */
    _cli_list[_cli_list_len++] = (_cli_s) { .cmd_str = cmd_str,
                                            .cmd     = cmd,
                                            .debug   = (bool) type };

    return ERRNO_SUCCESS;    
}


/*--------------------------------------------------------------------------------------------------
 * PRIVATE FUNCTION DEFINITIONS
 *------------------------------------------------------------------------------------------------*/
static void _cli_line_clear (uint8_t n_chars)
{
    uint8_t i;

    /* Return cursor to beginning of line. */
    printk ("\r");

    for (i = 0; i < n_chars; i++)
    {
        /* Print spaces to clear. */
        printk (" ");
    }

    /* Return cursor to beginning of line. */
    printk ("\r>>");
}

static uint8_t _cli_tokens_parse (char *p_buf)
{
    uint8_t argc = 0;

    char *p_token = strtok (_cli_buf, " ");

    while (p_token != NULL)
    {
        if (argc >= CLI_MAX_ARGS)
        {
            printk ("Error: Exceeded max arguments.");
            return 0xFF;
        }

        _cli_tokens[argc++] = p_token;

        p_token = strtok (NULL, " ");
    }

    return argc;
}

static bool _cli_chars_parse (void)
{
    static uint8_t idx = 0;

    /* Read in character. */
    char ch;
    
    if (UART_getc (&ch))
    {
        switch (ch)
        {
            /* Line feed or carriage return found. */
            case _CLI_CR:
            case _CLI_LF:
                /* Echo back character. */
                printk ("%c", ch);
                _cli_buf[idx] = _CLI_NULL;
                if (idx > 0)
                {
                    /* Reset index. */
                    idx = 0;
                    printk ("\r\n");
                    return true;
                }
                else
                {
                    printk ("\r\n>>");
                }
                break;

            case _CLI_BS:
                if (idx > 0)
                {
                    /* Clear line. */
                    _cli_line_clear (idx + 2);

                    _cli_buf[--idx] = _CLI_NULL;

                    /* Reprint */
                    printk ("%s", _cli_buf);
                }
                break;

            default:
                /* Echo back character. */
                printk ("%c", ch);
                /* Check if number of characters read have exceeded max length. */
                if (idx < CLI_MAX_BUF_LEN)
                {
                    /* Append character to buffer and update index. */
                    _cli_buf[idx++] = ch;
                }
                break;
        }
    }

    return false;
}

static void _cli_cmd_execute (void)
{
    uint8_t i;
    uint8_t argc;

    /* Parse CLI tokens and get argument count. */
    argc = _cli_tokens_parse (_cli_buf);

    for (i = 0; i < _cli_list_len; i++)
    {
        /* If debug mode is enabled. */
        if (_cli_cmd_mode == CLI_CMD_DEBUG)
        {
            /* Loop through command list and compare commands. */
            if (!strcmp (_cli_tokens[0], _cli_list[i].cmd_str))
            {
                /* Execute command. */
                _cli_list[i].cmd ((int32_t) (argc - 1), (char **) &_cli_tokens[1]);

                goto DONE;
            }
        }
        /* If not, only execute app commands. */
        else
        {
            /* Loop through command list and compare commands. */
            if (!_cli_list[i].debug && !strcmp (_cli_tokens[0], _cli_list[i].cmd_str))
            {
                /* Execute command. */
                _cli_list[i].cmd ((int32_t) (argc - 1), (char **) &_cli_tokens[1]);

                goto DONE;
            }
        }
    }

    printk ("Invalid command\r\n");

DONE:

    /* Clear CLI buffer for next command. */
    memset (_cli_buf, 0, sizeof (_cli_buf));
    
}

static void _cli_cmd_help (int32_t argc, char **argv)
{
    uint8_t i;

    printk ("COMMANDS\r\n");
    printk ("-------------------------------------------\r\n");

    /* Start at index of 2 to skip "help", "?", and "debug" command. */
    for (i = 3; i < _cli_list_len; i++)
    {
        /* If debug mode is enabled. */
        if (_cli_cmd_mode == CLI_CMD_DEBUG)
        {
            printk ("%s", _cli_list[i].cmd_str);
            printk ("\r\n");
        }
        else
        {
            if (!_cli_list[i].debug)
            {
                printk ("%s", _cli_list[i].cmd_str);
                printk ("\r\n");
            }
        }
    }
}

static void _cli_cmd_debug (int32_t argc, char **argv)
{
    if (argc == 0)
    {
        if (_cli_cmd_mode == CLI_CMD_DEBUG)
        {
            _cli_cmd_mode = CLI_CMD_APP;
            printk ("CLI mode : app\r\n");
            
        }
        else
        {
            _cli_cmd_mode = CLI_CMD_DEBUG;
            printk ("CLI mode : debug\r\n");
        }
    }
}
