//******************************************** Включаемые файлы *********************************************

#include "task_man.h"
#include "ring_buffer.h"
#include "lmcal.h"

//******************************************* Локальные константы *******************************************

#define TM_EN_RECURSIVE_TASK    (1)

//******************************************* Проверка параметров *******************************************

//******************************************** Определения типов ********************************************

// Состояния таймера
typedef enum
{
    TM_STATE_STOP = (0U),
    TM_STATE_RUN,
    TM_STATE_DELAY,
} tm_state_t;

// Параметры таймера
typedef struct
{
    bool                    bInit;
    volatile bool           bRunStatus;
    volatile tm_state_t     nState;
    volatile TM_SZ          nCounter;        
    volatile TM_SZ          nPeriod;        
    void_fun_t              pTimerHandler;
    volatile TM_SZ          nDelay;
} tm_param_t;


//************************************* Локальные (приватные) переменные ************************************

// Параметры всех периодических таймеров
static  tm_param_t TM_Param[TM_PERIODIC_QTY];

static ringbuf_t  TM_stPeriodicFunFifo;
static ringbuf_t  TM_stAsyncFunFifo;
static void_fun_t TM_pPeriodicFunBuf[TM_PERIODIC_QTY];
static void_fun_t TM_pAsyncFunBuf[TM_ASYNC_QTY];


#if (TM_EN_RECURSIVE_TASK == 0)
static ringbuf_t  TM_stFunStsFifo;
static u16 TM_pFunStsBuf[TM_PERIODIC_QTY];
#endif



//***********************************************************************************************************
//******************************** Определения локальных (приватных) функций ********************************
//***********************************************************************************************************

// Выполнение счетчиков периодических задач. Необходимо запускать с периодически, каждую 1 мс.
static void f_do_periodic_task_cnt(void)
{
    for (u16 nCnt = 0U; nCnt < TM_PERIODIC_QTY; nCnt++)
    {
        switch(TM_Param[nCnt].nState)
        {
            case(TM_STATE_STOP):
            break;

            case(TM_STATE_RUN):
                // Инкремент счетчика
                TM_Param[nCnt].nCounter++;

                // Проверка счетчика
                if (TM_Param[nCnt].nCounter >= TM_Param[nCnt].nPeriod)
                {
                    TM_Param[nCnt].nCounter = 0;

                    // Добавление в очередь

                    #if (TM_EN_RECURSIVE_TASK == 1)
                    // Если задача выполняется в данный момент, она все равно доб. в очередь.
                    // Это может привести к рекурсивному вызову одной и той же функции без
                    // возможности доб. в очередь других задач.
                    if (NULL != TM_Param[nCnt].pTimerHandler)
                    {
                        rb_add_item(&TM_stPeriodicFunFifo, &TM_Param[nCnt].pTimerHandler);
                    }

                    #else
                    // Если задача выполняется, она не будет добавлена в очередь.
                    // Будет добавлена при следующем переполнении таймера

                    // todo - добавить сохранение в фифо номера задачи
                    // TM_Param[nCnt].bRunStatus = false;
                    #endif
                }
            break;

            case(TM_STATE_DELAY):
                // Декремент счетчика задержки
                TM_Param[nCnt].nDelay--;

                if (0 == TM_Param[nCnt].nDelay)
                {
                    // Переход в состояние RUN
                    TM_Param[nCnt].nState = TM_STATE_RUN;

                    // Добавление в очередь
                    if (NULL != TM_Param[nCnt].pTimerHandler)
                    {
                        rb_add_item(&TM_stPeriodicFunFifo, &TM_Param[nCnt].pTimerHandler);
                    }
                }
            break;

            default:
            break;
        } // switch
    } // for()
}


//***********************************************************************************************************
//******************************* Определения глобальных (публичных) функций ********************************
//***********************************************************************************************************

void tm_init(void)
{
    lmcal_cpu_interrupt_enable(false);

    // Инициализация очередей
    TM_stAsyncFunFifo.item_size    = sizeof(void_fun_t);
    TM_stPeriodicFunFifo.item_size = sizeof(void_fun_t);

    rb_init(&TM_stAsyncFunFifo,
                 TM_pAsyncFunBuf,
                 TM_ASYNC_QTY);

    rb_init(&TM_stPeriodicFunFifo,
                 TM_pPeriodicFunBuf,
                 TM_PERIODIC_QTY);

    rb_clear(&TM_stAsyncFunFifo);
    rb_clear(&TM_stPeriodicFunFifo);

    // Инициализация структуры периодических задач
    for(u8 nCnt = 0U; nCnt < TM_PERIODIC_QTY; nCnt++)
    {
        TM_Param[nCnt].bInit = false;
        TM_Param[nCnt].bRunStatus = false;
        TM_Param[nCnt].nState = TM_STATE_STOP;
        TM_Param[nCnt].nCounter = 0;
        TM_Param[nCnt].nPeriod = 0;
        TM_Param[nCnt].nDelay = 0;
        TM_Param[nCnt].pTimerHandler = NULL;
    }

    // Выполнить периодический вызов счетчиков задач
    lmcal_ptim_set_callback(f_do_periodic_task_cnt);

    lmcal_cpu_interrupt_enable(true);
}



fun_res_t tm_add_periodic_task(u8* const desc, void_fun_t fun)
{
    fun_res_t err = ERR_OK;
    
    err = L_ASSERT(NULL != desc);
    L_CHECK_ERR(err);
    err = L_ASSERT(NULL != fun);
    L_CHECK_ERR(err);

    lmcal_cpu_interrupt_enable(false);

    // Поиск "свободного" таймера
    err = ERR_RUNTIME_ERROR;
    for (u16 nCnt = 0U; nCnt < TM_PERIODIC_QTY; nCnt++)
    {
        if (false == TM_Param[nCnt].bInit)
        {
            // Нашли
            TM_Param[nCnt].bInit = true;
            TM_Param[nCnt].nState = TM_STATE_STOP;
            TM_Param[nCnt].bRunStatus = false;
            TM_Param[nCnt].nCounter = 0;
            TM_Param[nCnt].nPeriod = 0;
            TM_Param[nCnt].nDelay = 0;
            TM_Param[nCnt].pTimerHandler = fun;

            // Возвращаем номер таймера
            *desc = nCnt;
            
            err = ERR_OK;
            break;
        }
    }
    
    lmcal_cpu_interrupt_enable(true);

    L_EXIT;
    return err;
}



fun_res_t tm_deinit_periodic_task(u8 desc)
{
    fun_res_t err = ERR_OK;

    err = L_ASSERT(true == TM_Param[desc].bInit);
    L_CHECK_ERR(err);
    err = L_ASSERT(TM_PERIODIC_QTY > desc);
    L_CHECK_ERR(err);

    lmcal_cpu_interrupt_enable(false);

    TM_Param[desc].bInit = false;
    TM_Param[desc].nState = TM_STATE_STOP;
    
    lmcal_cpu_interrupt_enable(true);

    L_EXIT;
    return err;
}



fun_res_t tm_start_periodic_task(u8 desc, TM_SZ period)
{
    fun_res_t err = ERR_OK;

    err = L_ASSERT(true == TM_Param[desc].bInit);
    L_CHECK_ERR(err);
    err = L_ASSERT(TM_PERIODIC_QTY > desc);
    L_CHECK_ERR(err);
    err = L_ASSERT(period != 0);
    L_CHECK_ERR(err);

    lmcal_cpu_interrupt_enable(false);

    // Запуск таймера
    TM_Param[desc].nCounter = 0;
    TM_Param[desc].nPeriod = period;
    TM_Param[desc].nState = TM_STATE_RUN;
    
    lmcal_cpu_interrupt_enable(true);

    L_EXIT;
    return err;
}



fun_res_t tm_stop_periodic_task(u8 desc)
{
    fun_res_t err = ERR_OK;

    err = L_ASSERT(true == TM_Param[desc].bInit);
    L_CHECK_ERR(err);
    err = L_ASSERT(TM_PERIODIC_QTY > desc);
    L_CHECK_ERR(err);

    lmcal_cpu_interrupt_enable(false);

    // Остановка таймера
    TM_Param[desc].nState = TM_STATE_STOP;
    TM_Param[desc].nCounter = 0;
    TM_Param[desc].nPeriod = 0;

    lmcal_cpu_interrupt_enable(true);

    L_EXIT;
    return err;
}



fun_res_t tm_delay_periodic_task(u8 desc, TM_SZ delay)
{
    fun_res_t err = ERR_OK;

    err = L_ASSERT(true == TM_Param[desc].bInit);
    L_CHECK_ERR(err);
    err = L_ASSERT(TM_STATE_RUN == TM_Param[desc].nState);
    L_CHECK_ERR(err);
    err = L_ASSERT(TM_PERIODIC_QTY > desc);
    L_CHECK_ERR(err);
    err = L_ASSERT(delay != 0);
    L_CHECK_ERR(err);

    lmcal_cpu_interrupt_enable(false);

    // Настройка задержки
    TM_Param[desc].nDelay = delay;
    TM_Param[desc].nState = TM_STATE_DELAY;

    lmcal_cpu_interrupt_enable(true);

    L_EXIT;
    return err;
}



fun_res_t tm_add_async_task(void_fun_t fun)
{
    fun_res_t err = ERR_OK;
    rb_size_t nSize = 0;

    err = L_ASSERT(fun != NULL);
    L_CHECK_ERR(err);

    lmcal_cpu_interrupt_enable(false);

    // Добавляем в очередь, если есть место
    nSize = rb_get_free_size(&TM_stAsyncFunFifo);
    err = L_ASSERT(nSize > 0);
    L_CHECK_ERR(err);
    
    rb_add_item(&TM_stAsyncFunFifo, &fun);

    lmcal_cpu_interrupt_enable(true);

    L_EXIT;
    return err;
}



void tm_loop(void)
{
    bool bState = true;
    rb_size_t nAsyncSize = 0;
    rb_size_t nPeriodicSize = 0;
    void_fun_t pHandler = 0;

    while(bState)
    {
        nAsyncSize = rb_get_items_qty(&TM_stAsyncFunFifo);
        nPeriodicSize = rb_get_items_qty(&TM_stPeriodicFunFifo);

        // Выполняем ассинхронные (все за проход цикла) и периодические задачи (по 1 за итерацию)
        // т.е. у ассинхронных задач приоритет над периодическими
        while(nAsyncSize > 0)
        {
            rb_get_item(&TM_stAsyncFunFifo, &pHandler);

            // Выполнение задачи
            pHandler();

            nAsyncSize = rb_get_items_qty(&TM_stAsyncFunFifo);
        }

        if (nPeriodicSize > 0)
        {
            rb_get_item(&TM_stPeriodicFunFifo, &pHandler);

            // Выполнение задачи
            pHandler();
        }

        // Условие выхода из цикла
        if ((nPeriodicSize == 0) && (nAsyncSize == 0))
        {
            bState = false;
        }
    } // while()

}


//*********************************************** КОНЕЦ ФАЙЛА ***********************************************

