/**
  ******************************************************************************
  * @file    shell.c
  * @author  YANDLD
  * @version V2.5
  * @date    2014.3.24
  * @brief   www.beyondcore.net   http://upcmcu.taobao.com 
  ******************************************************************************
  */
#include <stdio.h>
#include <stdarg.h>
#include "shell.h"
#include "shell_uart.h"

#if __ICCARM__
#include <yfuns.h>
#endif

#define CTL_CH(c)		((c) - 'a' + 1)
#define CTL_BACKSPACE		('\b')
#define DEL       ((char)255)
#define DEL7			((char)127)
#define CREAD_HIST_CHAR		('!')
#define putstr(str)  do {while (* str != '\0') {sputc(*str++);}} while (0)
		
#ifdef SHELL_CONFIG_USE_STDIO
#define sgetc()      getchar()     
#define sputc(ch)    putchar(ch)   

#else
#define sgetc()         dev_uart_in_char()
#define sputc(ch)       dev_uart_out_char(ch)
#endif

#define BEGINNING_OF_LINE() {			\
	while (num) {				\
		sputc(CTL_BACKSPACE);	\
		num--;				\
	}					\
}



#define ERASE_TO_EOL() {				\
	if (num < eol_num) {				\
		shell_printf("%*s", (int)(eol_num - num), ""); \
		do {					\
			sputc(CTL_BACKSPACE);	\
		} while (--eol_num > num);		\
	}						\
}

#define REFRESH_TO_EOL() {			\
	if (num < eol_num) {			\
		wlen = eol_num - num;		\
		putnstr(buf + num, wlen);	\
		num = eol_num;			\
	}					\
}

/*******************************************************************************
 * Prototypes
 ******************************************************************************/
 
extern int cmd_auto_complete(const char * const prompt, char * buf, uint8_t * np, uint8_t * colp);
static void putnstr(char * str, uint8_t n);
/********************************************************************************
 * Variables
 ******************************************************************************/
cmd_tbl_t *_syscall_table_begin 	= NULL;
cmd_tbl_t *_syscall_table_end 	= NULL;
#ifndef SHELL_CONFIG_USE_STDIO
static shell_io_install_t * gpIOInstallStruct;
#endif
static char console_buffer[SHELL_CB_SIZE + 1];	/* console I/O buffer	*/
/* hist support */
#ifdef SHELL_CONFIG_USE_HIST
static int8_t hist_max;
static int8_t hist_add_idx;
static int8_t hist_cur = -1;
static int8_t hist_num;
/* hist buffer */
static char *hist_list[HIST_MAX];
static char hist_lines[HIST_MAX][SHELL_CB_SIZE + 1];	/* Save room for NULL */
static char *hist_list[HIST_MAX];
static char hist_lines[HIST_MAX][SHELL_CB_SIZE + 1];	/* Save room for NULL */
#endif /* SHELL_CONFIG_USE_HIST */
 /*******************************************************************************
 * Code
 ******************************************************************************/

 
#ifdef SHELL_CONFIG_USE_STDIO

#else
 /*!
 * @brief use vsprintf for format.
 */
int shell_printf(const char * format,...)
{
    int chars;
    va_list ap;
    static char printbuffer[SHELL_CB_SIZE];
    
		memset(printbuffer, '0', SHELL_CB_SIZE);
		
		va_start(ap, format);
    chars = vsnprintf(printbuffer, SHELL_CB_SIZE, format, ap);
    //va_end(ap); /* follow MISRA... */
    putnstr(printbuffer, chars);
    return chars ;
}
#endif

 /*!
 * @brief install shell io function.
 */
void shell_io_install(shell_io_install_t * IOInstallStruct)
{
#ifndef SHELL_CONFIG_USE_STDIO
    if(IOInstallStruct != NULL)
    {
        gpIOInstallStruct = IOInstallStruct;
    }
#endif
}
 /*!
 * @brief beep consult
 */
void shell_beep(void)
{
    sputc('\a');
}

 /*!
 * @brief send n chars
 */
static void putnstr(char * str, uint8_t n)
{
    while (n--)
    {
        sputc(*str++); 
    }

}

#ifdef SHELL_CONFIG_USE_HIST
 /*!
 * @brief init history buffer and vars
 */
static void hist_init(void)
{
    uint8_t i;
    hist_max = 0;
    hist_add_idx = 0;
    hist_cur = -1;
    hist_num = 0;
    for (i = 0; i < HIST_MAX; i++)
    {
        hist_list[i] = hist_lines[i];
        hist_list[i][0] = '\0';
    }
}

 /*!
 * @brief and line string to history buffer
 */
static void cread_add_to_hist(char * line)
{
    strcpy(hist_list[hist_add_idx], line);
    if (++hist_add_idx >= HIST_MAX)
    {
        hist_add_idx = 0;
    }
    if (hist_add_idx > hist_max)
    {
        hist_max = hist_add_idx;
    }
    hist_num++;
}


 /*!
 * @brief get history data list and also get number of the list
 */
char ** shell_get_hist_data_list(uint8_t * num, uint8_t * cur_index)
{
    *num = hist_max + 1 ;
    *cur_index = hist_cur;
    return  &hist_list[0];
}

 /*!
 * @brief return previous history string
 */
static char * hist_prev(void)
{
    char * ret;
    uint8_t old_cur;
    if (hist_cur < 0)
    {
        return NULL;
    }
    old_cur = hist_cur;
    if (--hist_cur < 0)
    {
        hist_cur = hist_max;
    }
    if (hist_cur == hist_add_idx) 
    {
        hist_cur = old_cur;
        ret = NULL;
    } 
    else
    {
        ret = hist_list[hist_cur];
    }
    return (ret);
}

 /*!
 * @brief return next history string
 */
static char * hist_next(void)
{
    char * ret;
    if (hist_cur < 0)
    {
        return NULL;
    }
    if (hist_cur == hist_add_idx)
    {
        return NULL;
    }
    if (++hist_cur > hist_max)
    {
        hist_cur = 0;
    }
    if (hist_cur == hist_add_idx)
    {
        ret = "";
    }
    else
    {
        ret = hist_list[hist_cur];
    }
    return (ret);
}
#endif
 /*!
 * @brief add a char to conslt buffer
 */
static void cread_add_char(char ichar, uint8_t insert, uint8_t * num,
	       uint8_t * eol_num, char * buf, uint8_t len)
{
    unsigned long wlen;

    /* room ??? */
    if (insert || (*num == *eol_num))
    {
        if (*eol_num > len - 1)
        {
            shell_beep();
            return;
        }
        (*eol_num)++;
    }
    if (insert)
    {
        wlen = *eol_num - *num;
        if (wlen > 1)
        {
            memmove(&buf[*num+1], &buf[*num], wlen-1);
        }
        buf[*num] = ichar;
        putnstr(buf + *num, wlen);
        (*num)++;
        while (--wlen)
        {
            sputc(CTL_BACKSPACE);
        }
    }
    else
    {
        /* echo the character */
        wlen = 1;
        buf[*num] = ichar;
        putnstr(buf + *num, wlen);
        (*num)++;
    }
}

 /*!
 * @brief add string to conslt buffer
 */
static void cread_add_str(char * str, uint8_t strsize, uint8_t insert, uint8_t * num,
	      uint8_t * eol_num, char * buf, uint8_t len)
{
    while (strsize--)
    {
        cread_add_char(*str, insert, num, eol_num, buf, len);
        str++;
    }
}

 /*!
 * @brief read line into buffer
 */
static int cread_line(const char * const prompt, char * buf, uint8_t * len)
{
    uint8_t num = 0;
    uint8_t eol_num = 0;
    uint8_t wlen;
    char ichar = 0;
    uint8_t insert = 1;
    uint8_t esc_len = 0;
    char esc_save[8];
    uint8_t init_len = strlen(buf);
    if (init_len)
    {
        cread_add_str(buf, init_len, 1, &num, &eol_num, buf, *len);
    }
    while (1) 
    {
        do
        {
            ichar = sgetc();
        }while(!ichar);
        if ((ichar == '\n') || (ichar == '\r')) 
        {
            sputc('\r');sputc('\n');
            break;
        }
        /* handle standard linux xterm esc sequences for arrow key, etc.*/
        if (esc_len != 0)
        {
            if (esc_len == 1) 
            {
                if (ichar == '[')
                {
                    esc_save[esc_len] = ichar;
                    esc_len = 2;
                } 
                else
                {
                    cread_add_str(esc_save, esc_len, insert, &num, &eol_num, buf, *len);
                    esc_len = 0;
                }
                continue;
            } 
        switch (ichar) 
        {
            case 'D':	/* <- key */
                ichar = CTL_CH('b');
                esc_len = 0;
                break;
            case 'C':	/* -> key */
                ichar = CTL_CH('f');
                esc_len = 0;
                break;	/* pass off to ^F handler */
            case 'H':	/* Home key */
                ichar = CTL_CH('a');
                esc_len = 0;
                break;	/* pass off to ^A handler */
            case 'A':	/* up arrow */
                ichar = CTL_CH('p');
                esc_len = 0;
                break;	/* pass off to ^P handler */
            case 'B':	/* down arrow */
                ichar = CTL_CH('n');
                esc_len = 0;
                break;	/* pass off to ^N handler */
            default:
                esc_save[esc_len++] = ichar;
                cread_add_str(esc_save, esc_len, insert,
					      &num, &eol_num, buf, *len);
                esc_len = 0;
				continue;
        }
    }
    switch (ichar)
    {
        case 0x1b:
            if (esc_len == 0) 
            {
                esc_save[esc_len] = ichar;
                esc_len = 1;
            }
        else 
            {
                shell_printf("impossible condition #876\n");
                esc_len = 0;
            }
            break;
        case CTL_CH('a'):
            BEGINNING_OF_LINE()
            break;
        case CTL_CH('c'):	/* ^C - break */
            *buf = '\0';	/* discard input */
            return (-1);
            //break; /* have to follow MISRA */
        case CTL_CH('f'):
            if (num < eol_num)
            {
                sputc(buf[num]);
                num++;
            }
            break;
        case CTL_CH('b'):
            if (num)
            {
                sputc(CTL_BACKSPACE);
                num--;
            }
            break;
        case CTL_CH('d'):
            if (num < eol_num)
            {
                wlen = eol_num - num - 1;
                if (wlen)
                {
                    memmove(&buf[num], &buf[num+1], wlen);
                    putnstr(buf + num, wlen);
                }
                sputc(' ');
                do 
                {
                    sputc(CTL_BACKSPACE);
                } while (wlen--);
                eol_num--;
            }
            break;
        case CTL_CH('k'):
            ERASE_TO_EOL()
            break;
        case CTL_CH('e'):
            REFRESH_TO_EOL()
            break;
        case CTL_CH('o'):
            insert = !insert;
            break;
        case CTL_CH('x'):
        case CTL_CH('u'):
            BEGINNING_OF_LINE()
            ERASE_TO_EOL()
            break;
        case DEL:
        case DEL7:
        case 8:
            if (num)
            {
                wlen = eol_num - num;
                num--;
                memmove(&buf[num], &buf[num+1], wlen);
                sputc(CTL_BACKSPACE);
                putnstr(buf + num, wlen);
                sputc(' ');
                do
                {
                    sputc(CTL_BACKSPACE);
                } while (wlen--);
                eol_num--;
            }
            break;

        case CTL_CH('p'):
        case CTL_CH('n'):
#ifdef SHELL_CONFIG_USE_HIST
        {
            char * hline;
            esc_len = 0;
            if (ichar == CTL_CH('p'))
            {
                hline = hist_prev();
            }
            else
            {
                hline = hist_next();
            }
            if (!hline)
            {
                shell_beep();
                continue;
            }
            /* nuke the current line */
            /* first, go home */
            BEGINNING_OF_LINE()
            ERASE_TO_EOL()
            /* copy new line into place and display */
            strcpy(buf, hline);
            eol_num = strlen(buf);
            REFRESH_TO_EOL()
            continue;
            //break; /* have to follow MISRA */
        }
#else
        {
            continue;
        }
        
#endif /* SHELL_CONFIG_USE_HIST */
        case '\t': 
#ifdef SHELL_CONFIG_AUTO_COMPLETE
        {
            uint8_t num2, col;
            /* do not autocomplete when in the middle */
            if (num < eol_num)
            {
                shell_beep();
                break;
            }
            buf[num] = '\0';
            col = strlen(prompt) + eol_num;
            num2 = num;
            if (cmd_auto_complete(prompt, buf, &num2, &col))
            {
                col = num2 - num;
                num += col;
                eol_num += col;
            }
            break;
        }
#else
        {
            continue;
        }
#endif /* SHELL_CONFIG_AUTO_COMPLETE */
        default:
            cread_add_char(ichar, insert, &num, &eol_num, buf, *len);
            break;
        }
    }
    *len = eol_num;
    buf[eol_num] = '\0';	/* lose the newline */
#ifdef SHELL_CONFIG_USE_HIST
    if ((buf[0]) && (buf[0] != CREAD_HIST_CHAR))
    {
        cread_add_to_hist(buf);
    }
    hist_cur = hist_add_idx;
#endif
    return 0;
}

 /*!
 * @brief read line from consult
 */
 int readline (char * prompt)
{
    uint8_t len = SHELL_CB_SIZE;
    int8_t rc;
    char * p = console_buffer;
    static uint8_t initted = 0;
    /* break console_buffer so that is will not repeatable */
    console_buffer[0] = '\0';
		
    if (!initted)
    {
#ifdef SHELL_CONFIG_USE_HIST
        hist_init();
#endif
        initted = 1;
        //printf("\r\nSHELL (build: %s)\r\n", __DATE__);
    }
    if (prompt)
    {   
        putstr (prompt);
    }
    rc = cread_line(prompt, p, &len);
    return rc < 0 ? rc : len;
}

 /*!
 * @brief extract from readline
 */
static int parse_line (char * line, char * argv[])
{
    uint8_t nargs = 0;
    while (nargs < SHELL_MAX_ARGS) 
    {
        /* skip any white space */
        while (isblank(*line))
        {
            ++line;
        }
        if (*line == '\0')
        {	/* end of line, no more args	*/
            argv[nargs] = NULL;
            return nargs;
        }
        argv[nargs++] = line;	/* begin of argument string	*/
        /* find end of string */
        while ((*line) && (!isblank(*line)))
        {
            ++line;
        }
        if (*line == '\0') 
        {	/* end of line, no more args	*/
            argv[nargs] = NULL;
            return nargs;
        }
        *line++ = '\0';		/* terminate current arg	 */
    }
    shell_printf ("** Too many args (max. %d) **\r\n", SHELL_MAX_ARGS);
    return (nargs);
}

 /*!
 * @brief find command form command struct's name
 */
const cmd_tbl_t * shell_find_command (const char * cmd)
{
    const cmd_tbl_t *cmdtp_temp = NULL;
    if (cmd == NULL) 
    {
        return NULL;
    }
    for(cmdtp_temp = _syscall_table_begin; cmdtp_temp < _syscall_table_end; cmdtp_temp++)
    {
        if (!strcmp(cmd, cmdtp_temp->name))
        {
            return cmdtp_temp;
        }
    }
    return NULL;
}

static void _system_function_section_init(const void* begin, const void* end)
{
    _syscall_table_begin = ( cmd_tbl_t*) begin;
    _syscall_table_end = ( cmd_tbl_t*) end;
}

void shell_init(void)
{
#ifdef __CC_ARM                 /* ARM C Compiler */
    extern const int FSymTab$$Base;
    extern const int FSymTab$$Limit;
    _system_function_section_init(&FSymTab$$Base, &FSymTab$$Limit);
    
#elif defined (__ICCARM__)      /* for IAR Compiler */
    #pragma section="FSymTab"
    _system_function_section_init(__section_begin("FSymTab"),
                               __section_end("FSymTab"));

#endif
    
}

void shell_main_loop(char * prompt)
{
    int8_t len;
    uint8_t argc;
    int8_t result;
    const cmd_tbl_t * cmdtp;
    char * argv[SHELL_MAX_ARGS];	/* NULL terminated	*/
    uint8_t debug_level_temp;
    for (;;)
    {   
        len = readline(prompt);
        if (len > 0)
        {
            //debug_level_temp = debug_level;
           // debug_level = 3;
            if ((argc = parse_line (console_buffer, argv)) == 0) 
            {
                continue;
            }
            cmdtp = shell_find_command(argv[0]);
            if ((cmdtp != NULL) && (cmdtp->cmd != NULL))
            {
                if (argc > cmdtp->maxargs)
                {
                    result = CMD_RET_USAGE;
                }
                else
                {
                    result = (cmdtp->cmd)(argc, argv);
                }
            }
            else
            {
                shell_printf("Unknown command '%s' - try 'help'\r\n", argv[0]);	
            }
            if (result == CMD_RET_USAGE)
            {
                if (cmdtp->usage != NULL)
                {
                    shell_printf("%s - %s\r\n", cmdtp->name, cmdtp->usage);
                }
                if (cmdtp->help != NULL)
                {
                    shell_printf("Usage:\r\n%s ", cmdtp->name);
                    shell_printf("%s\r\n", cmdtp->help);
                }
                else
                {
                    shell_printf ("- No additional help available.\r\n");
                }
            }
            //debug_level = debug_level_temp;
        }
        else if (len == -1)
        {
            shell_printf("<INTERRUPT>\r\n");
        }
    }
}