/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

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

#include "os/mynewt.h"
#include "console/console.h"
#include "streamer/streamer.h"
#include "modlog/modlog.h"
#include "shell/shell.h"
#include "shell_priv.h"
#include "os/link_tables.h"

#define SHELL_PROMPT "shell"

static size_t
shell_mod_std_count(shell_mod_t mod)
{
    const struct shell_mod_std *std_mod = (const struct shell_mod_std *)mod;
    return std_mod->commands_end - std_mod->commands;
}

static shell_cmd_t
shell_mod_std_get(shell_mod_t mod, size_t ix)
{
    const struct shell_mod_std *std_mod = (const struct shell_mod_std *)mod;
    size_t limit = std_mod->commands_end - std_mod->commands;
    return ix < limit ? &std_mod->commands[ix] : NULL;
}

const struct shell_mod_ops shell_mod_std_ops = {
    .count = shell_mod_std_count,
    .get = shell_mod_std_get,
};

static struct shell_mod_std registered_shell_modules[MYNEWT_VAL(SHELL_MAX_MODULES)];

static size_t num_of_shell_entities;

#if MYNEWT_VAL(SHELL_COMPAT)
#define SHELL_COMPAT_MODULE_NAME "compat"
static struct shell_cmd registered_compat_commands[MYNEWT_VAL(SHELL_MAX_COMPAT_COMMANDS)];
static size_t num_compat_commands;

static size_t
shell_mod_compat_count(shell_mod_t mod)
{
    return shell_mod_std_count(mod) + num_of_shell_entities;
}

static shell_cmd_t
shell_mod_compat_get(shell_mod_t mod, size_t ix)
{
    size_t linked_commands = shell_mod_std_count(mod);

    if (ix < linked_commands) {
        return shell_mod_std_get(mod, ix);
    } else if (MYNEWT_VAL(SHELL_MAX_MODULES) > 0) {
        ix -= linked_commands;
        if (ix < num_compat_commands) {
            return &registered_compat_commands[ix];
        }
    }

    return NULL;
}

const shell_mod_ops_t shell_mod_compat_ops = {
    .count = shell_mod_compat_count,
    .get = shell_mod_compat_get,
};

LINK_TABLE(struct shell_cmd, compat_commands)
const struct shell_mod_std shell_mod_compat = {
    .mod = {
        .name = "compat",
        .ops = &shell_mod_compat_ops,
    },
    .commands = LINK_TABLE_START(compat_commands),
    .commands_end = LINK_TABLE_END(compat_commands),
};
/* This pointer goes to link table, it's the pointer to actual shell_mod_std */
LINK_TABLE_ELEMENT_REF(shell_modules, compat, shell_mod_compat.mod)

static const char *prompt;
/* Set default module */
static const struct shell_mod *default_module = &shell_mod_compat.mod;

static struct shell_cmd app_cmd;
static shell_prompt_function_t app_prompt_handler;

/* Shared queue for shell events to be processed */
static struct os_eventq *shell_evq;
/* Queue for available shell events */
static struct os_event shell_console_ev[MYNEWT_VAL(SHELL_MAX_CMD_QUEUED)];
static struct console_input buf[MYNEWT_VAL(SHELL_MAX_CMD_QUEUED)];

void
shell_evq_set(struct os_eventq *evq)
{
    shell_evq = evq;
    console_line_queue_set(shell_evq);
}

static const char *
get_prompt(void)
{
    const char *str;

    if (app_prompt_handler) {

        str = app_prompt_handler();
        if (str) {
            return str;
        }
    }

    if (default_module) {
        return default_module->name;
    }

    return prompt;
}

static void
print_prompt(const char *line)
{
    char full_prompt[30];
    strcpy(full_prompt, get_prompt());
    strcat(full_prompt, MYNEWT_VAL(SHELL_PROMPT_SUFFIX));
    console_prompt_set(full_prompt, line);
}

static void
print_prompt_if_console(struct streamer *streamer)
{
    if (streamer == streamer_console_get()) {
        print_prompt(NULL);
    }
}

static size_t
line2argv(char *str, char *argv[], size_t size, struct streamer *streamer)
{
    size_t argc = 0;
    const char *limit = str + strlen(str);
    char separator = ' ';
    bool in_arg = false;

    for (; str < limit; ++str) {
        if (in_arg) {
            if (*str == separator) {
                *str = '\0';
                in_arg = false;
                separator = ' ';
            }
        } else {
            if (*str == ' ') {
                continue;
            }
            if (*str == '"' && (str + 1 < limit)) {
                ++str;
                separator = '"';
            }
            argv[argc++] = str;
            in_arg = true;
            if (argc == size) {
                streamer_printf(streamer, "Too many parameters (max %zu)\n", size - 1);
                return 0;
            }
        }
    }

    /* keep it POSIX style where argv[argc] is required to be NULL */
    argv[argc] = NULL;

    return argc;
}

static shell_mod_t
shell_module_get(size_t ix)
{
    size_t build_time_module_count = shell_modules_size();

    if (ix < build_time_module_count) {
        return shell_modules[ix];
    } else if (MYNEWT_VAL_SHELL_MAX_MODULES > 0) {
        ix -= build_time_module_count;
        if (ix < num_of_shell_entities) {
            return (shell_mod_t)&registered_shell_modules[ix];
        }
    }
    return NULL;
}

static bool
module_str_matches(shell_mod_t mod, const char *str, int len)
{
    if (len < 0) {
        return strcmp(str, mod->name) == 0;
    } else {
        return strncmp(str, mod->name, len) == 0;
    }
}

const struct shell_mod *
get_destination_module(const char *module_str, int len)
{
    shell_mod_t mod;

    for (size_t i = 0; NULL != (mod = shell_module_get(i)); ++i) {
        if (module_str_matches(mod, module_str, len)) {
            return mod;
        }
    }

    return NULL;
}

/* For a specific command: argv[0] = module name, argv[1] = command name
 * If a default module was selected: argv[0] = command name
 */
static const char *
get_command_and_module(char *argv[], const struct shell_mod **module,
                       struct streamer *streamer)
{
    const struct shell_mod *def_module = default_module;
    const char *first_arg = argv[0];
    *module = NULL;

    if (!argv[0]) {
        streamer_printf(streamer, "Unrecognized command\n");
        return NULL;
    }

    if (first_arg[0] == '/') {
        first_arg++;
        def_module = NULL;
    }

    if (def_module == NULL) {
        if (!argv[1] || argv[1][0] == '\0') {
            streamer_printf(streamer, "Unrecognized command: %s\n", argv[0]);
            return NULL;
        }

        *module = get_destination_module(first_arg, -1);
        if (*module == NULL) {
            streamer_printf(streamer, "Illegal module %s\n", argv[0]);
            return NULL;
        }

        return argv[1];
    }

    *module = def_module;
    return argv[0];
}

static void
print_command_params(const struct shell_cmd *shell_cmd, struct streamer *streamer)
{
	int i;

	if (!(shell_cmd->help && shell_cmd->help->params)) {
		return;
	}

	for (i = 0; shell_cmd->help->params[i].param_name; i++) {
		streamer_printf(streamer, "%-30s%s\n",
                        shell_cmd->help->params[i].param_name,
			            shell_cmd->help->params[i].help);
	}
}

static int
show_cmd_help(char *argv[], struct streamer *streamer)
{
    const char *command = NULL;
    const struct shell_mod *shell_module = NULL;
    const struct shell_cmd *cmd;
    int i;

    command = get_command_and_module(argv, &shell_module, streamer);
    if ((shell_module == NULL) || (command == NULL)) {
        return 0;
    }

    for (i = 0; (cmd = shell_mod_command(shell_module, i)) != NULL; ++i) {

        if (!strcmp(command, cmd->sc_cmd)) {
            if (!cmd->help || (!cmd->help->summary &&
                               !cmd->help->usage &&
                               !cmd->help->params)) {
                streamer_printf(streamer, "(no help available)\n");
                return 0;
            }

            if (cmd->help->summary) {
                streamer_printf(streamer, "Summary:\n");
                streamer_printf(streamer, "%s\n", cmd->help->summary);
            }

            if (cmd->help->usage) {
                streamer_printf(streamer, "Usage:\n");
                streamer_printf(streamer, "%s\n", cmd->help->usage);
            }

            if (cmd->help->params) {
                streamer_printf(streamer, "Parameters:\n");
                print_command_params(cmd, streamer);
            }

            return 0;
        }
    }

    streamer_printf(streamer, "Unrecognized command: %s\n", argv[0]);
    return 0;
}

static void
print_modules(struct streamer *streamer)
{
    LINK_TABLE_FOREACH(mod, shell_modules) {
        streamer_printf(streamer, "%s\n", (*mod)->name);
    }
}

static void
print_module_commands(const struct shell_mod *shell_module, struct streamer *streamer)
{
    const struct shell_cmd *cmd;
    int i;

    for (i = 0; NULL != (cmd = shell_mod_command(shell_module, i)); ++i) {
        streamer_printf(streamer, "%-30s", cmd->sc_cmd);
        if (cmd->help && cmd->help->summary) {
            streamer_printf(streamer, "%s", cmd->help->summary);
        }
        streamer_printf(streamer, "\n");
    }
}

static int
show_help(const struct shell_cmd *cmd, int argc, char *argv[],
          struct streamer *streamer)
{
    const struct shell_mod *module;

    /* help per command */
    if ((argc > 2) || ((default_module != NULL) && (argc == 2))) {
        return show_cmd_help(&argv[1], streamer);
    }

    /* help per module */
    if ((argc == 2) || ((default_module != NULL) && (argc == 1))) {
        if (default_module == NULL) {
            module = get_destination_module(argv[1], -1);
            if (module == NULL) {
                streamer_printf(streamer, "Illegal module %s\n", argv[1]);
                return 0;
            }
        } else {
            module = default_module;
        }

        print_module_commands(module, streamer);
    } else { /* help for all entities */
        streamer_printf(streamer, "Available modules:\n");
        print_modules(streamer);
        streamer_printf(streamer,
                        "To select a module, enter 'select <module name>'.\n");
    }

    return 0;
}

MAKE_SHELL_EXT_CMD(help, show_help, NULL)

static int
set_default_module(const char *name)
{
    const struct shell_mod *module;

    module = get_destination_module(name, -1);

    if (module == NULL) {
        return -1;
    }

    default_module = module;

    return 0;
}

static int
select_module(const struct shell_cmd *cmd, int argc, char *argv[],
              struct streamer *streamer)
{
    if (argc == 1) {
        default_module = NULL;
    } else {
        set_default_module(argv[1]);
    }

    return 0;
}

MAKE_SHELL_EXT_CMD(select, select_module, NULL)

static const struct shell_cmd *
shell_find_cmd(int argc, char *argv[], struct streamer *streamer)
{
    const char *first_string = argv[0];
    const struct shell_mod *def_module = default_module;
    const struct shell_mod *shell_module;
    const char *command;
    const struct shell_cmd *cmd;
    int i;

    if (!first_string || first_string[0] == '\0') {
        streamer_printf(streamer, "Illegal parameter\n");
        return NULL;
    }

    if (first_string[0] == '/') {
        first_string++;
        def_module = NULL;
    }

    if (!strcmp(first_string, "help")) {
        return &shell_cmd_compat_help;
    }

    if (!strcmp(first_string, "select")) {
        return &shell_cmd_compat_select;
    }

    if ((argc == 1) && (def_module == NULL)) {
        streamer_printf(streamer, "Missing parameter\n");
        return NULL;
    }

    command = get_command_and_module(argv, &shell_module, streamer);
    if ((shell_module == NULL) || (command == NULL)) {
        return NULL;
    }

    for (i = 0; NULL != (cmd = shell_mod_command(shell_module, i)); ++i) {
        if (!strcmp(command, cmd->sc_cmd)) {
            return cmd;
        }
    }

    return NULL;
}

int
shell_exec(int argc, char **argv, struct streamer *streamer)
{
    const struct shell_cmd *cmd;
    size_t argc_offset = 0;
    int rc;
    const struct shell_mod *def_module = default_module;

    cmd = shell_find_cmd(argc, argv, streamer);
    if (!cmd) {
        if (app_cmd.sc_cmd_func != NULL) {
            cmd = &app_cmd;
        } else {
            streamer_printf(streamer, "Unrecognized command: %s\n", argv[0]);
            streamer_printf(streamer,
                            "Type 'help' for list of available commands\n");
            print_prompt_if_console(streamer);
            return SYS_ENOENT;
        }
    }

    if (argv[0][0] == '/') {
        def_module = NULL;
    }
    /* Allow invoking a cmd with module name as a prefix; a command should
     * not know how it was invoked (with or without prefix)
     */
    if (def_module == NULL && cmd != &shell_cmd_compat_select &&
        cmd != &shell_cmd_compat_help) {
        argc_offset = 1;
    }

    /* Execute callback with arguments */
    if (!cmd->sc_ext) {
        rc = cmd->sc_cmd_func(argc - argc_offset, &argv[argc_offset]);
    } else {
        rc = cmd->sc_cmd_ext_func(cmd, argc - argc_offset, &argv[argc_offset],
                                  streamer);
    }
    if (rc < 0) {
        show_cmd_help(argv, streamer);
    }

    print_prompt_if_console(streamer);

    return rc;
}

static void
shell_process_command(char *line, struct streamer *streamer)
{
    char *argv[MYNEWT_VAL(SHELL_CMD_ARGC_MAX) + 1];
    size_t argc;

    argc = line2argv(line, argv, MYNEWT_VAL(SHELL_CMD_ARGC_MAX) + 1, streamer);
    if (!argc) {
        print_prompt_if_console(streamer);
        return;
    }

    shell_exec(argc, argv, streamer);
}

#if MYNEWT_VAL(SHELL_MGMT)
static void
shell_process_nlip_line(char *shell_line, struct streamer *streamer)
{
    size_t shell_line_len;

    shell_line_len = strlen(shell_line);
    if (shell_line_len > 2) {
        if (shell_line[0] == SHELL_NLIP_PKT_START1 &&
                shell_line[1] == SHELL_NLIP_PKT_START2) {
            shell_nlip_clear_pkt();
            shell_nlip_process(&shell_line[2], shell_line_len - 2);
        } else if (shell_line[0] == SHELL_NLIP_DATA_START1 &&
                shell_line[1] == SHELL_NLIP_DATA_START2) {
            shell_nlip_process(&shell_line[2], shell_line_len - 2);
        } else {
            shell_process_command(shell_line, streamer);
        }
    } else {
        shell_process_command(shell_line, streamer);
    }
}
#endif

static void
shell(struct os_event *ev)
{
    struct console_input *cmd;
    struct streamer *streamer;

    if (!ev) {
        print_prompt(NULL);
        return;
    }

    cmd = ev->ev_arg;
    if (!cmd) {
        print_prompt(NULL);
        return;
    }

    streamer = streamer_console_get();

#if MYNEWT_VAL(SHELL_MGMT)
    shell_process_nlip_line(cmd->line, streamer);
#else
    shell_process_command(cmd->line, streamer);
#endif

    console_line_event_put(ev);
}

#if MYNEWT_VAL(SHELL_COMPLETION)
const struct shell_cmd *
get_command_from_module(const char *command, int len, const struct shell_mod *shell_module)
{
    int i;
    const struct shell_cmd *cmd;

    for (i = 0; NULL != (cmd = shell_mod_command(shell_module, i)); ++i) {

        if (strlen(cmd->sc_cmd) != len) {
            continue;
        }

        if (strncmp(command, cmd->sc_cmd, len) == 0) {
            return cmd;
        }
    }
    return NULL;
}

static int
get_token(char **cur, int *null_terminated)
{
    char *str = *cur;

    *null_terminated = 0;
    /* remove ' ' at the beginning */
    while (*str && *str == ' ') {
        str++;
    }

    if (*str == '\0') {
        *null_terminated = 1;
        return 0;
    }

    *cur = str;
    str = strchr(str, ' ');

    if (str == NULL) {
        *null_terminated = 1;
        return strlen(*cur);
    }

    return str - *cur;
}

static int
get_last_token(char **cur)
{
    *cur = strrchr(*cur, ' ');
    if (*cur == NULL) {
        return 0;
    }
    (*cur)++;
    return strlen(*cur);
}

static void
complete_param(char *line, const char *param_prefix, int param_len,
               const struct shell_cmd *command, console_append_char_cb append_char)
{
    const char *first_match = NULL;
    int i, j, common_chars = -1;

    if (!(command->help && command->help->params)) {
        return;
    }

    for (i = 0; command->help->params[i].param_name; i++) {

        if (strncmp(param_prefix,
            command->help->params[i].param_name, param_len)) {
            continue;
        }

        if (!first_match) {
            first_match = command->help->params[i].param_name;
            continue;
        }

        /* more commands match, print first match */
        if (first_match && (common_chars < 0)) {
            console_printf("\n");
            console_printf("%s\n", first_match);
            common_chars = strlen(first_match);
        }

        /* cut common part of matching names */
        for (j = 0; j < common_chars; j++) {
            if (first_match[j] != command->help->params[i].param_name[j]) {
                break;
            }
        }

        common_chars = j;

        console_printf("%s\n", command->help->params[i].param_name);
    }

    /* no match, do nothing */
    if (!first_match) {
        return;
    }

    if (common_chars >= 0) {
        /* multiple match, restore prompt */
        print_prompt(line);
    } else {
        common_chars = strlen(first_match);
    }

    /* complete common part */
    for (i = param_len; i < common_chars; i++) {
        if (!append_char(line, first_match[i])) {
            return;
        }
    }
}

static void
complete_command(char *line, char *command_prefix, int command_len,
                 const struct shell_mod *module, console_append_char_cb append_char)
{
    int first_match = -1;
    int match_count = 0;
    int i, j, common_chars = -1;
    const struct shell_cmd *match = NULL;
    const struct shell_cmd *command;

    for (i = 0; NULL != (command = shell_mod_command(module, i)); ++i) {
        if (0 != strncmp(command_prefix, command->sc_cmd, command_len)) {
            continue;
        }
        match_count++;

        if (match_count == 1) {
            first_match = i;
            match = command;
            common_chars = strlen(command->sc_cmd);
            continue;
        }

        /*
         * Common chars were already reduced to what was printed,
         * no need to check more.
         */
        if (common_chars <= command_len) {
            continue;
        }
        /* Check how many additional chars are same as first command's */
        for (j = command_len; j < common_chars; j++) {
            if (match->sc_cmd[j] != command->sc_cmd[j]) {
                break;
            }
        }
        common_chars = j;
    }

    if (match_count == 0) {
        return;
    }

    /* Additional characters could be appended */
    if (common_chars > command_len) {
        /* complete common part */
        for (i = command_len; i < common_chars; i++) {
            if (!append_char(line, (uint8_t)match->sc_cmd[i])) {
                return;
            }
        }
        if (match_count == 1) {
            /* Whole word matched, append space */
            append_char(line, ' ');
        }
        return;
    } else if (match_count == 1) {
        /* Whole word matched, append space */
        append_char(line, ' ');
        return;
    }

    /*
     * More words match but there is nothing that could be appended
     * list all possible matches.
     */
    console_out('\n');
    console_printf("%s\n", match->sc_cmd);
    for (i = first_match + 1; NULL != (command = shell_mod_command(module, i)); ++i) {
        if (0 == strncmp(command_prefix, command->sc_cmd, command_len)) {
            console_printf("%s\n", command->sc_cmd);
        }
    }
    /* restore prompt */
    print_prompt(line);
}

static void
complete_module(char *line, char *module_prefix,
                int module_len, console_append_char_cb append_char)
{
    int i, j;
    const char *first_match = NULL;
    int common_chars = -1, space = 0;
    shell_mod_t mod;

    if (!module_len) {
        console_out('\n');
        for (i = 0; NULL != (mod = shell_module_get(i)); ++i) {
            console_printf("%s\n", mod->name);
        }
        print_prompt(line);
        return;
    }

    for (i = 0; NULL != (mod = shell_module_get(i)); ++i) {

        if (strncmp(module_prefix, mod->name, module_len)) {
            continue;
        }

        if (!first_match) {
            first_match = mod->name;
            continue;
        }

        /* more commands match, print first match */
        if (first_match && (common_chars < 0)) {
            console_out('\n');
            console_printf("%s\n", first_match);
            common_chars = strlen(first_match);
        }

        /* cut common part of matching names */
        for (j = 0; j < common_chars; j++) {
            if (first_match[j] != mod->name[j]) {
                break;
            }
        }

        common_chars = j;

        console_printf("%s\n", mod->name);
    }

    /* no match, do nothing */
    if (!first_match) {
        return;
    }

    if (common_chars >= 0) {
        /* multiple match, restore prompt */
        print_prompt(line);
    } else {
        common_chars = strlen(first_match);
        space = 1;
    }

    /* complete common part */
    for (i = module_len; i < common_chars; i++) {
        if (!append_char(line, first_match[i])) {
            return;
        }
    }

    /* for convenience add space after command */
    if (space) {
        append_char(line, ' ');
    }
}

static void
complete_select(char *line, char *cur,
                int tok_len, console_append_char_cb append_char)
{
    int null_terminated = 0;

    cur += tok_len + 1;
    tok_len = get_token(&cur, &null_terminated);
    if (tok_len == 0) {
        console_out('\n');
        print_modules(streamer_console_get());
        print_prompt(line);
        return;
    }

    if (null_terminated) {
        complete_module(line, cur, tok_len, append_char);
    }
}

static void
completion(char *line, console_append_char_cb append_char)
{
    char *cur;
    int tok_len;
    const struct shell_cmd *command;
    int null_terminated = 0;
    const struct shell_mod *def_module = default_module;
    const struct shell_mod *module;

    /*
     * line to completion is not ended by '\0' as the line that gets from
     * os_eventq_get function
     */
    if (!append_char(line, '\0')) {
        return;
    }

    cur = line;
    tok_len = get_token(&cur, &null_terminated);
    if (tok_len > 0 && cur[0] == '/') {
        def_module = NULL;
        tok_len--;
        cur++;
    }

    /* empty token - print options */
    if (tok_len == 0) {
        console_out('\n');
        if (def_module == NULL) {
            print_modules(streamer_console_get());
        } else {
            print_module_commands(def_module, streamer_console_get());
        }
        print_prompt(line);
        return;
    }

    /* token can be completed */
    if (null_terminated) {
        if (def_module == NULL) {
            complete_module(line, cur, tok_len, append_char);
            return;
        }
        complete_command(line, cur, tok_len,
                         def_module, append_char);
        return;
    }

    if (strncmp("select", cur, tok_len) == 0) {
        complete_select(line, cur, tok_len, append_char);
        return;
    }

    if (def_module != NULL) {
        module = def_module;
    } else {
        module = get_destination_module(cur, tok_len);

        if (module == NULL) {
            return;
        }

        cur += tok_len + 1;
        tok_len = get_token(&cur, &null_terminated);

        if (tok_len == 0) {
            console_out('\n');
            print_module_commands(module, streamer_console_get());
            print_prompt(line);
            return;
        }

        if (null_terminated) {
            complete_command(line, cur, tok_len,
                             module, append_char);
            return;
        }
    }

    command = get_command_from_module(cur, tok_len, module);
    if (command == NULL) {
        return;
    }

    cur += tok_len;
    tok_len = get_last_token(&cur);
    if (tok_len == 0) {
        console_out('\n');
        print_command_params(command, streamer_console_get());
        print_prompt(line);
        return;
    }
    complete_param(line, cur, tok_len, command, append_char);
    return;
}
#endif /* MYNEWT_VAL(SHELL_COMPLETION) */

void
shell_register_app_cmd_handler(shell_cmd_func_t handler)
{
    app_cmd.sc_cmd_func = handler;
}

void
shell_register_prompt_handler(shell_prompt_function_t handler)
{
    app_prompt_handler = handler;
}

void
shell_register_default_module(const char *name)
{
    int result = set_default_module(name);

    if (result != -1) {
        console_out('\n');
        print_prompt(NULL);
    }
}

static void
shell_avail_queue_init(void)
{
    int i;

    for (i = 0; i < MYNEWT_VAL(SHELL_MAX_CMD_QUEUED); i++) {
        shell_console_ev[i].ev_cb = shell;
        shell_console_ev[i].ev_arg = &buf[i];
        console_line_event_put(&shell_console_ev[i]);
    }
}

int
shell_register(const char *module_name, const struct shell_cmd *commands)
{
    if (num_of_shell_entities >= MYNEWT_VAL(SHELL_MAX_MODULES)) {
        DFLT_LOG_ERROR("Max number of modules reached\n");
        assert(0);
    } else if (MYNEWT_VAL(SHELL_MAX_MODULES) > 0) {
        registered_shell_modules[num_of_shell_entities].mod.name = module_name;
        registered_shell_modules[num_of_shell_entities].mod.ops = &shell_mod_std_ops;
        registered_shell_modules[num_of_shell_entities].commands = commands;
        /* commands_end will point to empty command */
        while (commands->sc_cmd) {
            ++commands;
        }
        registered_shell_modules[num_of_shell_entities].commands_end = commands;
        ++num_of_shell_entities;
    }

    return 0;
}

int
shell_cmd_register(const struct shell_cmd *sc)
{
    if (num_compat_commands >= MYNEWT_VAL(SHELL_MAX_COMPAT_COMMANDS)) {
        DFLT_LOG_ERROR(
                     "Max number of compat commands reached\n");
        assert(0);
    } else if (MYNEWT_VAL(SHELL_MAX_COMPAT_COMMANDS) > 0) {
        registered_compat_commands[num_compat_commands] = *sc;
        ++num_compat_commands;
    }
    return 0;
}
#endif

void
shell_init(void)
{
    /* Ensure this function only gets called by sysinit. */
    SYSINIT_ASSERT_ACTIVE();

#if !MYNEWT_VAL(SHELL_TASK)
    return;
#endif

    shell_avail_queue_init();
    shell_evq_set(os_eventq_dflt_get());

    prompt = SHELL_PROMPT;

#if MYNEWT_VAL(SHELL_MGMT)
    shell_nlip_init();
#endif

#if MYNEWT_VAL(SHELL_COMPLETION)
    console_set_completion_cb(completion);
#endif

#if MYNEWT_VAL(SHELL_BRIDGE)
    shell_bridge_init();
#endif
}
