#include "cli_inter_preter.h"

static char  arg_str_list[CLI_IPT_ARG_NUM][CLI_IPT_ARG_STR_LEN];
unsigned int cli_ipt_arg_list[CLI_IPT_ARG_NUM][CLI_IPT_ARG_LEN];

void print_main_menu(command_entry *main_table);

void array_map_input_to_byte(unsigned char *b, unsigned int *i, unsigned int len)
{
    if (len > CLI_IPT_ARG_LEN)
        return;

    for (unsigned char index = 0; index < len; index++)
    {
        b[index] = i[index];
    }
}

int len_of_menu(command_entry *entry)
{
    int i, len = 0;
    for (i = 0; i < CLI_IPT_ENTRY_NUM_MAX; i++)
    {
        if (entry->action == NULL && entry->arg_desp == NULL && entry->arg_type == NULL && entry->desp == NULL && entry->name == NULL)
        {
            return len;
        }
        else
        {
            len++;
            entry++;
        }
    }
    return 0;
}

int cmd_to_param(const char *cmd, char *param, int param_len_max)
{
    int   state = 0, row = 0;
    char *base;

    base = param;

    while (*cmd != '\0')
    {
        if (*cmd != ' ')
        {
            if (state == 2 || state == 0)
            {
                row++;
                if (row > CLI_IPT_ARG_NUM)
                    return row;
                param = base + param_len_max * (row - 1);
                memset(param, 0, param_len_max);
                state = 1;
            }
            state  = 1;
            *param = *cmd;
            param++;
            if (param > base + param_len_max * (row)-1)
            {
                return 0;
            }
        }
        else
        {
            if (state == 1)
            {
                state = 2;
            }
        }
        cmd++;
    }
    return row;
}

void print_entry(command_entry *entry)
{
    int len, i;
    len = len_of_menu(entry);
    for (i = 0; i < len; i++)
    {
        if (entry[i].action != NULL)
        {
            CLI_PRINTF("%-30s- (%s)%s\r\n", entry[i].name, entry[i].arg_type, entry[i].desp);
        }
        else
        {
            CLI_PRINTF("%-30s- (sub_menu)%s\r\n", entry[i].name, entry[i].desp);
        }
    }
}

void print_arg_desp(command_entry *entry)
{
    int   len, i;
    char *d[] = {"<one-byte 1 or 0x01>", "<two-byte 1 or 0x01>", "<four-byte 1 or 0x01>", "<string \"abc\" >", "<array, {1,2,3}>"};
    char *desp;

    len = strlen(entry->arg_type);

    if (len == 0)
    {
        return;
    }
    for (i = 0; i < len; i++)
    {
        if (entry->arg_type[i] == 'u')
            desp = d[0];
        else if (entry->arg_type[i] == 'v')
            desp = d[1];
        else if (entry->arg_type[i] == 'w')
            desp = d[2];
        else if (entry->arg_type[i] == 's')
            desp = d[3];
        else if (entry->arg_type[i] == 'a')
            desp = d[4];
        else
            desp = "";

        if (entry->arg_desp == NULL)
        {
            CLI_PRINTF("arg%d   -   %c%s\r\n", i, entry->arg_type[i], desp);
        }
        else
        {
            CLI_PRINTF("arg%d   -   %c%s %s\r\n", i, entry->arg_type[i], desp, entry->arg_desp[i]);
        }
    }
}

static unsigned char hex_to_int(unsigned char ch)
{
    return ch - (ch >= 'a' ? 'a' - 10 : (ch >= 'A' ? 'A' - 10 : (ch <= '9' ? '0' : 0)));
}

static unsigned int string_to_unsigned_int(char *str, unsigned int *array, int param_len_max)
{
    int result = 0;
    int base   = 10;
    int i;
    int length;
    int pflag = 0;

    length = strlen(str);

    if (length == 0)
        return 1;
    if (str[0] == '-')
        pflag = 1;

    for (i = 0; i < length; i++)
    {
        unsigned char next = str[i];
        if (i == 0 && next == '-')
        {
            // do noting
        }
        else if ((next == 'x' || next == 'X') && result == 0 && (i == 1 || i == 2))
        {
            base = 16;
        }
        else
        {
            unsigned char value = hex_to_int(next);
            if (value < base)
            {
                result = result * base + value;
            }
            else
            {
                return 1;
            }
        }
    }
    if (pflag == 1)
    {
        result = ~result + 1;
    }
    *array = result;
    return 0;
}

//"s" "abc"
// "str\r\n"
int string_to_s(char *str, unsigned int *array, int param_len_max)
{
    int   i = 0, j = 0; /*, bs=0*/
    char *ps = (char *)array;

    if (str[0] != '"')
        return 1;

    j = 0;

    for (i = 0; str[i + 1] != '"';)
    {
        if (str[i + 1] == '\\')
        {
            if (str[i + 1 + 2] == 'n')
            {
                ps[j] = '\n';
                i++;
            }
            else if (str[i + 1 + 2] == 'r')
            {
                ps[j] = '\r';
                i++;
            }
            else
            {
                ps[j] = '\\';
            }
        }
        else
        {
            ps[j] = str[i + 1];
        }

        j++;

        if (j + 1 > param_len_max)
        {
            return 1;
        }
        i++;
    }
    ps[j] = 0;

    return 0;
}

// "a" {1,2,3}
int string_to_a(char *str, unsigned int *array, int param_len_max)
{
    int  i, index = 0, count = 0, flag = 0;
    char s[CLI_IPT_ARG_LEN] = {0};

    if (str[0] != '{')
        return 1;

    for (i = 0; str[i] != '\0'; i++)
    {
        if (str[i] == '{')
        {
            index = 0;
        }
        else if (str[i] == ',')
        {
            s[index] = 0;
            if (string_to_unsigned_int(s, array + count, CLI_IPT_ARG_LEN) != 0)
                return 1;
            index = 0;
            count++;
        }
        else if (str[i] == '}')
        {
            flag     = 1;
            s[index] = 0;
            if (string_to_unsigned_int(s, array + count, CLI_IPT_ARG_LEN) != 0)
                return 1;
        }
        else
        {
            s[index++] = str[i];
        }
    }
    if (flag == 0)
        return 1;

    return 0;
}

int command_action_exec(command_entry *entry, char *param, int param_len_max)
{
    int len, i, res = 0;
    len = strlen(entry->arg_type);

    // CLI_PRINTF("arg is:");

    for (i = 0; i < len; i++)
    {
        if (entry->arg_type[i] == 'u')
            res = string_to_unsigned_int(param + param_len_max * i, cli_ipt_arg_list[i], CLI_IPT_ARG_LEN);
        else if (entry->arg_type[i] == 'v')
            res = string_to_unsigned_int(param + param_len_max * i, cli_ipt_arg_list[i], CLI_IPT_ARG_LEN);
        else if (entry->arg_type[i] == 'w')
            res = string_to_unsigned_int(param + param_len_max * i, cli_ipt_arg_list[i], CLI_IPT_ARG_LEN);
        else if (entry->arg_type[i] == 's')
            res = string_to_s(param + param_len_max * i, cli_ipt_arg_list[i], CLI_IPT_ARG_LEN * sizeof(unsigned int));
        else if (entry->arg_type[i] == 'a')
            res = string_to_a(param + param_len_max * i, cli_ipt_arg_list[i], CLI_IPT_ARG_LEN);
        else
        {
        }

        if (res == 1)
        {
            return res;
        }
        // CLI_PRINTF("%d ", cli_ipt_arg_list[i][0]);
    }

    // CLI_PRINTF("\r\n");
    return res;
}

int stricmp(const char *a, const char *b)
{
    int i, len;
    if (strlen(a) != strlen(b))
        return 1;

    len = strlen(a);
    for (i = 0; i < len; i++)
    {
        if (toupper((unsigned int)a[i]) != toupper((unsigned int)b[i]))
        {
            return 1;
        }
    }

    return 0;
}

int command_match(const char *cmd, int param_len_max, int num, command_entry **entry_in, command_entry **entry_out)
{
    int i, len, res = 0;
    len = len_of_menu(*entry_in);

    for (i = 0; i < len; i++)
    {
        if (stricmp(cmd, (*entry_in)->name) == 0)
        {
            if ((*entry_in)->action != NULL)
            {
                *entry_out = *entry_in;
                res        = 1;
            }
            else
            {
                *entry_out = (command_entry *)((*entry_in)->arg_type);
                res        = 2;
            }
            break;
        }
        (*entry_in)++;
    }

    if (i == len)
    {
        res = 0;
    }
    return res;
}

int command_entry_match(command_entry *main_table, const char *cmd)
{
    static command_entry *ein, *eout, *ehint;
    int                   num, res = 2;
    char                 *plist;

    ein   = main_table;
    eout  = (command_entry *)NULL;
    ehint = ein;

    memset(arg_str_list, 0, sizeof(arg_str_list));
    num = cmd_to_param(cmd, (char *)arg_str_list, CLI_IPT_ARG_STR_LEN);

    if (num == 0)
    {
        // print_main_menu(main_table);
        CLI_PRINTF("# ");
        return 0;
    }

    plist = (char *)arg_str_list;
    while (res == 2)
    {
        res = command_match((const char *)plist, CLI_IPT_ARG_STR_LEN, num, &ein, &eout);

        if (res == 0)
        {
            // printf("commands or params unmatched!\r\n");
            print_entry(ehint);
            CLI_PRINTF("# ");
            return 0;
        }
        else if (res == 1)
        {
            if (command_action_exec(eout, plist + CLI_IPT_ARG_STR_LEN, CLI_IPT_ARG_STR_LEN) == 0)
            {
                eout->action();
            }
            else
            {
                // printf("argument error!\r\n");
                print_arg_desp(eout);
            }
        }
        else
        {
            plist = plist + CLI_IPT_ARG_STR_LEN;
            ein   = eout;
            ehint = ein;
        }
    }
    CLI_PRINTF("# ");
    return 1;
}

void print_main_menu(command_entry *main_table)
{
    print_entry(main_table);
}

int case_chg(char a)
{
    if (a >= 'A' && a <= 'Z')
        a = a - 'A' + 'a';

    return a;
}

int str_include(const char *s, const char *str)
{
    int i;

    if (strlen(s) > strlen(str))
        return -1;

    for (i = 0; i < strlen(s); i++)
    {
        if (case_chg(s[i]) != case_chg(str[i]))
            return -1;
    }

    return 0;
}

int command_entry_hint(command_entry *main_table, const char *cmd, char *out)
{

    static command_entry *ein, *eout;
    int                   num, res = 2, i, len, cnt;
    char                 *plist, tmp[CLI_IPT_ARG_STR_LEN];

    ein  = main_table;
    eout = ein;

    memset(arg_str_list, 0, sizeof(arg_str_list));
    num = cmd_to_param(cmd, (char *)arg_str_list, CLI_IPT_ARG_STR_LEN);

    if (num == 0)
    {
        // print_main_menu(main_table);
        // CLI_PRINTF("# ");
        return 0;
    }

    plist = (char *)arg_str_list;
    while (res == 2)
    {
        res = command_match((const char *)plist, CLI_IPT_ARG_STR_LEN, num, &ein, &eout);

        if (res == 0)
        {
            if (strlen(plist) == 0)
            {
                if (cmd[strlen(cmd) - 1] != ' ') //"s1" "s2"
                {
                    strcpy(out, cmd);
                    strcat(out, " ");
                    return 3;
                }
            }

            len = len_of_menu(eout);
            cnt = 0;
            strcpy(out, "");

            for (i = 0; i < len; i++)
            {
                if (str_include(plist, eout[i].name) == 0)
                {
                    cnt++;
                    strcpy(out, eout[i].name);
                    strcat(out, " ");
                }
            }
            if (cnt == 1)
            {
                strcpy(tmp, out);
                strcpy(out, "");
                for (i = 0; i < (num - 1); i++)
                {
                    strcat(out, arg_str_list[i]);
                    strcat(out, " ");
                }
                strcat(out, tmp);
                return 2;
            }
            else
            {
                return 1;
            }
        }
        else if (res == 1) // action
        {
            strcpy(out, "");
            for (i = 0; i < num; i++)
            {
                strcat(out, arg_str_list[i]);
                strcat(out, " ");

                if (strlen(out) >= ((unsigned int)CLI_IPT_ARG_STR_LEN >> 1))
                {
                    strcat(out, "...");
                    break;
                }
            }
            return 3;
        }
        else // sub_menu
        {
            plist = plist + CLI_IPT_ARG_STR_LEN;
            ein   = eout;
        }
    }

    return 1;
}