/**
  *****************************************************************************
  * @file    cli_engine.c
  * @author  Solution Team
  * @version V1.00
  * @date    28-February-2023
  * @brief   Command line interface engine
  *****************************************************************************
  * @attention 
  *  Copyright (c) 2017 - 2023 Unicmicro Co., Ltd. 
  *  All Rights Reserved. 
  *****************************************************************************
  */

#include <stdarg.h>
#include "cli_engine.h"

#if CLI_ENABLED

#include "libc.h"
#include "drivers.h"
#include "cli_cmd_base.h"
#include "sys_config.h"

/** \name CLI global variables
 * \{ 
 */
/** Flag indicat the login password is verified */
static bool_t cli_passwd_verified = FALSE;

/** CLI temporary input buffer */
static char cli_input_buf[CLI_MAX_CMD_BYTES];

/** CLI argument vectors */
static char *cli_cmd_argv[CLI_CMD_ARGCNT];

/** CLI command list help title */
const char cli_help_menu[]         = " Command list: ";

/** CLI password prompt */
const char cli_passwd_prompt[]     = "\nPassword: ";

/** CLI default password */
const char cli_passwd_def[]        = "upd123";

/** CLI common prompt */
const char cli_prompt[]            = "UPD> ";

/** CLI command executing results */
const char * const cli_cmd_result[] = {
    "",
    "failed",
    "param error",
    "denied",
    "not found"
};

const char g_copyright[]            = " Unicmicro Co., Ltd.\n All Rights Reserved.\n";
const char g_sys_ver_and_date[]     = " %s: %02u.%02u (%s %s)\n";
const char g_sys_name[]             = "Stone System";
const char g_build_date[]           = __DATE__;
const char g_build_time[]           = __TIME__;

/** \} */

CLI_CMD_LIST_t g_cli_cmd_list[CLI_CMD_LIST_ITEMS_MAX];
CLI_CMD_INFO_t g_cli_cmd_info;


/** \brief CLI print brand information
 *
 * \param copyright     Enable print copyright information or not
 */
void cli_print_brand(bool_t copyright)
{
    if (copyright)
    {
        drv_uart_putstr(g_copyright);
    }
    if (sys_cfg_sys_name_is_null())
    {
        printf(g_sys_ver_and_date, g_sys_name, BSP_MAJOR_VERSION, BSP_MINOR_VERSION, g_build_date, g_build_time);
    }
    else 
    {
        printf(g_sys_ver_and_date, sys_cfg_sys_name_get(), BSP_MAJOR_VERSION, BSP_MINOR_VERSION, g_build_date, g_build_time);
    }
    printf(" Chip Revision: %u\n", drv_chip_rev_get());
}

#if CLI_INCLUDE_HELP

/** g_cli_cmd_help_help:
 * This command displays each commands help text.
 * The help text is assumed to be formatted as an array of strings
 * where...
 *    the first string is the command desc;
 *    the second string is command usage;
 *    and all remaining strings up to the NULL are just printable text.
 */
const char * const g_cli_cmd_help_help[] = {
    "Command help",
#if CLI_ENABLED_VERBOSE_HELP
    "[cmdname]",
#endif /* CLI_ENABLED_VERBOSE_HELP */
    0,
};


int cli_cmd_help(int argc,char *argv[])
{
#if CLI_ENABLED_VERBOSE_HELP
    char    *cp;
#endif /* CLI_ENABLED_VERBOSE_HELP */
    bool_t        foundit;
    CLI_CMD_LIST_t *p_cmd;

    p_cmd = g_cli_cmd_list;

    if (argc == 1) { // print command list
        //cli_print_brand(FALSE);

        foundit = 1;
        drv_uart_puts(cli_help_menu);
        //printf("%s\n", cli_help_menu);
        while(p_cmd->command) {
            /* Print command and desc title */
            printf("  %-22s %s\n", p_cmd->command, p_cmd->desc[0]);
            p_cmd++; 
        }
        drv_uart_putchar(UART_LF);
    }
#if CLI_ENABLED_VERBOSE_HELP
    else { // print a command description
        foundit = 0;
        cp = argv[1];
        while(p_cmd->command) {
            if (strcasecmp(p_cmd->command,cp) == 0) {
                foundit = cli_show_verbose_help(g_cli_cmd_list, p_cmd-g_cli_cmd_list);
                break;
            }
            p_cmd++;
        }
    }
#endif /* CLI_ENABLED_VERBOSE_HELP */

    if (!foundit) {
        return(CLI_CMD_NOT_FOUND);
    }
    return(CLI_CMD_SUCCESS);
}
#endif

#if CLI_INCLUDE_LOGOUT
const char * const g_cli_cmd_logout_help[] = {
    "Exit system",
#if CLI_ENABLED_VERBOSE_HELP
    "",
#endif /* CLI_ENABLED_VERBOSE_HELP */
    0,
};

int cli_cmd_logout(int argc,char *argv[])
{
    cli_passwd_verified = FALSE;
    return(CLI_CMD_SUCCESS);
}
#endif


#if CLI_ENABLED_VERBOSE_HELP
bool_t cli_show_verbose_help(CLI_CMD_LIST_t *list, int index)
{
    CLI_CMD_LIST_t * p_cmd = &list[index];
    const char * const *pp_help = &p_cmd->desc[1];

    /* Print description title*/
    printf(" %s\n", p_cmd->desc[0]);

    /* Print command verbose description */
    while(*pp_help) 
    {
        if ('\t' == *(*pp_help))
        {
            printf("   %s\n", &(*pp_help++)[1]);
        }
        else 
        {
            printf("  %s %s\n",
                p_cmd->command, *(*pp_help) ? *pp_help : " ");    
            pp_help++;
        }
    }

    return(TRUE);
}
#endif /* CLI_ENABLED_VERBOSE_HELP */

void cli_cmd_clear(void)
{
    u8_t i;

    for (i = 0; i < g_cli_cmd_info.cursor_pos; i++)
        drv_uart_putbyte(CLI_BS);
    for (i = 0; i < g_cli_cmd_info.input_len; i++)
        drv_uart_putbyte(CLI_BLKSPACE);
    for (i = 0; i < g_cli_cmd_info.input_len; i++)
        drv_uart_putbyte(CLI_BS);
}

void cli_cmd_refresh(void)
{
    u8_t i, backup;

    for (i = 0; i < g_cli_cmd_info.input_len; i++) {
        if (!cli_passwd_verified) {
            drv_uart_putchar(CLI_STAR);
        }
        else {
           drv_uart_putchar(g_cli_cmd_info.input_cmd[i]);
        }
    }
    backup = g_cli_cmd_info.input_len - g_cli_cmd_info.cursor_pos;
    for (i = 0; i < backup; i++) {
        drv_uart_putchar(CLI_BS);
    }
}

#if CLI_INCLUDE_LINEEDIT
void cli_cmd_move_cursor(bool_t right)
{
    if (right) {
        if (g_cli_cmd_info.cursor_pos >= g_cli_cmd_info.input_len) {
            return;
        }
        cli_cmd_clear();
        g_cli_cmd_info.cursor_pos++;
        cli_cmd_refresh();
    } else {
        if (0 == g_cli_cmd_info.cursor_pos) {
            return;
        }
        g_cli_cmd_info.cursor_pos--;
        drv_uart_putbyte(CLI_BS);
    }
}
#endif

void cli_cmd_insert_char(char c)
{   
    bool_t refresh = FALSE;
    
    if (g_cli_cmd_info.input_len >= CLI_MAX_CMD_BYTES) {
        return;
    }
    
    if (g_cli_cmd_info.cursor_pos < g_cli_cmd_info.input_len) {
        /* move chars */
        memmove(&g_cli_cmd_info.input_cmd[g_cli_cmd_info.cursor_pos + 1],
                  &g_cli_cmd_info.input_cmd[g_cli_cmd_info.cursor_pos],
                  g_cli_cmd_info.input_len - g_cli_cmd_info.cursor_pos);
        refresh = TRUE;
        cli_cmd_clear();
    }
    
    g_cli_cmd_info.input_cmd[g_cli_cmd_info.cursor_pos] = c;
    g_cli_cmd_info.input_len++;
    g_cli_cmd_info.cursor_pos++;
    
    g_cli_cmd_info.input_cmd[g_cli_cmd_info.input_len] = '\0';
    if (refresh) {
        cli_cmd_refresh();  
    } else {
        if (!cli_passwd_verified) {
            drv_uart_putchar(CLI_STAR);
        }
        else {
            drv_uart_putchar(c);
        }
    }
}

void cli_cmd_backspace(void)
{
    int pos = g_cli_cmd_info.cursor_pos - 1;
    
    if ((g_cli_cmd_info.input_len <= 0) || (pos < 0)) {
        return;
    }

    cli_cmd_clear();
   
    g_cli_cmd_info.input_len--;
    
    if (g_cli_cmd_info.input_len) {
        memmove(&g_cli_cmd_info.input_cmd[pos],
                  &g_cli_cmd_info.input_cmd[pos + 1],
                  g_cli_cmd_info.input_len - pos);
    }
    g_cli_cmd_info.cursor_pos--;
    g_cli_cmd_info.input_cmd[g_cli_cmd_info.input_len] = '\0';          
    cli_cmd_refresh();
}

#if CLI_INCLUDE_LINEEDIT
void cli_cmd_his_show(bool_t prev)
{
    u8_t len;
    u8_t i;
    u8_t pos;

    for (i = 0; i < CLI_CMD_HIS_MAX; i++) {
        if (prev) {
            if (0 == g_cli_cmd_info.his_pos) {
                g_cli_cmd_info.his_pos = CLI_CMD_HIS_MAX - 1;
            }
            else {
                g_cli_cmd_info.his_pos--;
            }
            pos = g_cli_cmd_info.his_pos;
        }
        else {
            pos = g_cli_cmd_info.his_pos;
            g_cli_cmd_info.his_pos++;
            if (g_cli_cmd_info.his_pos >= CLI_CMD_HIS_MAX) {
                g_cli_cmd_info.his_pos = 0;    
            }
        }
        len = strlen(g_cli_cmd_info.his_cmd[pos]);
        if (len > 0) {
            strcpy(g_cli_cmd_info.input_cmd,g_cli_cmd_info.his_cmd[pos]);
            cli_cmd_clear();
            g_cli_cmd_info.input_len = len;
            g_cli_cmd_info.cursor_pos = len;
            cli_cmd_refresh();
            break;    
        }
    }
}
#endif

void cli_cmd_input(u8_t input)
{
    if (input) {
#if CLI_INCLUDE_LINEEDIT
        if (g_cli_cmd_info.esc_buf_pos > 0) {
            /* in an escape sequence */  
            g_cli_cmd_info.esc_buf[g_cli_cmd_info.esc_buf_pos] = input;
            g_cli_cmd_info.esc_buf_pos++;  
            if (g_cli_cmd_info.esc_buf_pos >= CLI_MAX_ESC_BYTES) {                              
                if (CLI_OPEN_BRACKET == g_cli_cmd_info.esc_buf[1]) {
                    switch (g_cli_cmd_info.esc_buf[2]) {
                    case CLI_VT100_UP:
                        cli_cmd_his_show(TRUE);
                        break;
                    case CLI_VT100_DOWN :
                        cli_cmd_his_show(FALSE);
                        break;                           
                    case CLI_VT100_RIGHT:
                        cli_cmd_move_cursor(TRUE);
                        break;
                    case CLI_VT100_LEFT :
                        cli_cmd_move_cursor(FALSE);
                        break;  
                    default:
                        break;
                    }
                }
                g_cli_cmd_info.esc_buf_pos = 0;
            }
            return;
        }
#endif

        if ((input >= 0x20) && (input <= 0x7E)) {            
            cli_cmd_insert_char(input);
        }
    }
}

/* cli_tokenize():
 *    Take the incoming string and create an argv[] array from that.  The
 *    incoming string is assumed to be writeable.  The argv[] array is simple
 *    a set of pointers into that string, where the whitespace delimited
 *    character sets are each NULL terminated.
 */
int cli_tokenize(char *string,char *argv[])
{
    int argc, done;

    /* Null out the incoming argv array. */
    for(argc=0;argc<CLI_CMD_ARGCNT;argc++)
        argv[argc] = (char *)0;

    argc = 0;
    while(1) {
        while ((*string == ' ') || (*string == '\t'))
            string++;
        if (*string == 0)
            break;
        argv[argc] = string;
        while ((*string != ' ') && (*string != '\t')) {
            if ((*string == '\\') && (*(string+1) == '"')) {
                strcpy(string,string+1);
            }
            else if (*string == '"') {
                strcpy(string,string+1);
                while(*string != '"') {
                    if ((*string == '\\') && (*(string+1) == '"'))
                        strcpy(string,string+1);
                    if (*string == 0)
                        return(-1);
                    string++;
                }
                strcpy(string,string+1);
                continue;
            }
            if (*string == 0)
                break;
            string++;
        }
        if (*string == 0)
            done = 1;
        else {
            done = 0;
            *string++ = 0;
        }
        argc++;
        if (done)
            break;
        if (argc >= CLI_CMD_ARGCNT) {
            argc = -1;
            break;
        }
    }
    return(argc);
}


int cli_cmd_parse(void)
{
    int ret, argc;
    CLI_CMD_LIST_t * p_cmd;
    CLI_CMD_LIST_t * p_cmd_base;

    if (g_cli_cmd_info.input_len <= 0) {
        return(CLI_CMD_NOT_FOUND);
    }
    
#if CLI_INCLUDE_LINEEDIT
    /* save current line to history buffer */
    strcpy(g_cli_cmd_info.his_cmd[g_cli_cmd_info.his_pos], g_cli_cmd_info.input_cmd);

    g_cli_cmd_info.his_pos++;
    if (g_cli_cmd_info.his_pos >= CLI_CMD_HIS_MAX) {
        g_cli_cmd_info.his_pos = 0;
    }
    g_cli_cmd_info.esc_buf_pos = 0;
#endif

    g_cli_cmd_info.input_len = 0;
    g_cli_cmd_info.cursor_pos = 0;

    memset(cli_input_buf,0,CLI_MAX_CMD_BYTES);
    strcpy(cli_input_buf, g_cli_cmd_info.input_cmd);

    /* Build argc/argv structure based on incoming command line.
     */
    argc = cli_tokenize(cli_input_buf, cli_cmd_argv);
    if (argc == 0)
        return(CLI_CMD_SUCCESS);    /* Empty line is ok */
    if (argc < 0) {
        drv_uart_putstr(" Command line error\n");
        return(CLI_CMD_FAILURE);
    }

#if 0  /* deleted by James Gan - 2018-04-06 */
    /* Initialize static data used by getopt_init().
     */
    getopt_init();
#endif /* #if 0 */

    p_cmd_base = g_cli_cmd_list;
    for(p_cmd = p_cmd_base; p_cmd->command; p_cmd++) {
        if (strcmp(cli_cmd_argv[0],p_cmd->command) == 0)
            break;
    }
    if (p_cmd->command) {
        /* Execute the command's function...
         */
        ret = p_cmd->func(argc, cli_cmd_argv);

        if ((ret > CLI_CMD_SUCCESS) && (ret < CLI_CMD_RESULT_MAX)) {
            printf(" Command '%s' %s\n", g_cli_cmd_info.input_cmd, cli_cmd_result[ret]);
#if CLI_ENABLED_VERBOSE_HELP
            cli_show_verbose_help(g_cli_cmd_list, p_cmd-g_cli_cmd_list);
#endif /* CLI_ENABLED_VERBOSE_HELP */
        }

        return(ret);
    }

    printf(" Invalid command: %s\n", g_cli_cmd_info.input_cmd);
    return(CLI_CMD_NOT_FOUND);

}

/** \brief Add cli command to global list
 *
 * \param command   Pointer to the command
 * \param desc      Pointer to the command description pointer
 * \param func      Command execute function
 * \return          0: FALSE, 1: TRUE
 */
int cli_cmd_list_add(const char *command, const char * const *desc, int (*func)(int,char **))
{
    CLI_CMD_LIST_t *p_cmd = g_cli_cmd_list;
    int index = 0;

    if (!command)
        return FALSE;

    /* duplicated checking */
    while ((index < CLI_CMD_LIST_ITEMS_VALID) && (p_cmd->command))
    {
        if (strcmp(command,p_cmd->command) == 0)
        {
            return FALSE;
        }
        p_cmd++;
        index++;
    }

    /* add to the end of list */
    if ((index < CLI_CMD_LIST_ITEMS_VALID) && (!(p_cmd->command)))
    {
        p_cmd->command = command;
        p_cmd->desc = desc;
        p_cmd->func = func;
        return TRUE;
    }
    else {
        return FALSE;
    }
}


void cli_init(void)
{
    memset((void *)&g_cli_cmd_list, 0, sizeof(g_cli_cmd_list));
    memset((void *)&g_cli_cmd_info, 0, sizeof(CLI_CMD_INFO_t));

    /* Init basic commands to global list */
    cli_cmd_base_init();

    cli_passwd_verified = FALSE;
    if (sys_cfg_dbg_get())
    {
        drv_uart_putstr(cli_passwd_prompt);
    }
}

void cli_do_work(u8_t input)
{
    cli_cmd_input(input);
    switch (input) {
    
    case CLI_CR:
    case CLI_NL:
        drv_uart_puts("\r");
        if (!cli_passwd_verified) {
            if (!strcmp(g_cli_cmd_info.input_cmd, cli_passwd_def)) {
                cli_passwd_verified = TRUE;
            }
            g_cli_cmd_info.input_len = 0;
            g_cli_cmd_info.cursor_pos = 0;
#if CLI_INCLUDE_LINEEDIT
            g_cli_cmd_info.esc_buf_pos = 0;
#endif /* CLI_INCLUDE_LINEEDIT */
        } 
        else {
            cli_cmd_parse();
        }
        if (!cli_passwd_verified) {
            drv_uart_putstr(cli_passwd_prompt);
        }
        else {
            drv_uart_putstr(cli_prompt);
        }
        break;
    case CLI_CTLC:
    	drv_uart_puts("^C");
        g_cli_cmd_info.input_len = 0;
        g_cli_cmd_info.cursor_pos = 0;
#if CLI_INCLUDE_LINEEDIT
        g_cli_cmd_info.esc_buf_pos = 0;
#endif /* CLI_INCLUDE_LINEEDIT */
        drv_uart_putstr(cli_prompt);
        break;
    case CLI_BS:
        cli_cmd_backspace(); 
        break;
#if CLI_INCLUDE_LINEEDIT
    case CLI_ESC:
        if (cli_passwd_verified) {
            g_cli_cmd_info.esc_buf[0] = input;
            g_cli_cmd_info.esc_buf_pos = 1;
        }
        break;
#endif
    default :
        break;
    }
}

#endif

