/*
 * Copyright (c) 2013 - 2014, Freescale Semiconductor, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * o Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * o Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 *
 * o Neither the name of Freescale Semiconductor, Inc. nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <string.h>

#include "cmd_handle.h"
#include "char_handle.h"
#include "fsl_misc_utilities.h"

extern cmd_tbl_t sf_cmd_tbl[SPI_FLASH_FUNC_NUM];

#define MAX_DELAY_STOP_STR	32
#define CB_SIZE			64
#define MAX_ARGS		4

#undef DEBUG_PARSER

char console_buffer[CB_SIZE];        /* console I/O buffer */

static char *delete_char (char *buffer, char *p, int32_t *colp, int32_t *np, int32_t plen);
static char erase_seq[] = "\b \b";        /* erase sequence */
static char tab_seq[] = "        ";       /* used to expand TABs */

/****************************************************************************/

static int32_t readline(const char *const prompt)
{
    char *p = console_buffer;
    char *p_buf = p;
    int32_t   n = 0;              /* buffer index */
    int32_t   plen = 0;           /* prompt length */
    int32_t   col;                /* output column cnt */
    int8_t  c;

    /* print prompt */
    if (prompt)
    {
        plen = strlen((char const *)prompt);
        cmd_puts(prompt);
    }
    col = plen;

    for (;;)
    {
        c = 0;
        getc((uint8_t *)&c);

        /* Special character handling */
        switch (c)
        {
        case '\r':                /* Enter */
        case '\n':
            *p = '\0';
            cmd_puts("\r\n");
            return (p - p_buf);

        case '\0':                /* nul */
            continue;

        case 0x03:                /* ^C - break */
            p_buf[0] = '\0';      /* discard input */
            return (1);

        case 0x15:                /* ^U - erase line */
            while (col > plen)
	    {
                cmd_puts(erase_seq);
                --col;
            }
            p = p_buf;
            n = 0;
            continue;

        case 0x17:                /* ^W - erase word */
            p = delete_char(p_buf, p, &col, &n, plen);
            while ((n > 0) && (*p != ' '))
            {
                p = delete_char(p_buf, p, &col, &n, plen);
            }
            continue;

        case 0x08:                /* ^H  - backspace */
        case 0x7F:                /* DEL - backspace */
            p = delete_char(p_buf, p, &col, &n, plen);
            continue;

        default:
            /* Must be a normal character then */
            if (n < CB_SIZE - 2)
            {
                if (c == '\t')
                {
                    /* expand TABs */
                    cmd_puts(tab_seq + (col & 07));
                    col += 8 - (col & 07);
                } else {
                    ++col;        /* echo input */
                    putc(c);
                }
                *p++ = c;
                ++n;
            } else {              /* Buffer full */
                putc('\a');
            }
        }
    }
}

/****************************************************************************/

static char *delete_char(char *buffer, char *p, int32_t *colp, int32_t *np, int32_t plen)
{
    char *s;

    if (*np == 0)
    {
        return (p);
    }

    if (*(--p) == '\t')
    {
        /* will retype the whole line */
        while (*colp > plen)
        {
            cmd_puts(erase_seq);
            (*colp)--;
        }
        for (s = buffer; s < p; ++s)
        {
            if (*s == '\t')
	    {
                cmd_puts(tab_seq+((*colp) & 07));
                *colp += 8 - ((*colp) & 07);
            } else {
                ++(*colp);
                putc(*s);
            }
        }
    } else {
        cmd_puts(erase_seq);
        (*colp)--;
    }
    (*np)--;

    return (p);
}

/****************************************************************************/

static int parse_line(char *line, char *argv[])
{
    int32_t nargs = 0;

#ifdef DEBUG_PARSER
    printf("parse_line: \"%s\"\r\n", line);
#endif
    while (nargs < MAX_ARGS)
    {

        /* skip any white space */
        while ((*line == ' ') || (*line == '\t'))
	{
            ++line;
        }

        if (*line == '\0')
	{
            /* end of line, no more args */
            argv[nargs] = NULL;
#ifdef DEBUG_PARSER
            printf("parse_line: nargs=%d\r\n", nargs);
#endif
            return (nargs);
        }

        argv[nargs++] = line;    /* begin of argument string */

        /* find end of string */
        while (*line && (*line != ' ') && (*line != '\t'))
        {
            ++line;
        }

        if (*line == '\0')
        {
            /* end of line, no more args */
            argv[nargs] = NULL;
#ifdef DEBUG_PARSER
            printf("parse_line: nargs=%d\r\n", nargs);
#endif
            return (nargs);
        }

        *line++ = '\0';        /* terminate current arg */
    }

    printf("** Too many args (max. %d) **\r\n", MAX_ARGS);

#ifdef DEBUG_PARSER
    printf("parse_line: nargs=%d\r\n", nargs);
#endif
    return (nargs);
}

static cmd_tbl_t *find_cmd(const char *cmd, cmd_tbl_t *table, int32_t table_len)
{
    cmd_tbl_t *cmdtp;
    cmd_tbl_t *cmdtp_temp = table;    /* Init value */
    const char *p;
    int32_t len;
    int32_t n_found = 0;

    len = ((p = strchr(cmd, '.')) == NULL) ? strlen (cmd) : (p - cmd);
    for (cmdtp = table;
        cmdtp != table + table_len;
        cmdtp++)
    {
        if (strncmp((char const *)cmd, (char const *)cmdtp->name, strlen((char const *)cmd)) == 0)
        {
            if (len == strlen((char const *)cmdtp->name))
	    {
                return cmdtp;    /* full match */
	    }

            cmdtp_temp = cmdtp;    /* abbreviated command ? */
            n_found++;
        }
    }
    if (n_found == 1)            /* exactly one match */
    {
        return cmdtp_temp;
    }

    return NULL;    /* not found or ambiguous command */
}

/*!
 * @brief Run command.
 *
 * WARNING:
 *
 * We must create a temporary copy of the command since the command we get
 * may be the result from getenv(), which returns a pointer directly to
 * the environment data, which may change magicly when the command we run
 * creates or modifies environment variables (like "bootp" does).
 *
 * @param[in] cmd name string of command.
 * @param[in] flag not used yet. 
 *
 * @return
 *    1  - command executed, repeatable
 *    0  - command executed but not repeatable, interrupted commands are
 *         always considered not repeatable
 *    -1 - not executed (unrecognized, bootd recursion or too many args)
 *           (If cmd is NULL or "" or longer than CONFIG_SYS_CBSIZE-1 it is
 *           considered unrecognized)
 */
static int32_t run_command(const char *cmd, int32_t flag)
{
    cmd_tbl_t *cmdtp;
    char *argv[MAX_ARGS + 1];    /* NULL terminated */
    int32_t argc;

#ifdef DEBUG_PARSER
    printf("[RUN_COMMAND] cmd[%p]=\"", cmd);
    cmd_puts(cmd ? cmd : "NULL");    /* use puts - string may be loooong */
    cmd_puts("\"\r\n");
#endif

    if (!cmd || !*cmd)
    {
        return 1;    /* empty command */
    }

    if (strlen(cmd) >= CB_SIZE)
    {
        cmd_puts("## Command too long!\r\n");
        return 1;
    }

    /*
     * Process separators and check for invalid
     * repeatable commands
     */

#ifdef DEBUG_PARSER
    printf("[PROCESS_SEPARATORS] %s\r\n", cmd);
#endif

    /* Extract arguments */
    if ((argc = parse_line((char *)cmd, argv)) == 0)
    {
        return 1;
    }

    /* Look up command in command table */
    if ((cmdtp = find_cmd(argv[0], sf_cmd_tbl, (ARRAY_SIZE(sf_cmd_tbl)))) == NULL)
    {
        printf("Unknown command '%s' - try 'help'\r\n", argv[0]);
        return 1;
    }

    /* found - check max args */
    if (argc > cmdtp->maxargs)
    {
        return 1;
    }

    /* OK - call function to do the command */
    if ((cmdtp->cmd)(argc, argv) != 0)
    {
        return 1;
    }

    return 0;
}

void cmd_handle_loop(void)
{
    static char lastcommand[CB_SIZE] = { 0 };
    int32_t len;
    int32_t rc = 1;
    int32_t flag;

    /* Main Loop for Monitor Command Processing */
    for (;;)
    {
        len = readline("SF Test >");

        flag = 0;    /* assume no special flags for now */

        if (len == -1)
	{
            cmd_puts("<INTERRUPT>\r\n");
	}
        else
	{
            if (len > 0)
            {
                strcpy(lastcommand, console_buffer);
            }
            rc = run_command(lastcommand, flag);
	}

        if (rc <= 0)
        {
            /* invalid command or not repeatable, forget it */
            lastcommand[0] = 0;
        }
    }
}

/****************************************************************************/

