//*****************************************************************************
//
//! @file am_vos_util.c
//!
//! @brief Task, timer, print wrapper
//
//*****************************************************************************

//*****************************************************************************
//
// Copyright (c) 2024, Ambiq Micro, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// Third party software included in this distribution is subject to the
// additional license terms as defined in the /docs/licenses directory.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// This is part of revision ambiqvos_r4.5-fdfa8cf6a4 of the AmbiqSuite Development Package.
//
//*****************************************************************************

#include "am_vos_sys_config.h"

#include "am_mcu_apollo.h"
#include "am_util.h"

#include "am_vos_utils.h"
#include "am_vos_task.h"

//*****************************************************************************
//
// Global Variables
//
//*****************************************************************************
AmVosUtil g_sAmUtil =
{
    .pfnPrintf = NULL,
    .ui32BuffIndx = 0,
};

//*****************************************************************************
//
//! @brief Sets the interface for printf calls.
//!
//! @param pfnPrintFunc - Function pointer to be used to print to interface
//!
//! This function initializes the global print function which is used for
//! printf. This allows users to define their own printf interface and pass it
//! in as an am_vos_stdio_print_t type. 
//!
//! @return None.
//
//*****************************************************************************
void
am_vos_stdio_printf_init(am_vos_stdio_printf_t pfnPrintFunc)
{
    g_sAmUtil.pfnPrintf = pfnPrintFunc;
}

//*****************************************************************************
//
//! @brief A lite version of printf()
//!
//! @param *pcFmt - Pointer to formatter string
//!
//!
//!
//! @return uint32_t representing the number of characters printed.
//
// *****************************************************************************
uint32_t
am_vos_stdio_printf(uint8_t print_type, const char *pcFmt, ...)
{
    uint32_t ui32NumChars;
    uint8_t print_buff[AM_VOS_PRINTF_BUFFSIZE]; // local buffer to handle the data

    AM_CRITICAL_BEGIN;
    
    configASSERT(strlen(pcFmt) < AM_VOS_PRINTF_BUFFSIZE);
    
    //
    // Convert to the desired string.
    //
    va_list pArgs;
    va_start(pArgs, pcFmt);
    ui32NumChars = am_util_stdio_vsprintf((char *)print_buff, pcFmt, pArgs);
    va_end(pArgs);

    if(g_sAmUtil.ui32BuffIndx + ui32NumChars > (AM_VOS_PRINTF_TOTAL_BUFFSIZE - 1))
    {
        g_sAmUtil.ui32BuffIndx = 0;
    }

    // move data into global print buffer
#if configUSE_PRINTF_UART0
    if(print_type == 1) {
        strcpy((char *)(&(g_sAmUtil.pcStdioBuff[g_sAmUtil.ui32BuffIndx])), "\033[1;32m");
        memcpy((char *)(&(g_sAmUtil.pcStdioBuff[g_sAmUtil.ui32BuffIndx + 7])), (char *)print_buff, ui32NumChars);
        strcpy((char *)(&(g_sAmUtil.pcStdioBuff[g_sAmUtil.ui32BuffIndx + 7 + ui32NumChars])), "\033[0m");
        ui32NumChars += 11;
    }
    else if(print_type == 2) {
        strcpy((char *)((&g_sAmUtil.pcStdioBuff[g_sAmUtil.ui32BuffIndx])), "\033[1;33m");
        memcpy((char *)((&g_sAmUtil.pcStdioBuff[g_sAmUtil.ui32BuffIndx + 7])), (char *)print_buff, ui32NumChars);
        strcpy((char *)((&g_sAmUtil.pcStdioBuff[g_sAmUtil.ui32BuffIndx + 7 + ui32NumChars])), "\033[0m");
        ui32NumChars += 11;
    }
    else {
        memcpy((char *)(&(g_sAmUtil.pcStdioBuff[g_sAmUtil.ui32BuffIndx])), (char *)print_buff, ui32NumChars);
    }
#else // configUSE_PRINTF_UART0
    memcpy((char *)(&(g_sAmUtil.pcStdioBuff[g_sAmUtil.ui32BuffIndx])), (char *)print_buff, ui32NumChars);
#endif // configUSE_PRINTF_UART0
    
    g_sAmUtil.pcStdioBuff[g_sAmUtil.ui32BuffIndx + ui32NumChars] = NULL;

    //
    // This is where we print the buffer to the configured interface.
    //
    if(g_sAmUtil.pfnPrintf)
        g_sAmUtil.pfnPrintf(g_sAmUtil.ui32BuffIndx, print_type);
    
    g_sAmUtil.ui32BuffIndx += ui32NumChars + 1;   //add 1 byte NULL
    
    AM_CRITICAL_END;

    //
    // return the number of characters printed.
    //
    return ui32NumChars;
}

//*****************************************************************************
//
// Initialize the tasks list 
//
//
// Returns true if success.
//
//*****************************************************************************
void am_vos_task_init(void)
{
    uint32_t i;
    for(i=0 ; i<AM_VOS_MAX_TASK; i++)
    {
        am_KWD_tasks[i].task = NULL;
        am_KWD_tasks[i].queue = NULL;
    }
}

//*****************************************************************************
//
// Create the RTOS task and queue specified by indx, with the specified setup.
//
// This may block and if it does it blocks forever.
//
// Returns true if message successfully sent.
//
//*****************************************************************************
void 
am_vos_task_create(am_vos_task_setup_t setup)
{
    configASSERT(setup.indx < AM_VOS_MAX_TASK);
    xTaskCreate(setup.pxTaskCode, setup.pcName, setup.usStackDepth, setup.pvParameters, setup.uxPriority, &(am_KWD_tasks[setup.indx].task));
    configASSERT(am_KWD_tasks[setup.indx].task != NULL);
    am_KWD_tasks[setup.indx].queue = xQueueCreate(setup.uxQueueLength, sizeof(am_vos_task_queue_element_t));  
    configASSERT(am_KWD_tasks[setup.indx].queue != NULL);
}

//*****************************************************************************
//
// Initialize system timers 
//
//
// Returns true if success.
//
//*****************************************************************************

void am_vos_timer_create(am_vos_timer_setup_t setup)
{
    configASSERT(setup.indx < AM_VOS_MAX_TIMER);
    am_KWD_timers[setup.indx] = xTimerCreate(setup.pcTimerName, setup.xTimerPeriodInTicks, 
                                                setup.uxAutoReload, 0, setup.pxTaskCode);

}

//*****************************************************************************
//
// Initializes all timers
//
//*****************************************************************************
void
am_vos_timer_create_all_timers(const am_vos_timer_setup_t *setup_array, uint8_t timer_count)
{
    for(uint8_t timer = 0; timer<timer_count; timer++)
    {
        // Spawn all timers
        am_vos_timer_create(setup_array[timer]);
    }

}

//*****************************************************************************
//
// Send a message from the Source task to Dest queue
//
// This may block and if it does it blocks forever.
//
// Returns true if message successfully sent.
//
//*****************************************************************************
bool
am_vos_task_send(am_vos_task_enum_t Source, am_vos_task_enum_t Dest, uint32_t ui32MessageType, 
                    uint32_t ui32Info, am_vos_ring_buffer_t *pData)
{
    BaseType_t retval = pdFAIL;
    am_vos_task_queue_element_t Element;
    Element.Source = Source;
        
    configASSERT(ui32MessageType < AM_VOS_MESSAGE_MAX);
    Element.ui32MessageType = ui32MessageType;
    switch(Element.ui32MessageType)
    {
        case AM_VOS_MESSAGE_SHORT:
            Element.info.ui32Note = ui32Info;
            break;
        case AM_VOS_MESSAGE_LONG:
            Element.info.ui32Length = ui32Info;
            break;
        case AM_VOS_MESSAGE_STR:
            Element.info.ui32Indx = ui32Info;
            break;
        default:
            break;
    }
    Element.pDataBuffer = pData;
    if(am_KWD_tasks[Dest].queue != NULL)
    {
        retval = xQueueSendToBack(am_KWD_tasks[Dest].queue, &Element, 0 );
    }
    
    return (retval != pdFAIL);
}

//*****************************************************************************
//
// Send a message from the Source task to the Dest Queue, from an ISR
//
// This may block and if it does it blocks forever.
//
// Returns true if message successfully sent.
//
//*****************************************************************************
bool
am_vos_task_send_fromISR(am_vos_task_enum_t Source, am_vos_task_enum_t Dest, uint32_t ui32MessageType, 
                           uint32_t ui32Info, am_vos_ring_buffer_t *pData)
{
    BaseType_t retval = pdFAIL;
    am_vos_task_queue_element_t Element;
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    Element.Source = Source;
    Element.ui32MessageType = ui32MessageType;
    switch(Element.ui32MessageType)
    {
        case AM_VOS_MESSAGE_SHORT:
            Element.info.ui32Note = ui32Info;
            break;
        case AM_VOS_MESSAGE_LONG:
            Element.info.ui32Length = ui32Info;
            break;
        case AM_VOS_MESSAGE_STR:
            Element.info.ui32Indx = ui32Info;
            break;
        default:
            break;
    }
    Element.pDataBuffer = pData;

    if(am_KWD_tasks[Dest].queue != NULL)
    {
        retval = xQueueSendFromISR( am_KWD_tasks[Dest].queue , &Element, &xHigherPriorityTaskWoken );
    }

    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    
    return (retval != pdFAIL);
}

//*****************************************************************************
//
// Read a message from the Queue specified by indx.
//
// This may block and if it does it blocks forever.
//
// Returns true if message successfully read.
//
//*****************************************************************************
bool
am_vos_task_read(am_vos_task_enum_t indx, am_vos_task_queue_element_t *Element)
{
    BaseType_t retval = pdFAIL;
    if(am_KWD_tasks[indx].queue != NULL)
    {
        retval = xQueueReceive(am_KWD_tasks[indx].queue, Element, portMAX_DELAY );
    }

    return retval; 
}

//*****************************************************************************
//
// Get the RTOS task handle from the app_task specified by indx.
//
// This may block and if it does it blocks forever.
//
// Returns true if message successfully read.
//
//*****************************************************************************
TaskHandle_t am_vos_task_get_task_handle(am_vos_task_enum_t indx)
{
    return am_KWD_tasks[indx].task;
}

//*****************************************************************************
//
// Initializes all tasks
//
//*****************************************************************************
void
am_vos_task_create_all_tasks(const am_vos_task_setup_t *setup_array, uint8_t task_count)
{
    for(uint8_t task = 0; task<task_count; task++)
    {
        // Spawn all tasks
        am_vos_task_create(setup_array[task]);
    }
}

//*****************************************************************************
//
// Resume the task specified by indx.
//
//*****************************************************************************
void am_vos_task_resume(am_vos_task_enum_t indx)
{
    if(am_KWD_tasks[indx].task != NULL)
    {
        vTaskResume(am_KWD_tasks[indx].task);
    }
}

void am_vos_task_suspend(am_vos_task_enum_t indx)
{
    if(am_KWD_tasks[indx].task != NULL)
    {
        vTaskSuspend(am_KWD_tasks[indx].task);
    }
}
