/*
 * Copyright (c) 2022, Shenzhen CVA Innovation CO.,LTD
 * All rights reserved.
 *
 * Shenzhen CVA Innovation CO.,LTD (CVA chip) is supplying this file for use
 * exclusively with CVA's microcontroller products. This file can be freely
 * distributed within development tools that are supporting such microcontroller
 * products.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS".  NO WARRANTIES, WHETHER EXPRESS, IMPLIED
 * OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE.
 * CVA SHALL NOT, IN ANY CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL,
 * OR CONSEQUENTIAL DAMAGES, FOR ANY REASON WHATSOEVER.
 */

/*******************************************************************************
 * the includes
 ******************************************************************************/

#include <stdint.h>
#include <string.h>
#include "lin_cfg.h"
#include "lin_type.h"
#include "lin_schedule.h"
#include "lin_if.h"

/*******************************************************************************
 * the defines
 ******************************************************************************/

/*******************************************************************************
 * the typedefs
 ******************************************************************************/

/*******************************************************************************
 * the globals
 ******************************************************************************/

LinSchedule_CtrlType LinSchedule_Ctrl;

/*******************************************************************************
 * the functions
 ******************************************************************************/

/*! \brief Initialize the schedule state information
 *
 * This function execute to initialize the schedule state information
 *
 * \param[in] channelIdx: which physical channel is used
 * \return: void
 */
void LinSchedule_Init(uint8_t channelIdx)
{
    uint8_t i = 0, defaultScheduleIdx = LINSCH_SCHEDULE_TABLE_NULL;

    defaultScheduleIdx = LinSchedule_Ctrl.chxState[channelIdx].defaultScheduleTblIdx;
    memset(&LinSchedule_Ctrl.chxState[channelIdx], 0, sizeof(LinSchedule_StateType));

    LinSchedule_Ctrl.chxState[channelIdx].currentScheduleTblIdx  = LINSCH_SCHEDULE_TABLE_NULL;
    LinSchedule_Ctrl.chxState[channelIdx].prevScheduleTblIdx     = LINSCH_SCHEDULE_TABLE_NULL;
    LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr = NULL;
    LinSchedule_Ctrl.chxState[channelIdx].defaultScheduleTblIdx  = defaultScheduleIdx;

    for(i = 0; i < LinSchedule_Ctrl.pLinSchedule_TblInfo->frameGeneralityNum; i++)
    {
        memset(&LinSchedule_Ctrl.pLinSchedule_TblInfo->pFrameGenerality[i].pFrameDataPtr[i], 0u, sizeof(Lin_FrameDataType));
        *LinSchedule_Ctrl.pLinSchedule_TblInfo->pFrameGenerality[i].pFrameAccessStatePtr = LIN_FRAME_ACCESS_STATE_IDLE;
    }
}

/*! \brief Initialize the schedule table information
 *
 * This function execute to initialize the schedule table information
 *
 * \param[in] pTblInfo: table information pointer
 * \return: void
 */
void LinSchedule_TblInit(const LinSchedule_TblInfoType *pTblInfo)
{
    uint8_t i = 0;

    memset(&LinSchedule_Ctrl, 0, sizeof(LinSchedule_Ctrl));

    LinSchedule_Ctrl.pLinSchedule_TblInfo = pTblInfo;

    for(i = 0; i < LINCFG_CH_NUM; i++)
    {
        LinSchedule_Init(i);
    }
}

/*! \brief Get the support frame number in the LIN BUS
 *
 * This function execute to get the frame number
 *
 * \param[in] uint8_t channelIdx: which physical channel is used
 * \return: the frame number
 */
uint8_t LinSchedule_GetFrameNum(uint8_t channelIdx)
{
    uint8_t ret = 0;

    ret = LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo[channelIdx].chxFrameAttributeNum;

    return ret;
}

/*! \brief Get the frame number in the current schedule table
 *
 * This function execute to get the frame number in the current schedule table
 *
 * \param[in] channelIdx: which physical channel is used
 * \param[in] linkTblIdx: which schedule table is to be check
 * \return: the frame number in the current schedule table
 */
uint8_t LinSchedule_GetFrameNumInScheduleTbl(uint8_t channelIdx, uint8_t linkTblIdx)
{
    uint8_t ret = 0;

    ret = LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo[channelIdx].pChxScheduleTblList[linkTblIdx].frameNum;
    
    return ret;
}

/*! \brief Get the pid by the pid index
 *
 * This function execute to get the pid in the support pid list
 *
 * \param[in] pidIdx: pid index
 * \return: pid
 */
uint8_t LinSchedule_GetPid(uint8_t pidIdx)
{
    uint8_t ret = LIN_DATA_INVALID;

    
    if(pidIdx < LinSchedule_Ctrl.pLinSchedule_TblInfo->frameGeneralityNum)
    {
        ret = LinSchedule_Ctrl.pLinSchedule_TblInfo->pFrameGenerality[pidIdx].pid;
    }

    return ret;
}

/*! \brief Get the pid index by pid
 *
 * This function execute to get the pid by the pid index in the support pid list
 *
 * \param[in] pid: pid
 * \return: pid index
 */
uint8_t LinSchedule_GetPidIdx(uint8_t pid)
{
    uint8_t frameIdx = LIN_DATA_INVALID;
    uint8_t i        = 0;

    for(i = 0; i < LinSchedule_Ctrl.pLinSchedule_TblInfo->frameGeneralityNum; i++)
    {
        if(pid == LinSchedule_Ctrl.pLinSchedule_TblInfo->pFrameGenerality[i].pid)
        {
            frameIdx = i;
            break;
            ;
        }
    }

    return frameIdx;
}

/*! \brief Get the frame type
 *
 * This function execute to get the frame type by the pid index
 *
 * \param[in] channelIdx: : which physical channel is used
 * \param[in] pidIdx: pid index
 * \return: the frame type
 */
Lin_FrametypeType LinSchedule_GetFrameType(uint8_t channelIdx, uint8_t pidIdx)
{
    Lin_FrametypeType ret = LIN_FRAME_TYPE_RESERVED;

    if(pidIdx != LIN_DATA_INVALID)
    {
        if(pidIdx < LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo[channelIdx].chxFrameAttributeNum)
        {
            ret = LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo[channelIdx].pChxFrameAttribute[pidIdx].frameType;
        }
    }

    return ret;
}

/*! \brief Get the frame control
 *
 * This function execute to get the frame control
 *
 * \param[in] channelIdx: : which physical channel is used
 * \param[in] pidIdx: pid index
 * \return: frame control type
 */
Lin_FrameCtrlType LinSchedule_GetFrameCtrl(uint8_t channelIdx, uint8_t pidIdx)
{
    Lin_FrameCtrlType ret = LIN_FRAME_CTRL_NO_ACTION;

    if(pidIdx != LIN_DATA_INVALID)
    {
        if(pidIdx < LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo[channelIdx].chxFrameAttributeNum)
        {
            ret = LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo[channelIdx].pChxFrameAttribute[pidIdx].frameCtrl;
        }
    }

    return ret;
}

/*! \brief Get the frame data length by pid index
 *
 * This function execute to get the frame data length by pid index
 *
 * \param[in] pidIdx: pid index
 * \return: the length of the frame data
 */
uint8_t LinSchedule_GetFrameDataLen(uint8_t pidIdx)
{
    uint8_t ret = 0;

    if(pidIdx < LinSchedule_Ctrl.pLinSchedule_TblInfo->frameGeneralityNum)
    {
        ret = LinSchedule_Ctrl.pLinSchedule_TblInfo->pFrameGenerality[pidIdx].frameLen;
    }

    return ret;
}

/*! \brief Get the checksum type
 *
 * This function execute to get the checksum type
 *
 * \param[in] pid: pid
 * \return: the type of check sum
 */
Lin_CheckSumType LinSchedule_GetCheckSumType(uint8_t pid)
{
    Lin_CheckSumType ret = LIN_CHECKSUM_CLASSIC;

    if((pid != LIN_STACK_PID_MRF) && (pid != LIN_STACK_PID_SRF))
    {
#if LINCFG_PROTOCOL_VER >= LIN_PROTOCOL_VER_20
        ret = LIN_CHECKSUM_ENHANCED;
#endif
    }

    return ret;
}

/*! \brief Get the frame buffer pointer
 *
 * This function execute to get the frame buffer pointer, each frame has their own buffer
 *
 * \param[in] pidIdx: pid index
 * \return: the pointer of frame buffer
 */
Lin_FrameDataType *LinSchedule_GetFrameBufferPtr(uint8_t pidIdx)
{
    Lin_FrameDataType *ret = 0;

    if(pidIdx != LIN_DATA_INVALID)
    {
        ret = (Lin_FrameDataType *)(LinSchedule_Ctrl.pLinSchedule_TblInfo->pFrameGenerality[pidIdx].pFrameDataPtr);
    }

    return ret;
}

/*! \brief Sect the frame access state
 *
 * This function execute to set the frame access state, allow or not allow to readout from the frame buffer or write data into frame buffer
 *
 * \param[in] pidIdx: pid index
 * \param[in] accessState: access state
 * \return: void
 */
void LinSchedule_SetFrameAccessState(uint8_t pidIdx, Lin_FrameAccessStateType accessState)
{
    if(pidIdx != LIN_DATA_INVALID)
    {
        *(LinSchedule_Ctrl.pLinSchedule_TblInfo->pFrameGenerality[pidIdx].pFrameAccessStatePtr) = accessState;
    }
}

/*! \brief Get the frame access state
 *
 * This function execute
 *
 * \param[in] uint8_t pidIdx:
 * \return: Lin_FrameAccessStateType
 */
Lin_FrameAccessStateType LinSchedule_GetFrameAccessState(uint8_t pidIdx)
{
    Lin_FrameAccessStateType ret = LIN_FRAME_ACCESS_STATE_IDLE;
    if(pidIdx != LIN_DATA_INVALID)
    {
        ret = *LinSchedule_Ctrl.pLinSchedule_TblInfo->pFrameGenerality[pidIdx].pFrameAccessStatePtr;
    }

    return ret;
}

/*! \brief Get the frame slot tick
 *
 * This function execute to get the frame slot tick in the special schedule table by pid
 *
 * \param[in] channelIdx: : which physical channel is used
 * \param[in] scheduleTblIdx: the target schedule table index
 * \param[in] pidIdx: pid index
 * \return: the frame slot tick
 */
uint16_t LinSchedule_GetFrameSlotTick(uint8_t channelIdx, uint8_t scheduleTblIdx, uint8_t pidIdx)
{
    uint16_t ret = 0;
    uint8_t  i   = 0;

    if(scheduleTblIdx < LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo->chxScheduleTblNum)
    {
        for(i = 0; i < LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo->pChxScheduleTblList[scheduleTblIdx].frameNum; i++)
        {
            if(pidIdx == LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo->pChxScheduleTblList[scheduleTblIdx].pScheduleTblInfo[i].pidIdx)
            {
                ret = LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo->pChxScheduleTblList[scheduleTblIdx].pScheduleTblInfo[pidIdx].frameSlotTick;
                break;
            }
        }
    }

    return ret;
}

/*! \brief Get the type of current operated schedule table
 *
 * This function execute to get the type of current operated schedule table
 *
 * \param[in] channelIdx: : which physical channel is used
 * \param[in] scheduleTblIdx: schedule table index
 * \return: the type of schedule table
 */
LinSchedule_TableType LinSchedule_GetScheduleTableType(uint8_t channelIdx, uint8_t scheduleTblIdx)
{
    LinSchedule_TableType ret = LINSCH_TBL_TYPE_NULL;
    uint8_t               i   = 0;

    for(i = 0; i < LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo->chxScheduleTblNum; i++)
    {
        if(scheduleTblIdx == LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo->pChxScheduleTblList[i].tblIdx)
        {
            ret = LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo->pChxScheduleTblList[i].tblType;
            break;
        }
    }

    return ret;
}

/*! \brief Get the schedule table index by table type
 *
 * This function execute to get the type of current operated schedule table
 *
 * \param[in] tableType: : the type of schedule table
 * \return: return the first match table index in the schedule table list
 */
uint8_t LinSchedule_GetScheduleTblIdxByTableType(LinSchedule_TableType tableType)
{
    uint8_t i = 0, ret = 0;

    for(i = 0; i < LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo->chxScheduleTblNum; i++)
    {
        if(tableType == LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo->pChxScheduleTblList[i].tblType)
        {
            ret = i;
            break;
        }
    }

    return ret;
}

/*! \brief Get the link schedule table pointer
 *
 * This function execute to get the link schedule table pointer
 *
 * \param[in] channelIdx: which physical channel is used
 * \param[in] linkTblIdx: the link schedule table index
 * \return: the link schedule table pointer
 */
LinSchedule_ScheduleTblListType *LinSchedule_GetLinkScheduleTblPtr(uint8_t channelIdx, uint8_t linkTblIdx)
{
    LinSchedule_ScheduleTblListType *ret = NULL;
    uint8_t                          i   = 0;

    for(i = 0; i < LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo->chxScheduleTblNum; i++)
    {
        if(linkTblIdx == LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo->pChxScheduleTblList[i].tblIdx)
        {
            ret = (LinSchedule_ScheduleTblListType *)&LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo->pChxScheduleTblList[i];
            break;
        }
    }

    return ret;
}

/*! \brief Get the pid index in the target schedule table
 *
 * This function execute to get the pid index in the target schedule table by frame index
 *
 * \param[in] channelIdx: which physical channel is used
 * \param[in] linkTblIdx: link schedule table index
 * \param[in] frameIdx: the frame index in schedule table
 * \return: pid index
 */
uint8_t LinSchedule_GetPidIdxInSchedulerTbl(uint8_t channelIdx, uint8_t linkTblIdx, uint8_t frameIdx)
{
    uint8_t ret = LINSCH_FRAMEID_IDX_INVALID;

    if((linkTblIdx < LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo->chxScheduleTblNum) && (frameIdx < LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo->pChxScheduleTblList[linkTblIdx].frameNum))
    {
        if(LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo->pChxScheduleTblList[linkTblIdx].pScheduleTblInfo != NULL)
        {
            ret = LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo->pChxScheduleTblList[linkTblIdx].pScheduleTblInfo[frameIdx].pidIdx;
        }
    }

    return ret;
}

/*! \brief Get the link schedule table index
 *
 * This function execute to get the link schedule table index by the pid index in current schedule table
 *
 * \param[in] channelIdx: which physical channel is used
 * \param[in] tblIdx: the current schedule table index
 * \param[in] pidIdx: the pid index
 * \return: the link schedule table index
 */
uint8_t LinSchedule_GetLinkTblIdx(uint8_t channelIdx, uint8_t tblIdx, uint8_t pidIdx)
{
    uint8_t ret = LINSCH_SCHEDULE_TABLE_NULL;
    uint8_t i   = 0;

    if(tblIdx < LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo->chxScheduleTblNum)
    {
        if(LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo->pChxScheduleTblList[tblIdx].pScheduleTblInfo != NULL)
        {
            for(i = 0; i < LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo->pChxScheduleTblList[tblIdx].frameNum; i++)
            {
                if(pidIdx == LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo->pChxScheduleTblList[tblIdx].pScheduleTblInfo[i].pidIdx)
                {
                    ret = LinSchedule_Ctrl.pLinSchedule_TblInfo->chxTableInfo->pChxScheduleTblList[tblIdx].pScheduleTblInfo[pidIdx].linkTblIdx;
                    break;
                }
            }
        }
    }

    return ret;
}

#if LINCFG_NODE_USED == LINCFG_NODE_MASTER

/*! \brief Get the default schedule table index
 *
 * This function execute to get the default schedule table index
 *
 * \param[in] channelIdx: which physical channel is used
 * \return:  the default schedule table index
 */
uint8_t LinSchedule_GetDefaultScheduleTblIdx(uint8_t channelIdx)
{
    return LinSchedule_Ctrl.chxState[channelIdx].defaultScheduleTblIdx;
}

/*! \brief Set the default schedule table index
 *
 * This function execute to set the default schedule table index
 *
 * \param[in] channelIdx: which physical channel is used
 * \param[in]:  the default schedule table index
 */
void LinSchedule_SetDefaultScheduleTblIdx(uint8_t channelIdx, uint8_t defaultScheduleTblIdx)
{
    LinSchedule_Ctrl.chxState[channelIdx].defaultScheduleTblIdx = defaultScheduleTblIdx;
}

/*! \brief Request to switch schedule table
 *
 * This function execute to request to switch to target schedule table
 *
 * \param[in] channelIdx: which physical channel is used
 * \param[in] targetScheduleTblIdx: target schedule index
 * \return: true: switch to target schedule table successfully
 *          false: switch to target schedule table failed
 */
bool LinSchedule_ReqSwitchScheduleTbl(uint8_t channelIdx, uint8_t targetScheduleTblIdx)
{
    bool ret = false;

    if((targetScheduleTblIdx != LinSchedule_Ctrl.chxState[channelIdx].currentScheduleTblIdx) && (LinSchedule_Ctrl.chxState[channelIdx].scheduleTblSwitchReqEn == false))
    {
        LinSchedule_Ctrl.chxState[channelIdx].targetScheduleTblIdx   = targetScheduleTblIdx;
        LinSchedule_Ctrl.chxState[channelIdx].scheduleTblSwitchReqEn = true;
        ret                                                       = true;
    }

    return ret;
}
#endif

#if LINCFG_NODE_USED == LINCFG_NODE_MASTER
/*! \brief Go on to transmit the frame in the current schedule table
 *
 * This function execute to go on to transmit the frame in the current schedule table
 *
 * \param[in] channelIdx:  which physical channel is used
 * \return: void
 */
static void LinSchedule_ContinueTransmitProc(uint8_t channelIdx)
{
    /* If transmit frame are not all transmitted and no the action of switch table request is exist, continue to transmit the frames in the current table */
    bool    txReq  = false;
    uint8_t pidIdx = LINSCH_FRAMEID_IDX_INVALID;

    /* Check the current frame in the current schedule table is a sporadic or not */
    if(LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr->pScheduleTblInfo[LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl].frameType != LIN_FRAME_TYPE_SPORADIC)
    {
        /* Transmit the next frame */
        LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl = LinSchedule_Ctrl.chxState[channelIdx].txFrameCnt;
        pidIdx                                                          = LinSchedule_GetPidIdxInSchedulerTbl(channelIdx, LinSchedule_Ctrl.chxState[channelIdx].currentScheduleTblIdx, LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl);
        if(pidIdx != LINSCH_FRAMEID_IDX_INVALID)
        {
            Lin_FrameAccessStateType accessState;

            accessState = LinSchedule_GetFrameAccessState(pidIdx);
            if((accessState == LIN_FRAME_ACCESS_STATE_IDLE) || (accessState == LIN_FRAME_ACCESS_STATE_UPDATED))
            {
                txReq = true;
            }
        }
    }
    else
    {
        uint8_t linkTblIdx = LINSCH_SCHEDULE_TABLE_NULL, i = 0;

        linkTblIdx = LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr->pScheduleTblInfo[LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl].linkTblIdx;
        if(linkTblIdx != LINSCH_SCHEDULE_TABLE_NULL)
        {
            /* Before switch to new table, store the current table information */
            LinSchedule_Ctrl.chxState[channelIdx].prevScheduleTblIdx        = LinSchedule_Ctrl.chxState[channelIdx].currentScheduleTblIdx;
            LinSchedule_Ctrl.chxState[channelIdx].prevFrameIdxInScheduleTbl = LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl;

            /* Switch to the new schedule table */
            LinSchedule_Ctrl.chxState[channelIdx].currentScheduleTblIdx  = linkTblIdx;
            LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr = (const LinSchedule_ScheduleTblListType *)LinSchedule_GetLinkScheduleTblPtr(channelIdx, linkTblIdx);

            LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl = 0;
            LinSchedule_Ctrl.chxState[channelIdx].txFrameCnt                   = 0;

            if(LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr != NULL)
            {
                uint8_t frameNum = 0;

                frameNum = LinSchedule_GetFrameNumInScheduleTbl(channelIdx, linkTblIdx);
                for(i = 0; i < frameNum; i++)
                {
                    /* Check the upper data are changed or not */
                    LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl = i;
                    LinSchedule_Ctrl.chxState[channelIdx].frameSlotTick                = LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr->pScheduleTblInfo[i].frameSlotTick;
                    if(LinSchedule_GetFrameAccessState(LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr->pScheduleTblInfo[i].pidIdx) == LIN_FRAME_ACCESS_STATE_UPDATED)
                    {
                        txReq = true;
                        break;
                    }
                    else
                    {
                        LinSchedule_Ctrl.chxState[channelIdx].txFrameCnt++;
                    }
                }
            }
        }
    }

    if(txReq == true)
    {
        Lin_FrameInfoType frameInfo;
        uint8_t           pidIdx = 0;

        pidIdx = LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr->pScheduleTblInfo[LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl].pidIdx;

        LinSchedule_Ctrl.chxState[channelIdx].frameSlotTick = LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr->pScheduleTblInfo[LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl].frameSlotTick;
        frameInfo.frameType                              = LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr->pScheduleTblInfo[LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl].frameType;
        frameInfo.pid                                    = LinSchedule_GetPid(pidIdx);
        frameInfo.pFrameData                             = LinSchedule_GetFrameBufferPtr(pidIdx);
        frameInfo.dataLen                                = LinSchedule_GetFrameDataLen(pidIdx);
        frameInfo.frameCtrl                              = LinSchedule_GetFrameCtrl(channelIdx, pidIdx);
        if(LinIf_ReqTransmit(channelIdx, &frameInfo) == true)
        {
            /* Nothing to do */
        }

        LinSchedule_Ctrl.chxState[channelIdx].txFrameCnt++;
    }
}
#endif

#if LINCFG_NODE_USED == LINCFG_NODE_MASTER
/*! \brief The main function of schedule table process
 *
 * This function execute to process the schedule table
 *
 * \param[in] channelIdx:  which physical channel is used
 * \return: void
 */
void LinSchedule_MainFunction(uint8_t channelIdx)
{
    if(LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr != NULL)
    {
        if(LinSchedule_Ctrl.chxState[channelIdx].frameSlotTick > LINCFG_TICK_BASE)
        {
            LinSchedule_Ctrl.chxState[channelIdx].frameSlotTick -= LINCFG_TICK_BASE;
        }
        else
        {
            Lin_OpStatusType linOpStatus;

            linOpStatus = LinIf_GetTransferOperateStatus(channelIdx);
            if((linOpStatus == LIN_STATUS_IDLE) || (linOpStatus == LIN_STATUS_TX_OK) || (linOpStatus == LIN_STATUS_RX_OK) || ((linOpStatus >= LIN_STATUS_ERR_BIT) && (LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr->pScheduleTblInfo[LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl].frameType == LIN_FRAME_TYPE_UNCONDITIONAL)) || ((linOpStatus == LIN_STATUS_ERR_TIMEOUT) && (LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr->pScheduleTblInfo[LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl].frameType == LIN_FRAME_TYPE_EVENT_TRIGGER)))
            {
                /* Check the current table , all frame in the table have been transmitted */
                if(LinSchedule_Ctrl.chxState[channelIdx].txFrameCnt < LinSchedule_GetFrameNumInScheduleTbl(channelIdx, LinSchedule_Ctrl.chxState[channelIdx].currentScheduleTblIdx))
                {
                    if(linOpStatus >= LIN_STATUS_ERR_BIT)
                    {
                        linOpStatus = LIN_STATUS_IDLE;
                    }
                    /* If transmit frame are not all transmitted, then before transmit the next frame, need to check if there is the action of request schedule table to switch */
                    if(LinSchedule_Ctrl.chxState[channelIdx].scheduleTblSwitchReqEn == true)
                    {
                        /* If the target schedule is different with the current schedult table, switch to the target schedule table */
                        if(LinSchedule_Ctrl.chxState[channelIdx].currentScheduleTblIdx != LinSchedule_Ctrl.chxState[channelIdx].targetScheduleTblIdx)
                        {
                            /* If the current schedult table is normal type, then allow to switch target table , if not, continue to transmit the frame in the current table, until finished transmit all frame */
                            if(LinSchedule_GetScheduleTableType(channelIdx, LinSchedule_Ctrl.chxState[channelIdx].currentScheduleTblIdx) == LINSCH_TBL_TYPE_NORM)
                            {
                                uint8_t pidIdx = LINSCH_FRAMEID_IDX_INVALID;

                                LinSchedule_Ctrl.chxState[channelIdx].scheduleTblSwitchReqEn    = false;
                                LinSchedule_Ctrl.chxState[channelIdx].prevFrameIdxInScheduleTbl = LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl;
                                LinSchedule_Ctrl.chxState[channelIdx].prevScheduleTblIdx        = LinSchedule_Ctrl.chxState[channelIdx].currentScheduleTblIdx;
                                LinSchedule_Ctrl.chxState[channelIdx].currentScheduleTblIdx     = LinSchedule_Ctrl.chxState[channelIdx].targetScheduleTblIdx;
                                LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr    = (const LinSchedule_ScheduleTblListType *)LinSchedule_GetLinkScheduleTblPtr(channelIdx, LinSchedule_Ctrl.chxState[channelIdx].currentScheduleTblIdx);

                                LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl = 0;
                                LinSchedule_Ctrl.chxState[channelIdx].txFrameCnt                   = 0;

                                /* Read out from the new schedule table */
                                pidIdx = LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr->pScheduleTblInfo[LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl].pidIdx;
                                if(pidIdx != LINSCH_FRAMEID_IDX_INVALID)
                                {
                                    Lin_FrameAccessStateType accessState;

                                    accessState = LinSchedule_GetFrameAccessState(pidIdx);
                                    if((accessState == LIN_FRAME_ACCESS_STATE_IDLE) || (accessState == LIN_FRAME_ACCESS_STATE_UPDATED))
                                    {
                                        Lin_FrameInfoType frameInfo;

                                        LinSchedule_Ctrl.chxState[channelIdx].frameSlotTick = LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr->pScheduleTblInfo[LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl].frameSlotTick;

                                        frameInfo.frameType  = LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr->pScheduleTblInfo[LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl].frameType;
                                        frameInfo.dataLen    = LinSchedule_GetFrameDataLen(pidIdx);
                                        frameInfo.pid        = LinSchedule_GetPid(pidIdx);
                                        frameInfo.frameCtrl  = LinSchedule_GetFrameCtrl(channelIdx, pidIdx);
                                        frameInfo.pFrameData = LinSchedule_GetFrameBufferPtr(pidIdx);

                                        if(LinIf_ReqTransmit(channelIdx, &frameInfo) == true)
                                        {
                                            /* Indication Upper layer that go to sleep cmd is doing */
                                            if(LinSchedule_GetScheduleTableType(channelIdx, LinSchedule_Ctrl.chxState[channelIdx].currentScheduleTblIdx) == LINSCH_TBL_TYPE_GO_TO_SLEEP)
                                            {
                                                LinIf_SetSleepRequestIndication(LINCFG_MASTER_CH_IDX);
                                            }
                                        }

                                        LinSchedule_Ctrl.chxState[channelIdx].txFrameCnt++;
                                    }
                                }
                            }
                            else
                            {
                                LinSchedule_ContinueTransmitProc(channelIdx);
                            }
                        }
                    }
                    else
                    {
                        LinSchedule_ContinueTransmitProc(channelIdx);
                    }
                }
                else
                {
                    /* The current schedule table, all frames are transmitted, need to transmit from the header of the current table, if there is no action of switch schedule table */
                    if(LinSchedule_Ctrl.chxState[channelIdx].scheduleTblSwitchReqEn == true)
                    {
                        if(LinSchedule_Ctrl.chxState[channelIdx].currentScheduleTblIdx != LinSchedule_Ctrl.chxState[channelIdx].targetScheduleTblIdx)
                        {
                            uint8_t pidIdx = LINSCH_FRAMEID_IDX_INVALID;

                            LinSchedule_Ctrl.chxState[channelIdx].prevFrameIdxInScheduleTbl = LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl;
                            LinSchedule_Ctrl.chxState[channelIdx].prevScheduleTblIdx        = LinSchedule_Ctrl.chxState[channelIdx].currentScheduleTblIdx;
                            LinSchedule_Ctrl.chxState[channelIdx].currentScheduleTblIdx     = LinSchedule_Ctrl.chxState[channelIdx].targetScheduleTblIdx;
                            LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr    = (const LinSchedule_ScheduleTblListType *)LinSchedule_GetLinkScheduleTblPtr(channelIdx, LinSchedule_Ctrl.chxState[channelIdx].currentScheduleTblIdx);

                            LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl = 0;
                            LinSchedule_Ctrl.chxState[channelIdx].txFrameCnt                   = 0;

                            pidIdx = LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr->pScheduleTblInfo[LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl].pidIdx;
                            if(pidIdx != LINSCH_FRAMEID_IDX_INVALID)
                            {
                                Lin_FrameAccessStateType accessState;

                                accessState = LinSchedule_GetFrameAccessState(pidIdx);
                                if((accessState == LIN_FRAME_ACCESS_STATE_IDLE) || (accessState == LIN_FRAME_ACCESS_STATE_UPDATED))
                                {
                                    Lin_FrameInfoType frameInfo;

                                    LinSchedule_Ctrl.chxState[channelIdx].frameSlotTick = LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr->pScheduleTblInfo[LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl].frameSlotTick;

                                    frameInfo.frameType  = LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr->pScheduleTblInfo[LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl].frameType;
                                    frameInfo.dataLen    = LinSchedule_GetFrameDataLen(pidIdx);
                                    frameInfo.pid        = LinSchedule_GetPid(pidIdx);
                                    frameInfo.frameCtrl  = LinSchedule_GetFrameCtrl(channelIdx, pidIdx);
                                    frameInfo.pFrameData = LinSchedule_GetFrameBufferPtr(pidIdx);

                                    if(LinIf_ReqTransmit(channelIdx, &frameInfo) == true)
                                    {
                                        /* Indication Upper layer that go to sleep cmd is doing */
                                        if(LinSchedule_GetScheduleTableType(channelIdx, LinSchedule_Ctrl.chxState[channelIdx].currentScheduleTblIdx) == LINSCH_TBL_TYPE_GO_TO_SLEEP)
                                        {
                                            LinIf_SetSleepRequestIndication(LINCFG_MASTER_CH_IDX);
                                        }
                                    }

                                    LinSchedule_Ctrl.chxState[channelIdx].txFrameCnt++;
                                }
                            }
                        }

                        LinSchedule_Ctrl.chxState[channelIdx].scheduleTblSwitchReqEn = false;
                    }
                    else
                    {
                        bool    txReq  = false;
                        uint8_t pidIdx = LINSCH_FRAMEID_IDX_INVALID;

                        /* If the current schedule table is the same as the previous table */
                        if(LinSchedule_Ctrl.chxState[channelIdx].currentScheduleTblIdx == LinSchedule_Ctrl.chxState[channelIdx].prevScheduleTblIdx)
                        {
                            LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl = 0;
                            LinSchedule_Ctrl.chxState[channelIdx].txFrameCnt                   = 0;

                            pidIdx = LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr->pScheduleTblInfo[LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl].pidIdx;
                            if(pidIdx != LINSCH_FRAMEID_IDX_INVALID)
                            {
                                Lin_FrameAccessStateType accessState;

                                accessState = LinSchedule_GetFrameAccessState(pidIdx);
                                if((accessState == LIN_FRAME_ACCESS_STATE_IDLE) || (accessState == LIN_FRAME_ACCESS_STATE_UPDATED))
                                {
                                    txReq = true;
                                }
                            }
                        }
                        else
                        {
                            /* If the current schedule table is different with the previous table, restore the previous table from the break frame index to continue to transmit */
                            LinSchedule_Ctrl.chxState[channelIdx].currentScheduleTblIdx        = LinSchedule_Ctrl.chxState[channelIdx].prevScheduleTblIdx;
                            LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl = LinSchedule_Ctrl.chxState[channelIdx].prevFrameIdxInScheduleTbl + 1u;
                            LinSchedule_Ctrl.chxState[channelIdx].txFrameCnt                   = LinSchedule_Ctrl.chxState[channelIdx].prevFrameIdxInScheduleTbl + 1u;
                            LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr       = (const LinSchedule_ScheduleTblListType *)LinSchedule_GetLinkScheduleTblPtr(channelIdx, LinSchedule_Ctrl.chxState[channelIdx].currentScheduleTblIdx);

                            pidIdx = LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr->pScheduleTblInfo[LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl].pidIdx;
                            if(pidIdx != LINSCH_FRAMEID_IDX_INVALID)
                            {
                                Lin_FrameAccessStateType accessState;

                                accessState = LinSchedule_GetFrameAccessState(pidIdx);
                                if((accessState == LIN_FRAME_ACCESS_STATE_IDLE) || (accessState == LIN_FRAME_ACCESS_STATE_UPDATED))
                                {
                                    txReq = true;
                                }
                            }
                        }

                        if(txReq == true)
                        {
                            Lin_FrameInfoType frameInfo;
                            uint8_t           pidIdx = LINSCH_FRAMEID_IDX_INVALID;

                            pidIdx = LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr->pScheduleTblInfo[LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl].pidIdx;

                            LinSchedule_Ctrl.chxState[channelIdx].frameSlotTick = LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr->pScheduleTblInfo[LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl].frameSlotTick;

                            frameInfo.frameType  = LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr->pScheduleTblInfo[LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl].frameType;
                            frameInfo.dataLen    = LinSchedule_GetFrameDataLen(pidIdx);
                            frameInfo.pid        = LinSchedule_GetPid(pidIdx);
                            frameInfo.frameCtrl  = LinSchedule_GetFrameCtrl(channelIdx, pidIdx);
                            frameInfo.pFrameData = LinSchedule_GetFrameBufferPtr(pidIdx);

                            if(LinIf_ReqTransmit(channelIdx, &frameInfo) == true)
                            {
                                /* Nothing to do */
                            }

                            LinSchedule_Ctrl.chxState[channelIdx].txFrameCnt++;
                        }
                    }
                }
            }
            else if(linOpStatus >= LIN_STATUS_ERR_BIT)
            {
                /* If the current frame type is event trigger frame, allow to exist the error of timeout */
                if(linOpStatus < LIN_STATUS_ERR_TIMEOUT)
                {
                    /* If the error happen, indicate the arbitration of bus is exist, need to switch to collision table */
                    if(LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr->pScheduleTblInfo[LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl].frameType == LIN_FRAME_TYPE_EVENT_TRIGGER)
                    {
                        uint8_t linkTblIdx = LINSCH_SCHEDULE_TABLE_NULL;

                        /* Get the associated schedule table of the current event trigger frame, means to switch the associated collision schedule table */
                        linkTblIdx = LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr->pScheduleTblInfo[LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl].linkTblIdx;
                        if(linkTblIdx != LINSCH_SCHEDULE_TABLE_NULL)
                        {
                            uint8_t pidIdx = LINSCH_FRAMEID_IDX_INVALID;

                            LinSchedule_Ctrl.chxState[channelIdx].prevScheduleTblIdx        = LinSchedule_Ctrl.chxState[channelIdx].currentScheduleTblIdx;
                            LinSchedule_Ctrl.chxState[channelIdx].prevFrameIdxInScheduleTbl = LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl;

                            LinSchedule_Ctrl.chxState[channelIdx].currentScheduleTblIdx  = linkTblIdx;
                            LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr = (const LinSchedule_ScheduleTblListType *)LinSchedule_GetLinkScheduleTblPtr(channelIdx, linkTblIdx);

                            LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl = 0;
                            LinSchedule_Ctrl.chxState[channelIdx].txFrameCnt                   = 0;

                            pidIdx = LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr->pScheduleTblInfo[LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl].pidIdx;
                            if(pidIdx != LINSCH_FRAMEID_IDX_INVALID)
                            {
                                Lin_FrameAccessStateType accessState;

                                accessState = LinSchedule_GetFrameAccessState(pidIdx);
                                if(accessState == LIN_FRAME_ACCESS_STATE_UPDATED)
                                {
                                    Lin_FrameInfoType frameInfo;

                                    LinSchedule_Ctrl.chxState[channelIdx].frameSlotTick = LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr->pScheduleTblInfo[LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl].frameSlotTick;

                                    frameInfo.frameType  = LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr->pScheduleTblInfo[LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl].frameType;
                                    frameInfo.dataLen    = LinSchedule_GetFrameDataLen(pidIdx);
                                    frameInfo.pid        = LinSchedule_GetPid(pidIdx);
                                    frameInfo.frameCtrl  = LinSchedule_GetFrameCtrl(channelIdx, pidIdx);
                                    frameInfo.pFrameData = LinSchedule_GetFrameBufferPtr(pidIdx);

                                    if(LinIf_ReqTransmit(channelIdx, &frameInfo) == true)
                                    {
                                        /* Nothing to do */
                                    }

                                    LinSchedule_Ctrl.chxState[channelIdx].txFrameCnt++;
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    else
    {
        /* If the current schedule is empty, if exist the action of request to switch new table , switch to new schedule table */
        if(LinSchedule_Ctrl.chxState[channelIdx].scheduleTblSwitchReqEn == true)
        {
            LinSchedule_Ctrl.chxState[channelIdx].scheduleTblSwitchReqEn       = false;
            LinSchedule_Ctrl.chxState[channelIdx].currentFrameSlotTickCnt      = 0;
            LinSchedule_Ctrl.chxState[channelIdx].frameSlotTick                = 0;
            LinSchedule_Ctrl.chxState[channelIdx].txFrameCnt                   = 0;
            LinSchedule_Ctrl.chxState[channelIdx].currentFrameIdxInScheduleTbl = 0;
            LinSchedule_Ctrl.chxState[channelIdx].prevFrameIdxInScheduleTbl    = 0;

            LinSchedule_Ctrl.chxState[channelIdx].currentScheduleTblIdx  = LinSchedule_Ctrl.chxState[channelIdx].targetScheduleTblIdx;
            LinSchedule_Ctrl.chxState[channelIdx].prevScheduleTblIdx     = LinSchedule_Ctrl.chxState[channelIdx].currentScheduleTblIdx;
            LinSchedule_Ctrl.chxState[channelIdx].pCurrentScheduleTblPtr = (const LinSchedule_ScheduleTblListType *)LinSchedule_GetLinkScheduleTblPtr(channelIdx, LinSchedule_Ctrl.chxState[channelIdx].currentScheduleTblIdx);
        }
    }
}
#endif
