/**
 *******************************************************************************
 * @file  menu_console.c
 * @brief This file provides firmware functions to manage the menu console.
 @verbatim
   Change Logs:
   Date             Author          Notes
   2022-06-30       CDT             First version
   2023-01-17       CDT             Typo: Initailize/Creage/u32ExecCdoe -> Initialize/Create/u32ExecCode
 @endverbatim
 *******************************************************************************
 * Copyright (C) 2022-2023, Xiaohua Semiconductor Co., Ltd. All rights reserved.
 *
 * This software component is licensed by XHSC under BSD 3-Clause license
 * (the "License"); You may not use this file except in compliance with the
 * License. You may obtain a copy of the License at:
 *                    opensource.org/licenses/BSD-3-Clause
 *
 *******************************************************************************
 */

/*******************************************************************************
 * Include files
 ******************************************************************************/
#include "menu_console.h"

/**
 * @addtogroup Serial_Port_Console
 * @{
 */

/**
 * @defgroup Menu_Console Menu Console
 * @{
 */

/*******************************************************************************
 * Local type definitions ('typedef')
 ******************************************************************************/

/**
 * @defgroup Menu_Console_Local_Types Menu Console Local Types
 * @{
 */
typedef struct stc_tc_second_timer {
    stc_menu_timer_t     *pstcMemuTimer;
    uint32_t             u32PeriodValue;
    volatile uint32_t    u32CountValue;

    timer_second_handler pfnUserHandlerPerSecond;
    timer_second_handler pfnUserHandlerOverflow;
} stc_tc_second_timer_t;

typedef struct stc_tc_menu_console {
    /* Console device */
    stc_console_dev_t      *pstcConsoleDev;
    stc_console_cmd_line_t *pstcCmlLine;

    /* Second timer */
    stc_tc_second_timer_t  *pstcSecondTimer;

    /* Menu*/
    stc_slist_t stcPageHead;
    stc_menu_page_t *pstcCurrPage;
    stc_menu_list_t *pstcCurrList;

    /* Menu item table */
    stc_menu_item_t *pstcMenuItemTable;
    uint32_t u32MenuItemTableSize;
} stc_tc_menu_console_t;
/**
 * @}
 */

/*******************************************************************************
 * Local pre-processor symbols/macros ('#define')
 ******************************************************************************/

/*******************************************************************************
 * Global variable definitions (declared in header file with 'extern')
 ******************************************************************************/

/*******************************************************************************
 * Local function prototypes ('static')
 ******************************************************************************/

/*******************************************************************************
 * Local variable definitions ('static')
 ******************************************************************************/

/**
 * @defgroup Menu_Console_Local_Variables Menu Console Local Variables
 * @{
 */
static stc_tc_menu_console_t m_stcConsole;

static const char *m_pcMenuItemExecStr[] = {
    [MENU_ITEM_EXEC_RDY]       = "[ ...... ]",
    [MENU_ITEM_EXEC_PASS]      = "[   OK   ]",
    [MENU_ITEM_EXEC_FAIL]      = "[ Failed ]",
    [MENU_ITEM_EXEC_TESTED]    = "[ Tested ]",
    [MENU_ITEM_EXEC_UNSUPPORT] = "[  Todo  ]",
};

static const stc_menu_page_attr_t m_stcTcPageAttr = {
    .pcBorderTop         = "********************************************************************************\r\n",
    .pcTitle             = "*                        Evaluation Board Test Procedure                       *\r\n",
    .pcBorderBottom      = "*   Key in the option number                                                   *\r\n"
                           "*   Press 'Enter' key to run the procedure                                     *\r\n"
                           "*                                                                              *\r\n"
                           "********************************************************************************\r\n"
                           "<_ ",
    .pcListSeparatorLine = NULL,

    .pcDisplayFormatTitle             = "%s",
    .pcDisplayFormatBorderTop         = "\r\n%s",
    .pcDisplayFormatBorderBottom      = "%s",
    .pcDisplayFormatListSeparatorLine = "%s",
};

static const stc_menu_list_attr_t m_stcTcListAttr = {
    .pcName              = "*                                                                              *\r\n",
    .pcListTop           = "*                                                                              *\r\n",
    .pcListBottom        = "*                                                                              *\r\n",
    .pcItemLeft          = ":  ",
    .pcItemRight         = "   *\r\n",

    .pcDisplayFormatName        = "%s",
    .pcDisplayFormatListTop     = "%s",
    .pcDisplayFormatListBottom  = "%s",
    .pcDisplayFormatItemLeft    = "*  %2d%s",
    .pcDisplayFormatItemRight   = "%s",
    .pcDisplayFormatItem        = "%-50.50s%-18.18s",
};

/**
 * @}
 */

/*******************************************************************************
 * Function implementation - global ('extern') and local ('static')
 ******************************************************************************/

/**
 * @defgroup Menu_Console_Local_Functions Menu Console Local Functions
 * @{
 */

static inline const char *TC_GetItemExecCodeStr(uint32_t u32ExecCode)
{
    if (u32ExecCode > MENU_ITEM_EXEC_UNSUPPORT) {
        u32ExecCode = MENU_ITEM_EXEC_UNSUPPORT;
    }

    return m_pcMenuItemExecStr[u32ExecCode];
}

/**
 * @brief  Show item attribute.
 * @param  [in] pstcItem            Pointer to a @ref stc_menu_item_t structure
 * @retval Error code
 */
static int32_t TC_ShowItemAttr(stc_menu_item_t *pstcItem)
{
    if (pstcItem != NULL) {
        printf(m_stcTcListAttr.pcDisplayFormatItem, pstcItem->pcName, TC_GetItemExecCodeStr(pstcItem->u32ExecCode));
    }

    return OPCODE_OK;
}

/**
 * @brief  Show page attribute.
 * @param  [in] pstcConsole         Pointer to a @ref stc_tc_menu_console_t structure
 * @retval None
 */
static void TC_ShowPage(stc_tc_menu_console_t *pstcConsole)
{
    if (pstcConsole != NULL) {
        Menu_Page_ShowAttr(pstcConsole->pstcCurrPage, &TC_ShowItemAttr);
    }
}

/**
 * @brief  Find page.
 * @param  [in] pstcConsole         Pointer to a @ref stc_tc_menu_console_t structure
 * @param  [in] pcPageTitle         Pointer to title characters
 * @retval Pointer to a @ref stc_menu_page_t structure.
 */
static stc_menu_page_t *TC_FindPage(stc_tc_menu_console_t *pstcConsole, const char *pcPageTitle)
{
    stc_slist_t *pstcPos;
    stc_menu_page_t *pstcPageTmp;
    stc_menu_page_t *pstcPage = NULL;

    if ((pstcConsole != NULL) && (pcPageTitle != NULL)) {
        slist_for_each(pstcPos, &pstcConsole->stcPageHead) {
            pstcPageTmp = ((stc_menu_page_t *)pstcPos);
            if (0 == strcmp(pstcPageTmp->pstcPageAttr->pcTitle, pcPageTitle)) {
                pstcPage = pstcPageTmp;
                break;
            }
        }
    }

    return pstcPage;
}

/**
 * @brief  Insert page.
 * @param  [in] pstcConsole         Pointer to a @ref stc_tc_menu_console_t structure
 * @param  [in] pstcPage            Pointer to a @ref stc_menu_page_t structure
 * @retval None
 */
static void TC_InsertPage(stc_tc_menu_console_t *pstcConsole, stc_menu_page_t *pstcPage)
{
    if ((pstcConsole != NULL) && (pstcPage != NULL)) {
        slist_add_tail(&pstcConsole->stcPageHead, &pstcPage->stcPageNode);
    }
}

/**
 * @brief  Find page.
 * @param  [in] pstcConsole             Pointer to a @ref stc_tc_menu_console_t structure
 * @param  [in] pstcPageAttr            Pointer to @ref stc_menu_page_attr_t structure
 * @param  [in] pstcListAttr            Pointer to @ref stc_menu_list_attr_t structure
 * @param  [in] astcMenuItemTable       Pointer to @ref stc_menu_item_t structure
 * @param  [in] u32MenuItemTableSize    Table size
 * @retval Pointer to a @ref stc_menu_page_t structure.
 */
static stc_menu_page_t *TC_CreatePage(stc_tc_menu_console_t *pstcConsole,
                                      const stc_menu_page_attr_t *pstcPageAttr,
                                      const stc_menu_list_attr_t *pstcListAttr,
                                      stc_menu_item_t astcMenuItemTable[],
                                      uint32_t u32MenuItemTableSize)
{
    stc_menu_page_t *pstcPage;
    stc_menu_list_t *pstcList;

    pstcPage = TC_FindPage(pstcConsole, pstcPageAttr->pcTitle);
    if (pstcPage == NULL) {
        /* Create menu page */
        pstcPage = Menu_Page_Create(pstcPageAttr);

        /* Create menu list */
        pstcList = Menu_List_Create(pstcListAttr, astcMenuItemTable);
        if (pstcList != NULL) {
            /* Insert menu list to page */
            Menu_Page_InsertList(pstcPage, pstcList);

            /* Insert menu page to TC */
            TC_InsertPage(pstcConsole, pstcPage);
            pstcConsole->pstcMenuItemTable = astcMenuItemTable;
            pstcConsole->u32MenuItemTableSize = u32MenuItemTableSize;
        } else {
            Menu_Page_Destroy(pstcPage);
            pstcPage = NULL;
        }
    }

    return pstcPage;
}

/**
 * @brief  Timer handler per second
 * @param  [in] pstcTimer           Pointer to a @ref stc_menu_timer_t structure
 * @retval Error code
 */
static int32_t TC_SecondTimer_Handler(stc_menu_timer_t *pstcTimer)
{
    if (m_stcConsole.pstcSecondTimer != NULL) {
        if (m_stcConsole.pstcSecondTimer->u32CountValue < m_stcConsole.pstcSecondTimer->u32PeriodValue) {
            m_stcConsole.pstcSecondTimer->u32CountValue++;

            if (m_stcConsole.pstcSecondTimer->pfnUserHandlerPerSecond != NULL) {
                m_stcConsole.pstcSecondTimer->pfnUserHandlerPerSecond(pstcTimer);
            }

            if (m_stcConsole.pstcSecondTimer->u32CountValue >= m_stcConsole.pstcSecondTimer->u32PeriodValue) {
                if (m_stcConsole.pstcSecondTimer->pfnUserHandlerOverflow != NULL) {
                    m_stcConsole.pstcSecondTimer->pfnUserHandlerOverflow(pstcTimer);
                }
            }
        }
    }

    return OPCODE_OK;
}

/**
 * @}
 */

/**
 * @defgroup Test_Console_Global_Functions Test Console Global Functions
 * @{
 */

/**
 * @brief  Initialize TC second timer.
 * @param  [in] u32PeriodValue              Period value(unit: second)
 * @param  [in] u32CountValue               Count value(unit: second)
 * @param  [in] pfnUserHandlerPerSecond     User handler per second
 * @param  [in] pfnUserHandlerOverflow      User handler overflow
 * @retval None
 */
void TC_SecondTimer_Init(uint32_t u32PeriodValue,
                         uint32_t u32CountValue,
                         timer_second_handler pfnUserHandlerPerSecond,
                         timer_second_handler pfnUserHandlerOverflow)
{
    stc_menu_timer_t *pstcMemuTimer;
    stc_tc_second_timer_t *pstcSecondTimer;

    if (m_stcConsole.pstcSecondTimer == NULL) {
        pstcSecondTimer = malloc(sizeof(stc_tc_second_timer_t));
        if (pstcSecondTimer != NULL) {
            pstcMemuTimer = Menu_Timer_Create(CONSOLE_SYSTICK_TICK_FREQ, true, TC_SecondTimer_Handler); /* Period time: 1S */
            if (pstcMemuTimer != NULL) {
                pstcSecondTimer->pstcMemuTimer = pstcMemuTimer;
                pstcSecondTimer->u32CountValue = u32CountValue;
                pstcSecondTimer->u32PeriodValue = u32PeriodValue;
                pstcSecondTimer->pfnUserHandlerPerSecond = pfnUserHandlerPerSecond;
                pstcSecondTimer->pfnUserHandlerOverflow = pfnUserHandlerOverflow;
            } else {
                free(pstcSecondTimer);
                pstcSecondTimer = NULL;
            }

            m_stcConsole.pstcSecondTimer = pstcSecondTimer;
        }
    }
}

/**
 * @brief  Start second timer.
 * @param  None
 * @retval None
 */
void TC_SecondTimer_Start(void)
{
    if ((m_stcConsole.pstcSecondTimer != NULL) && (m_stcConsole.pstcSecondTimer->pstcMemuTimer != NULL)) {
        Menu_Timer_Start(m_stcConsole.pstcSecondTimer->pstcMemuTimer);
    }
}

/**
 * @brief  Stop second timer.
 * @param  None
 * @retval None
 */
void TC_SecondTimer_Stop(void)
{
    if ((m_stcConsole.pstcSecondTimer != NULL) && (m_stcConsole.pstcSecondTimer->pstcMemuTimer != NULL)) {
        Menu_Timer_Stop(m_stcConsole.pstcSecondTimer->pstcMemuTimer);
    }
}

/**
 * @brief  Set second timer period value.
 * @param  [in] u32PeriodValue      Period value(unit: second)
 * @retval None
 */
void TC_SecondTimer_SetPeriodValue(uint32_t u32PeriodValue)
{
    uint32_t u32SR;

    u32SR = CRITICAL_ZONE_ENTER();
    m_stcConsole.pstcSecondTimer->u32CountValue = 0UL;
    m_stcConsole.pstcSecondTimer->u32PeriodValue = u32PeriodValue;
    CRITICAL_ZONE_EXIT(u32SR);
}

/**
 * @brief  Get second timer count value.
 * @param  None
 * @retval Count value (unit: second)
 */
uint32_t TC_SecondTimer_GetCountValue(void)
{
    return m_stcConsole.pstcSecondTimer->u32CountValue;
}

/**
 * @brief  Get timeout result.
 * @param  None
 * @retval Timeout result
 */
bool TC_SecondTimer_IsOverflow(void)
{
    return (m_stcConsole.pstcSecondTimer->u32CountValue == m_stcConsole.pstcSecondTimer->u32PeriodValue);
}

/**
 * @brief  Set timer user callback per second.
 * @param  [in] pfnUserHandlerPerSecond     Timer user callback per second
 * @retval None
 */
void TC_SecondTimer_SetUserHandlerPerSecond(timer_second_handler pfnUserHandlerPerSecond)
{
    uint32_t u32SR;

    u32SR = CRITICAL_ZONE_ENTER();
    m_stcConsole.pstcSecondTimer->pfnUserHandlerPerSecond = pfnUserHandlerPerSecond;
    CRITICAL_ZONE_EXIT(u32SR);
}

/**
 * @brief  Set timer user callback overflow.
 * @param  [in] pfnUserHandlerOverflow      Timer user callback overflow
 * @retval None
 */
void TC_SecondTimer_SetUserHandlerOverflow(timer_second_handler pfnUserHandlerOverflow)
{
    uint32_t u32SR;

    u32SR = CRITICAL_ZONE_ENTER();
    m_stcConsole.pstcSecondTimer->pfnUserHandlerOverflow = pfnUserHandlerOverflow;
    CRITICAL_ZONE_EXIT(u32SR);
}

/**
 * @brief  Get test key number.
 * @param  None
 * @retval Key number
 */
int32_t TC_GetKeyNum(void)
{
    return CONSOLE_GetKeyNum(m_stcConsole.pstcCmlLine);
}

/**
 * @brief  TC Systick handler.
 * @param  None
 * @retval None
 */
void TC_SysTickHandler(void)
{
    Menu_Timer_IncTick();
}

/**
 * @brief  Test console initialize.
 * @param  [in] astcMenuItemTable       Menu item table
 * @param  [in] u32MenuItemTableSize    Menu item table size
 * @retval None
 */
void TC_Init(stc_menu_item_t astcMenuItemTable[], uint32_t u32MenuItemTableSize)
{
    stc_menu_page_t  *pstcPage;

    /* Initialize console device */
    if (m_stcConsole.pstcConsoleDev == NULL) {
        m_stcConsole.pstcConsoleDev = CONSOLE_CreateDevice(CONSOLE_DEV_RXBUF_LEN_MAX);
    }

    /* Initialize console device */
    if (m_stcConsole.pstcCmlLine == NULL) {
        m_stcConsole.pstcCmlLine = CONSOLE_CreateCommandLine(CONSOLE_CMD_LEN_MAX);
    }

    /* Create TC menu page */
    pstcPage = TC_CreatePage(&m_stcConsole, &m_stcTcPageAttr, &m_stcTcListAttr,
                             astcMenuItemTable, u32MenuItemTableSize);
    if (pstcPage != NULL) {
        m_stcConsole.pstcCurrPage = pstcPage;
        m_stcConsole.pstcCurrList = Menu_Page_FindList(m_stcConsole.pstcCurrPage, m_stcTcListAttr.pcName);
    }

    /* Configure timer */
    TC_SecondTimer_Init(0UL, 0UL, NULL, NULL);
    TC_SecondTimer_Start();

    /* Show menu page */
    TC_ShowPage(&m_stcConsole);
}

/**
 * @brief  Test console loop.
 * @param  None
 * @retval None
 */
void TC_Loop(void)
{
    int32_t i32KeyNum = TC_GetKeyNum();
    int32_t i32ItemMax = (int32_t)(m_stcConsole.u32MenuItemTableSize - 2UL);

    if (CONSOLE_CMD_KEY_ENTER <= i32KeyNum) {
        if (i32KeyNum <= i32ItemMax) {
            if (i32KeyNum != CONSOLE_CMD_KEY_ENTER) {
                /* Execute item handler */
                Menu_List_ExecItemById(m_stcConsole.pstcCurrList, i32KeyNum);
            }

            /* TC show menu page */
            TC_ShowPage(&m_stcConsole);
        } else {
            printf("Number = %d, please input the number range in [0, %d]\r\n<_", (int)i32KeyNum, (int)i32ItemMax);
        }
    }
}

/**
 * @}
 */

/**
 * @}
 */

/**
 * @}
 */

/*******************************************************************************
 * EOF (not truncated)
 ******************************************************************************/
