/*
 * Copyright (c) 2015, Freescale Semiconductor, Inc.
 * Copyright 2016-2020 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#ifndef __FSL_SHELL_H__
#define __FSL_SHELL_H__

/*!
 * @addtogroup SHELL
 * @{
 */

#include "fsl_common.h"
#include "fsl_component_serial_manager.h"
#include "fsl_component_generic_list.h"

/*******************************************************************************
 * Definitions
 ******************************************************************************/

/*! @brief Whether use non-blocking mode. */
#ifndef SHELL_NON_BLOCKING_MODE
#define SHELL_NON_BLOCKING_MODE SERIAL_MANAGER_NON_BLOCKING_MODE
#endif

/*! @brief Macro to set on/off auto-complete feature. */
#define SHELL_AUTO_COMPLETE (1U)

/*! @brief Macro to set console buffer size. */
#ifndef SHELL_BUFFER_SIZE
#define SHELL_BUFFER_SIZE (64U)
#endif

/*! @brief Macro to set maximum arguments in command. */
#define SHELL_MAX_ARGS (8U)

/*! @brief Macro to set maximum count of history commands. */
#ifndef SHELL_HISTORY_COUNT
#define SHELL_HISTORY_COUNT (3U)
#endif

/*! @brief Macro to bypass arguments check */
#define SHELL_IGNORE_PARAMETER_COUNT (0xFF)

/*! @brief The handle size of the shell module. It is the sum of the SHELL_HISTORY_COUNT * SHELL_BUFFER_SIZE +
 * SHELL_BUFFER_SIZE + SERIAL_MANAGER_READ_HANDLE_SIZE + SERIAL_MANAGER_WRITE_HANDLE_SIZE*/
#define SHELL_HANDLE_SIZE                                                                                   \
    (160U + SHELL_HISTORY_COUNT * SHELL_BUFFER_SIZE + SHELL_BUFFER_SIZE + SERIAL_MANAGER_READ_HANDLE_SIZE + \
     SERIAL_MANAGER_WRITE_HANDLE_SIZE)

/*! @brief Macro to determine whether use common task. */
#ifndef SHELL_USE_COMMON_TASK
#define SHELL_USE_COMMON_TASK (0U)
#endif

/*! @brief Macro to set shell task priority. */
#ifndef SHELL_TASK_PRIORITY
#define SHELL_TASK_PRIORITY (2U)
#endif

/*! @brief Macro to set shell task stack size. */
#ifndef SHELL_TASK_STACK_SIZE
#define SHELL_TASK_STACK_SIZE (1000U)
#endif

/*! @brief Shell status */
typedef enum _shell_status
{
    kStatus_SHELL_Success               = kStatus_Success,                    /*!< Success */
    kStatus_SHELL_Error                 = MAKE_STATUS(kStatusGroup_SHELL, 1), /*!< Failed */
    kStatus_SHELL_OpenWriteHandleFailed = MAKE_STATUS(kStatusGroup_SHELL, 2), /*!< Open write handle failed */
    kStatus_SHELL_OpenReadHandleFailed  = MAKE_STATUS(kStatusGroup_SHELL, 3), /*!< Open read handle failed */
} shell_status_t;

/*! @brief The handle of the shell module */
typedef void *shell_handle_t;

/*! @brief User command function prototype. */
typedef shell_status_t (*cmd_function_t)(shell_handle_t shellHandle, int32_t argc, char **argv);

/*! @brief User command data configuration structure. */
typedef struct _shell_command
{
    const char *pcCommand; /*!< The command that is executed.  For example "help".  It must be all lower case. */
    char *pcHelpString;    /*!< String that describes how to use the command.  It should start with the command itself,
                                    and end with "\r\n".  For example "help: Returns a list of all the commands\r\n". */
    const cmd_function_t
        pFuncCallBack; /*!< A pointer to the callback function that returns the output generated by the command. */
    uint8_t cExpectedNumberOfParameters; /*!< Commands expect a fixed number of parameters, which may be zero. */
    list_element_t link;                 /*!< link of the element */
} shell_command_t;

/*!
 * @brief Defines the shell handle
 *
 * This macro is used to define a 4 byte aligned shell handle.
 * Then use "(shell_handle_t)name" to get the shell handle.
 *
 * The macro should be global and could be optional. You could also define shell handle by yourself.
 *
 * This is an example,
 * @code
 * SHELL_HANDLE_DEFINE(shellHandle);
 * @endcode
 *
 * @param name The name string of the shell handle.
 */
#define SHELL_HANDLE_DEFINE(name) uint32_t name[((SHELL_HANDLE_SIZE + sizeof(uint32_t) - 1U) / sizeof(uint32_t))]

#if defined(__ICCARM__)
/* disable misra 19.13 */
_Pragma("diag_suppress=Pm120")
#endif
/*!
 * @brief Defines the shell command structure
 *
 * This macro is used to define the shell command structure #shell_command_t.
 * And then uses the macro SHELL_COMMAND to get the command structure pointer.
 * The macro should not be used in any function.
 *
 * This is a example,
 * @code
 * SHELL_COMMAND_DEFINE(exit, "\r\n\"exit\": Exit program\r\n", SHELL_ExitCommand, 0);
 * SHELL_RegisterCommand(s_shellHandle, SHELL_COMMAND(exit));
 * @endcode
 *
 * @param command The command string of the command. The double quotes do not need. Such as exit for "exit",
 *                help for "Help", read for "read".
 * @param descriptor The description of the command is used for showing the command usage when "help" is typing.
 * @param callback The callback of the command is used to handle the command line when the input command is matched.
 * @param paramCount The max parameter count of the current command.
 */
#define SHELL_COMMAND_DEFINE(command, descriptor, callback, paramCount) \
                                                                        \
    shell_command_t g_shellCommand##command = {                         \
        (#command), (descriptor), (callback), (paramCount), {0},        \
    }

/*!
 * @brief Gets the shell command pointer
 *
 * This macro is used to get the shell command pointer. The macro should not be used before the macro
 * SHELL_COMMAND_DEFINE is used.
 *
 * @param command The command string of the command. The double quotes do not need. Such as exit for "exit",
 *                help for "Help", read for "read".
 */
#define SHELL_COMMAND(command) &g_shellCommand##command

#if defined(__ICCARM__)
    _Pragma("diag_default=Pm120")
#endif

/*******************************************************************************
 * API
 ******************************************************************************/

#if defined(__cplusplus)
        extern "C"
{
#endif /* _cplusplus */

    /*!
     * @name Shell functional operation
     * @{
     */

    /*!
     * @brief Initializes the shell module
     *
     * This function must be called before calling all other Shell functions.
     * Call operation the Shell commands with user-defined settings.
     * The example below shows how to set up the Shell and
     * how to call the SHELL_Init function by passing in these parameters.
     * This is an example.
     * @code
     *   static SHELL_HANDLE_DEFINE(s_shellHandle);
     *   SHELL_Init((shell_handle_t)s_shellHandle, (serial_handle_t)s_serialHandle, "Test@SHELL>");
     * @endcode
     * @param shellHandle Pointer to point to a memory space of size #SHELL_HANDLE_SIZE allocated by the caller.
     * The handle should be 4 byte aligned, because unaligned access doesn't be supported on some devices.
     * You can define the handle in the following two ways:
     * #SHELL_HANDLE_DEFINE(shellHandle);
     * or
     * uint32_t shellHandle[((SHELL_HANDLE_SIZE + sizeof(uint32_t) - 1U) / sizeof(uint32_t))];
     * @param serialHandle The serial manager module handle pointer.
     * @param prompt  The string prompt pointer of Shell. Only the global variable can be passed.
     * @retval kStatus_SHELL_Success The shell initialization succeed.
     * @retval kStatus_SHELL_Error An error occurred when the shell is initialized.
     * @retval kStatus_SHELL_OpenWriteHandleFailed Open the write handle failed.
     * @retval kStatus_SHELL_OpenReadHandleFailed Open the read handle failed.
     */
    shell_status_t SHELL_Init(shell_handle_t shellHandle, serial_handle_t serialHandle, char *prompt);

    /*!
     * @brief Registers the shell command
     *
     * This function is used to register the shell command by using the command configuration shell_command_config_t.
     * This is a example,
     * @code
     * SHELL_COMMAND_DEFINE(exit, "\r\n\"exit\": Exit program\r\n", SHELL_ExitCommand, 0);
     * SHELL_RegisterCommand(s_shellHandle, SHELL_COMMAND(exit));
     * @endcode
     * @param shellHandle The shell module handle pointer.
     * @param shellCommand  The command element.
     * @retval kStatus_SHELL_Success Successfully register the command.
     * @retval kStatus_SHELL_Error An error occurred.
     */
    shell_status_t SHELL_RegisterCommand(shell_handle_t shellHandle, shell_command_t * shellCommand);

    /*!
     * @brief Unregisters the shell command
     *
     * This function is used to unregister the shell command.
     *
     * @param shellCommand The command element.
     * @retval kStatus_SHELL_Success Successfully unregister the command.
     */
    shell_status_t SHELL_UnregisterCommand(shell_command_t * shellCommand);

    /*!
     * @brief Sends data to the shell output stream.
     *
     * This function is used to send data to the shell output stream.
     *
     * @param shellHandle The shell module handle pointer.
     * @param buffer Start address of the data to write.
     * @param length Length of the data to write.
     * @retval kStatus_SHELL_Success Successfully send data.
     * @retval kStatus_SHELL_Error An error occurred.
     */
    shell_status_t SHELL_Write(shell_handle_t shellHandle, const char *buffer, uint32_t length);

    /*!
     * @brief Writes formatted output to the shell output stream.
     *
     * Call this function to write a formatted output to the shell output stream.
     *
     * @param shellHandle The shell module handle pointer.
     *
     * @param   formatString Format string.
     * @return  Returns the number of characters printed or a negative value if an error occurs.
     */
    int SHELL_Printf(shell_handle_t shellHandle, const char *formatString, ...);
    /*!
     * @brief Sends data to the shell output stream with OS synchronization.
     *
     * This function is used to send data to the shell output stream with OS synchronization, note the function could
     * not be called in ISR.
     *
     * @param shellHandle The shell module handle pointer.
     * @param buffer Start address of the data to write.
     * @param length Length of the data to write.
     * @retval kStatus_SHELL_Success Successfully send data.
     * @retval kStatus_SHELL_Error An error occurred.
     */
    shell_status_t SHELL_WriteSynchronization(shell_handle_t shellHandle, const char *buffer, uint32_t length);

    /*!
     * @brief Writes formatted output to the shell output stream with OS synchronization.
     *
     * Call this function to write a formatted output to the shell output stream with OS synchronization, note the
     * function could not be called in ISR.
     *
     * @param shellHandle The shell module handle pointer.
     *
     * @param   formatString Format string.
     * @return  Returns the number of characters printed or a negative value if an error occurs.
     */
    int SHELL_PrintfSynchronization(shell_handle_t shellHandle, const char *formatString, ...);
    /*!
     * @brief Change shell prompt.
     *
     * Call this function to change shell prompt.
     *
     * @param shellHandle The shell module handle pointer.
     *
     * @param prompt The string which will be used for command prompt
     * @return  NULL.
     */
    void SHELL_ChangePrompt(shell_handle_t shellHandle, char *prompt);

    /*!
     * @brief Print shell prompt.
     *
     * Call this function to print shell prompt.
     *
     * @param shellHandle The shell module handle pointer.
     *
     * @return  NULL.
     */
    void SHELL_PrintPrompt(shell_handle_t shellHandle);

/*!
 * @brief The task function for Shell.
 * The task function for Shell; The function should be polled by upper layer.
 * This function does not return until Shell command exit was called.
 *
 * @param shellHandle The shell module handle pointer.
 */
#if !(defined(SHELL_NON_BLOCKING_MODE) && (SHELL_NON_BLOCKING_MODE > 0U))
    void SHELL_Task(shell_handle_t shellHandle);
#endif

    /*!
     * @brief Check if code is running in ISR.
     *
     * This function is used to check if code running in ISR.
     *
     * @retval TRUE if code runing in ISR.
     */
    static inline bool SHELL_checkRunningInIsr(void)
    {
#if (defined(__DSC__) && defined(__CW__))
        return !(isIRQAllowed());
#elif defined(__GIC_PRIO_BITS)
    return (0x13 == (__get_CPSR() & CPSR_M_Msk));
#elif defined(__get_IPSR)
    return (0U != __get_IPSR());
#else
    return false;
#endif
    }

    /* @} */

#if defined(__cplusplus)
}
#endif

/*! @}*/

#endif /* __FSL_SHELL_H__ */
