/******************************************************************************
 * file    lib/management/cli.c 
 * author  YuLiang
 * version 1.0.0
 * date    10-Sep-2021
 * brief   This file provides all the cli related operation functions.
 *
 ******************************************************************************
 * Attention
 *
 * <h2><center>&copy; COPYRIGHT(c) 2021 LandPower</center></h2>
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *   1. Redistributions of source code must retain the above copyright notice,
 *      this list of conditions and the following disclaimer.
 *   2. 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.
 *   3. Neither the name of LandPower 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.
 *
 ******************************************************************************/

/* Includes ------------------------------------------------------------------*/
/* Standard includes. */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <termios.h>
#include <pthread.h>

/* 外部程序库头文件. */
#include <readline/readline.h>
#include <readline/history.h>

/* User includes. */
#include "cli.h"
#include "list.h"

/* Private define ------------------------------------------------------------*/

/* Private macro -------------------------------------------------------------*/

/* Private typedef -----------------------------------------------------------*/

/* Private variables ---------------------------------------------------------*/
static int32_t _cli_help(const char *cmd_str);
static int32_t _cli_version(const char *cmd_str);
static int32_t _cli_exit(const char *cmd_str);

/* The definition of the "help" command.  This command is always at the front
   of the list of registered commands. */
static const cli_command_t _cli_cmd_help =
{
    "help",
    "List all command.",
    _cli_help,
    0
};

static const cli_command_t _cli_cmd_version =
{
    "version",
    "Show device version information.",
    _cli_version,
    0
};

static const cli_command_t _cli_cmd_exit =
{
    "exit",
    "Returns the upper-level node.",
    _cli_exit,
    0
};

/* The definition of the list of commands.  Commands that are registered are
   added to this list. */
static cli_node_t _cli_node[CLI_NODE_COUNT] =
{
    {"common", 0xffff, CLI_NODE_COMMON, {NULL, NULL}},
    {"Username: ", 0, CLI_NODE_PASSWORD, {NULL, NULL}},
    {"config", 0xffff, CLI_NODE_PASSWORD, {NULL, NULL}},
};

/* 保存readline命令行历史的文件. */
static const char _cli_history_file[128] = "/home/embed/.command_line_history";
/* 保存readline命令行内容的buf. */
static char *_cli_cmd_inpurt = NULL;
/* 当前cli所在的节点. */
static CLI_NODE_E _cli_now = CLI_NODE_PASSWORD;
/* 命令行用户链表. */
static struct list_head _cli_user_list;
/* cli线程pid. */
static pthread_t _cli_pid;

/* Private function prototypes -----------------------------------------------*/
extern char* version_get();
extern char* version_date_get();

static int8_t _cli_param_num_get(const char *cmd_str);
static cli_list_item_t *_cli_cmd_search(const char *cmd_str, CLI_NODE_E node);

/* Internal functions --------------------------------------------------------*/
/* 将node节点下的命令复制到cmd_list_new链表中并排序. */
static int32_t _cli_cmd_copy_for_node(struct list_head *cmd_list_new, CLI_NODE_E node)
{
    struct list_head *cmd_list = NULL;
    cli_list_item_t *cmd_item = NULL;    
    cli_list_item_t *cmd_item_new = NULL;
    cli_list_item_t *cmd_item_temp = NULL;
    cli_list_item_t *cmd_item_next = NULL;
    int32_t rv = E_NONE;
    
    cmd_list = &_cli_node[node].list;
    list_for_each_entry(cmd_item, cmd_list, list)
    {
        /* 复制命令 */
        cmd_item_new = XMALLOC(MTYPE_CLI, sizeof(cli_list_item_t));
        if (NULL == cmd_item_new)
        {
            goto CMD_COPY_FOR_NODE_ERROR;
        }
        memcpy(cmd_item_new, cmd_item, sizeof(cli_list_item_t));

        /* 命令排序 */
        list_for_each_entry_reverse(cmd_item_temp, cmd_list_new, list)
        {
            if (strcmp(cmd_item_new->cmd->cmd_str, cmd_item_temp->cmd->cmd_str) > 0)
            {
                list_add(&cmd_item_new->list, &cmd_item_temp->list);
                break;
            }
        }

        /* 最小的命令放在链表头 */
        if (&cmd_item_temp->list == cmd_list_new)
        {
            list_add(&cmd_item_new->list, &cmd_item_temp->list);
        }
    }

    return rv;

    /* 错误回滚已经申请的内存 */
CMD_COPY_FOR_NODE_ERROR:
    list_for_each_entry_safe(cmd_item_temp, cmd_item_next, cmd_list_new, list)
    {
        list_del(&cmd_item_temp->list);
        XFREE(MTYPE_CLI, cmd_item_temp);
    }
    return rv;

}

/* The callback function tyhat is executed when "help" is entered.  This is the
   only default command that is always present. */
static int32_t _cli_help(const char *cmd_str)
{
    struct list_head cmd_list_new;
    cli_list_item_t *cmd_item_temp = NULL;
    cli_list_item_t *cmd_item_next = NULL;

    INIT_LIST_HEAD(&cmd_list_new);

    /* 复制命令 */
    LD_E_RETURN(DBG_M_CLI, _cli_cmd_copy_for_node(&cmd_list_new, _cli_now));
    LD_E_RETURN(DBG_M_CLI, _cli_cmd_copy_for_node(&cmd_list_new, CLI_NODE_COMMON));

    /* 打印信息 */
    list_for_each_entry(cmd_item_temp, &cmd_list_new, list)
    {
        printf("%-32s", cmd_item_temp->cmd->cmd_str);
        printf("%s\r\n", cmd_item_temp->cmd->help_str);
    }
    printf("\n");

    /* 释放内存 */
    list_for_each_entry_safe(cmd_item_temp, cmd_item_next, &cmd_list_new, list)
    {
        list_del(&cmd_item_temp->list);
        XFREE(MTYPE_CLI, cmd_item_temp);
    }

    return E_NONE;
}

static int32_t _cli_version(const char *cmd_str)
{
    printf("Welcome To Power IoT Application, By LandPower Embed Application Team.\r\n");
    printf("Copyright(c) 2021 LandPower. All rights reserved.\r\n");
    printf("Software version %s, compile time is %s.\r\n\n", version_get(), version_date_get());

    return E_NONE;
}

static int32_t _cli_exit(const char *cmd_str)
{
    cli_quit_node();

    return E_NONE;
}

/* Return the number of parameters that follow the command name. */
static int8_t _cli_param_num_get(const char *cmd_str)
{
    int8_t num = 0;
    bool is_end = FALSE;

    /* Count the number of space delimited words in pcCommandString. */
    while(*cmd_str != 0)
    {
        if (' ' == (*cmd_str))
        {
            if (is_end != TRUE)
            {
                num++;
                is_end = TRUE;
            }
        }
        else
        {
            is_end = FALSE;
        }

        cmd_str++;
    }

    /* If the command string ended with spaces, then there will have been too
       many parameters counted. */
    if( is_end == TRUE )
    {
        num--;
    }

    /* The value returned is one less than the number of space delimited words,
       as the first word should be the command itself. */
    return num;
}

/* 命令行查找 */
static cli_list_item_t *_cli_cmd_search(const char *cmd_str, CLI_NODE_E node)
{
    struct list_head *cmd_list = &_cli_node[node].list;
    cli_list_item_t *cmd_item = NULL;
    const char *cmd_str_register = NULL;
    size_t cmd_str_len = 0;

    /* Search for the command string in the list of registered commands. */
    list_for_each_entry(cmd_item, cmd_list, list)
    {
        cmd_str_register = cmd_item->cmd->cmd_str;
        cmd_str_len = strlen(cmd_str_register);

        /* To ensure the string lengths match exactly, so as not to pick up
           a sub-string of a longer command, check the byte after the expected
           end of the string is either the end of the string or a space before
           a parameter. */
        if ((cmd_str[cmd_str_len] != ' ') && (cmd_str[cmd_str_len] != 0x00))
        {      
            continue;
        }

        if (0 == strncmp(cmd_str, cmd_str_register, cmd_str_len))
        {
            return cmd_item;
        }
    }

    return NULL;
}

/* 获取命令行前缀 */
char *_cli_prefix_get(char *host)
{
    static char buf[100] = {0};
    
    if (0xffff == _cli_node[_cli_now].index)
    {
        snprintf(buf, sizeof(buf), "%s(%s)> ", host, _cli_node[_cli_now].name);
    }
    else
    {
        snprintf(buf, sizeof(buf), "%s(%s-%d)> ", host, _cli_node[_cli_now].name, _cli_node[_cli_now].index);
    }

    return buf;
}

/* readline初始化 */
void _cli_readline_init(void)
{
    /* 修改自己的按键操作. */
    //rl_bind_key('?', (rl_command_func_t *)vtysh_rl_question);
    //rl_bind_key('\t', (rl_command_func_t *)vtysh_rl_completion);
    /* do not append space after completion. It will be appended
     * in new_completion() function explicitly. */
    rl_completion_append_character = '\0';
    
    read_history(_cli_history_file);
}

/* 密码处理函数 */
static int32_t _cli_process_password(const char * const cmd_str)
{
    static cli_user_t user_login;
    cli_user_t *user = NULL;

    /* 表示在输入username */
    if (0 == _cli_node[_cli_now].index)
    {
        /* 保存username,并进入输入password模式 */
        _cli_node[_cli_now].index = 1;
        snprintf(_cli_node[_cli_now].name, CLI_NODE_MAME_LEN, "Password: ");
        snprintf(user_login.username, CLI_USERNAME_LEN, "%s", cmd_str);
        
        return E_NONE;
    }
    else
    {
        _cli_node[_cli_now].index = 0;
        snprintf(_cli_node[_cli_now].name, CLI_NODE_MAME_LEN, "Username: ");
            
        /* 保存password,比较输入正确性 */
        snprintf(user_login.password, CLI_USERNAME_LEN, "%s", cmd_str);
        list_for_each_entry(user, &_cli_user_list, list)
        {  
            if (0 == strcmp(user_login.username, user->username)
                && 0 == strcmp(user_login.password, user->password))
            {
                /* 找到匹配 */
                printf("\r\n\n");
                cli_entry_node(CLI_NODE_CONFIG, 0xffff);
                return E_NONE;
            }
        }
    }

    return E_NOT_FOUND;
}

/* 添加一个新用户 */
int32_t _cli_user_add(const cli_user_t* const user)
{
    cli_user_t *user_new = NULL;

    user_new = XMALLOC(MTYPE_CLI, sizeof(cli_user_t));
    if (NULL == user_new)
    {
        return E_MEM;
    }
    memcpy(user_new, user, sizeof(cli_user_t));

    list_add(&user_new->list, &_cli_user_list);

    return E_NONE;
}

void *_cli_handle(void *arg)
{
    char *cmd = NULL;
    
    /* 主循环 */
    for(;;)
    {
        /* 输入命令. */
        cmd = cli_shell_gets();
        if (NULL == cmd)
        {
            continue;
        }

        /* 执行命令. */
        cli_process_command(cmd);
    }
}

/* Interface functions -------------------------------------------------------*/
/* description: 命令行注册函数.
   param:   cmd     --  要注册的命令行结构体
            node    --  要注册到的节点
   return:  E_NONE  --  OK
            其他      --  ERROR */
int32_t cli_register_command(const cli_command_t* const cmd, CLI_NODE_E node)
{
    struct list_head *cmd_list = &_cli_node[node].list;
    cli_list_item_t *cmd_item = NULL;

    /* Check the parameter is not NULL. */
    if (NULL == cmd)
    {
        return E_NULL;
    }

    /* Create a new list item that will reference the command being registered. */
    cmd_item = XMALLOC(MTYPE_CLI, sizeof(cli_list_item_t));
    if (NULL == cmd_item)
    {
        return E_MEM;
    }

    /* Reference the command being registered from the newly created list item. */
    cmd_item->cmd = cmd;

    /* The new list item will get added to the end of the list. */
    list_add(&cmd_item->list, cmd_list);

    return E_NONE;
}

/* description: 命令行注册函数.
   param:   cmd_str --  要执行的命令行
   return:  E_NONE  --  OK
            其他      --  ERROR */
int32_t cli_process_command(const char * const cmd_str)
{
    static const cli_list_item_t *cmd_item = NULL;
    int32_t rv = E_NONE;

    /* 输入密码的过程不同于执行命令的过程 */
    if (CLI_NODE_PASSWORD == _cli_now)
    {
        if (_cli_process_password(cmd_str) != E_NONE)
        {
            printf("Username or password is not match!!!\r\n\n");
            return E_NOT_FOUND;
        }
        return E_NONE;
    }

    cmd_item = _cli_cmd_search(cmd_str, (CLI_NODE_E)_cli_now);
    if (NULL == cmd_item)
    {
        cmd_item = _cli_cmd_search(cmd_str, CLI_NODE_COMMON);
    }

    /* The command has been found.  Check it has the expected
       number of parameters.  If cExpectedNumberOfParameters is -1,
       then there could be a variable number of parameters and no
       check is made. */
    if (cmd_item != NULL)
    {
        if (cmd_item->cmd->param_num >= 0
            && _cli_param_num_get(cmd_str) > cmd_item->cmd->param_num )
        {
            rv = E_BAD_PARAM;
        }
    }

    if ((cmd_item != NULL) && (rv != E_NONE))
    {
        /* The command was found, but the number of parameters with the command
        was incorrect. */
        printf("%s", cli_param_erro);
        cmd_item = NULL;
    }
    else if(cmd_item != NULL)
    {
        /* Call the callback function that is registered to this command. */
        rv = cmd_item->cmd->function(cmd_str);
        if (rv != E_NONE)
        {
            printf("Command(%s) return %d.\r\n\n", cmd_item->cmd->cmd_str, rv);
        }
    }
    else
    {
        /* pxCommand was NULL, the command was not found. */
        printf("Command not recognised.\r\n\n");
        rv = E_NOT_FOUND;
    }

    return rv;
}

/* description: 进入命令行节点.
   param:   cmd_str     --  命令字符串
            param_index --  需要获取的参数索引
            param_len   --  获取的参数长度
   return:  char*   --  获取的参数字符串 */
const char *cli_parameter_get(const char *cmd_str, int32_t param_index, OUT int32_t *param_len)
{
    int32_t index = 0;
    const char *param_str = NULL;

    *param_len = 0;

    while(index < param_index)
    {
        /* Index the character pointer past the current word.  If this is the start
        of the command string then the first word is the command itself. */
        while(((*cmd_str) != 0x00 ) && ((*cmd_str) != ' '))
        {
            cmd_str++;
        }

        /* Find the start of the next string. */
        while(((*cmd_str) != 0x00) && (' ' == (*cmd_str)))
        {
            cmd_str++;
        }

        /* Was a string found? */
        if (*cmd_str != 0x00)
        {
            /* Is this the start of the required parameter? */
            index++;

            if (index == param_index)
            {
                /* How long is the parameter? */
                param_str = cmd_str;
                while(((*cmd_str) != 0x00 ) && ((*cmd_str) != ' '))
                {
                    (*param_len)++;
                    cmd_str++;
                }

                if (*param_len == 0 || *param_len >= CLI_PARAM_LEN)
                {
                    param_str = NULL;
                }

                break;
            }
        }
        else
        {
            break;
        }
    }

    return param_str;
}

/* description: 进入命令行节点.
   param:   node    --  节点号
            index   --  节点内部索引
   return:  E_NONE  --  OK
            其他      --  ERROR */
int32_t cli_entry_node(CLI_NODE_E node, int16_t index)
{
    if (node >= CLI_NODE_COUNT)
    {
        return E_BAD_PARAM;
    }

    _cli_now = node;
    _cli_node[_cli_now].index = index;
    
    return E_NONE;
}

/* description: 返回上一级节点.
   param:
   return: */
void cli_quit_node(void)
{
    _cli_now = _cli_node[_cli_now].upper_node;
}

/* description: 命令行读取.
   param:
   return:  char*   --  读取的一行命令 */
char *cli_shell_gets(void)
{
    HIST_ENTRY *last = NULL;
    struct termios new_setting,init_setting;
    
    /* readline要求自己释放其返回的buf. */
    if (_cli_cmd_inpurt)
    {
        free(_cli_cmd_inpurt);
        _cli_cmd_inpurt = NULL;
    }
     
    /* 获取一行命令. */
    if (CLI_NODE_PASSWORD == _cli_now)        
    {
        /* 如果是输入密码则关闭回显. */
        if (1 == _cli_node[_cli_now].index)
        {
            tcgetattr(0, &init_setting);
            new_setting = init_setting;
            new_setting.c_lflag &= ~ECHO;
            tcsetattr(0,TCSANOW,&new_setting);
        }

        _cli_cmd_inpurt = readline(_cli_node[_cli_now].name);

        if (1 == _cli_node[_cli_now].index)
        {
            tcsetattr(0,TCSANOW,&init_setting);
        }
    }
    else
    {
        _cli_cmd_inpurt = readline(_cli_prefix_get("Power_IoT"));
        /* 如果命令有效记录历史. */
        if (_cli_cmd_inpurt && *_cli_cmd_inpurt)
        {
            using_history();
            last = previous_history();
            if (!last || strcmp (last->line, _cli_cmd_inpurt) != 0)
            {
                add_history(_cli_cmd_inpurt);
                append_history(1, _cli_history_file);
            }
        }
    }

    //if (strlen(_cli_cmd_inpurt) > 0)
    //{
    //    log_warn(LOG_CLI, "\"%s\" is input.", _cli_cmd_inpurt);
    //}
    
    return _cli_cmd_inpurt;
}

/* description: 命令行初始函数.
   param:
   return: */
int32_t cli_init(void)
{
    struct sched_param param;
    pthread_attr_t attr;
    cli_user_t user_default;
    int32_t i = 0;
    int32_t rv = 0;

    /* 初始化readline */
    _cli_readline_init();

    /* 初始化命令节点数据链表 */
    for(i = 0; i < CLI_NODE_COUNT; i++)
    {
        INIT_LIST_HEAD(&_cli_node[i].list);
    }

    /* 注册公有命令 */
    cli_register_command(&_cli_cmd_help, CLI_NODE_COMMON);
    cli_register_command(&_cli_cmd_version, CLI_NODE_COMMON);
    cli_register_command(&_cli_cmd_exit, CLI_NODE_COMMON);

    /* 初始化用户列表 */
    INIT_LIST_HEAD(&_cli_user_list);

    /* 添加默认用户 */
    memset(&user_default, 0, sizeof(cli_user_t));
    snprintf(user_default.username, CLI_USERNAME_LEN, CLI_USERNAME_DEFAULT);
    snprintf(user_default.password, CLI_USERNAME_LEN, CLI_PASSWORD_DEFAULT);
    _cli_user_add(&user_default);

    /* 配置线程RR调度,优先级50 */
    pthread_attr_init(&attr);
    param.sched_priority = 10;
    pthread_attr_setschedpolicy(&attr, SCHED_RR);
    pthread_attr_setschedparam(&attr, &param);
    pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);

    rv = pthread_create(&_cli_pid, &attr, _cli_handle, NULL);
    if (rv != 0)
    {
        DBG(DBG_M_CLI, "Can't create pthread %d\r\n", rv);
    }
    else
    {
        thread_m_add("CLI", _cli_pid);
    }
    pthread_attr_destroy(&attr);

    return E_NONE;
}
/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/
