/*
 * MOST NetServices "Light" V3.2.7.0.1796 MultiInstance Patch
 *
 * Copyright (C) 2015 Microchip Technology Germany II GmbH & Co. KG
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * You may also obtain this software under a propriety license from Microchip.
 * Please contact Microchip for further information.
 *
 */

/* parasoft suppress item * reason "not part of MOST NetServices delivery" */

/*!
 * \file
 * \brief Implementation of MOST NetServices Light
 */

/*------------------------------------------------------------------------------------------------*/
/* Includes                                                                                       */
/*------------------------------------------------------------------------------------------------*/
#include "mns_types_cfg.h"
#include "mnsl.h"
#include "mns_base.h"
#include "mns_misc.h"
#include "mns_pmchannel.h"
#include "mns_ams.h"
#include "mns_transceiver.h"
#include "mns_pmfifos.h"

#include <assert.h> //TKU

/*------------------------------------------------------------------------------------------------*/
/* Internal prototypes                                                                            */
/*------------------------------------------------------------------------------------------------*/
static void Mnsl_OnPmsEvent(void *self, void *event_code);
static void Mnsl_OnServiceRequest(void *self, void *data_ptr);
static void Mnsl_OnGetTickCount(void *self, void *tick_count_value_ptr);
static void Mnsl_OnSetApplicationTimer(void *self, void *new_time_value_ptr);

/*------------------------------------------------------------------------------------------------*/
/* Implementation of class CMnsl                                                                  */
/*------------------------------------------------------------------------------------------------*/
/*! \brief   Assigns default values to a provided MNSL init structure
 *  \param   init_ptr         Reference to MNSL init structure.
 *  \param   enable_watchdog  Set to \c true for normal use. Set to \c false to disable the watchdog 
 *                            supervision for debugging purpose only.
 */
extern void Mnsl_SetDefaultConfig(Mnsl_InitData_t *init_ptr, bool enable_watchdog)
{
    MISC_MEM_SET(init_ptr, 0, sizeof(Mnsl_InitData_t));

    init_ptr->pms.active_fifos = MNSL_FIFOS_MCM_ICM;
    init_ptr->pms.compressed = false;
    
    init_ptr->pms.icm_config.fifo_id = PMP_FIFO_ID_ICM;
    init_ptr->pms.icm_config.tx_wd_timeout = 0U;
    init_ptr->pms.icm_config.tx_wd_timer_value = 0U;
    init_ptr->pms.icm_config.rx_ack_timeout = 10U;
    init_ptr->pms.icm_config.rx_busy_allowed = 0xFU;
    init_ptr->pms.icm_config.rx_credits = PMCH_FIFO_CREDITS;
    init_ptr->pms.icm_config.rx_threshold = PMCH_FIFO_THRESHOLD;

    init_ptr->pms.rcm_config.fifo_id = PMP_FIFO_ID_RCM;
    init_ptr->pms.rcm_config.tx_wd_timeout = 10U;       /* watchdog timeout: 1s */
    init_ptr->pms.rcm_config.tx_wd_timer_value = 600U;  /* watchdog trigger every 600 ms */
    init_ptr->pms.rcm_config.rx_ack_timeout = 10U;
    init_ptr->pms.rcm_config.rx_busy_allowed = 0xFU;
    init_ptr->pms.rcm_config.rx_credits = PMCH_FIFO_CREDITS;
    init_ptr->pms.rcm_config.rx_threshold = PMCH_FIFO_THRESHOLD;

    init_ptr->pms.mcm_config.fifo_id = PMP_FIFO_ID_MCM;
    init_ptr->pms.mcm_config.tx_wd_timeout = 10U;       /* watchdog timeout: 1s */
    init_ptr->pms.mcm_config.tx_wd_timer_value = 600U;  /* watchdog trigger every 600 ms */
    init_ptr->pms.mcm_config.rx_ack_timeout = 10U;
    init_ptr->pms.mcm_config.rx_busy_allowed = 0xFU;
    init_ptr->pms.mcm_config.rx_credits = PMCH_MCM_CREDITS;
    init_ptr->pms.mcm_config.rx_threshold = PMCH_MCM_THRESHOLD;
                                                          
    if (enable_watchdog == false)
    {
        init_ptr->pms.icm_config.rx_ack_timeout = 0U;   /* acknowledge timeout: 0 -> infinite */
        init_ptr->pms.rcm_config.tx_wd_timeout = 0U;    /* watchdog timeout:    0 -> infinite */
        init_ptr->pms.rcm_config.tx_wd_timer_value = 0U;/* watchdog timer:      0 -> no timer */
        init_ptr->pms.rcm_config.rx_ack_timeout = 0U;   /* acknowledge timeout: 0 -> infinite */
        init_ptr->pms.mcm_config.tx_wd_timeout = 0U;    /* watchdog timeout:    0 -> infinite */
        init_ptr->pms.mcm_config.tx_wd_timer_value = 0U;/* watchdog timer:      0 -> no timer */
        init_ptr->pms.mcm_config.rx_ack_timeout = 0U;   /* acknowledge timeout: 0 -> infinite */
    }
}

/*! \brief  Initialize MNS Light class
 *  \param  init_ptr    Reference to the MNSL init structure.
 *                      The memory of the init structure can be freed after the function returns.
 */
void Mnsl_Init(CMnsl *mnsl, Mnsl_InitData_t *init_ptr, void *inst_ptr)
{
    assert(NULL != mnsl);
    CSingleObserver *srv_request_obs_ptr = NULL;
    CSingleObserver *app_timer_obs_ptr = NULL;
    Base_InitData_t base_init_data;
    Pmch_InitData_t pmch_init_data;
    CPmFifo *icm_ptr = NULL;
    CPmFifo *rcm_ptr = NULL;
    CPmFifo *mcm_ptr = NULL;
    CTransceiver *trcv_mcm_ptr = NULL;
    CTransceiver *trcv_rcm_ptr = NULL;

    MISC_MEM_SET(mnsl, 0, sizeof(mnsl));
    MISC_MEM_SET(&base_init_data, 0, sizeof(base_init_data));

    mnsl->inst_ptr = inst_ptr; //TKU
    mnsl->inst_id = 1U;
    mnsl->get_tick_count_fptr = init_ptr->general.get_tickcount_fptr;
    mnsl->srv_request_fptr = init_ptr->general.request_service_fptr;
    mnsl->set_app_timer_fptr = init_ptr->general.set_app_timer_fptr;

    if(mnsl->srv_request_fptr != NULL)
    {
        Sobs_Ctor(&mnsl->srv_request_obs, mnsl, &Mnsl_OnServiceRequest);
        srv_request_obs_ptr = &mnsl->srv_request_obs;
    }
    if (mnsl->set_app_timer_fptr != NULL)
    {
        Sobs_Ctor(&mnsl->set_app_timer_obs, mnsl, &Mnsl_OnSetApplicationTimer);
        app_timer_obs_ptr = &mnsl->set_app_timer_obs;
    }

    base_init_data.mns_inst_id = 1U;
    base_init_data.tm.mns_inst_id = 1U;
    base_init_data.scd.mns_inst_id = 1U;

    Sobs_Ctor(&mnsl->get_tick_count_obs, mnsl, &Mnsl_OnGetTickCount);
    base_init_data.tm.get_tick_count_obs_ptr = &mnsl->get_tick_count_obs;
    base_init_data.scd.service_request_obs_ptr = srv_request_obs_ptr;
    base_init_data.tm.set_application_timer_obs_ptr = app_timer_obs_ptr;

    Base_Ctor(&mnsl->base, &base_init_data);
    
    /* Initialize port message service */
    pmch_init_data.mns_inst_id = 1U;
    pmch_init_data.tx_release_fptr = &Fifo_TxOnRelease;
    pmch_init_data.lld_iface = init_ptr->lld;
    Pmch_Ctor(&mnsl->pm_channel, &pmch_init_data, inst_ptr);

    if((init_ptr->pms.active_fifos & MNSL_FIFOS_ICM) == MNSL_FIFOS_ICM)
    {
        Fifo_InitData_t icm_init;

        icm_init.base_ptr = &mnsl->base;
        icm_init.channel_ptr = &mnsl->pm_channel;
        icm_init.rx_cb_fptr = &Trcv_RxOnMsgComplete;
        icm_init.rx_cb_inst = &mnsl->icm_transceiver;

        if (init_ptr->pms.compressed)
        {
            icm_init.tx_encoder_ptr = Enc_GetEncoder(ENC_CONTENT_80);
            icm_init.rx_encoder_ptr = Enc_GetEncoder(ENC_CONTENT_80);
        }
        else
        {
            icm_init.tx_encoder_ptr = Enc_GetEncoder(ENC_CONTENT_00);
            icm_init.rx_encoder_ptr = Enc_GetEncoder(ENC_CONTENT_00);
        }

        Fifo_Ctor(&mnsl->icm_fifo, &icm_init, &init_ptr->pms.icm_config);
        Trcv_Ctor(&mnsl->icm_transceiver, &mnsl->icm_fifo, MSG_ADDR_EHC_CFG, base_init_data.mns_inst_id, PMP_FIFO_ID_ICM);/* initialize ICM transceiver and set link to PMS instance */

        icm_ptr = &mnsl->icm_fifo;
    }

    if((init_ptr->pms.active_fifos & MNSL_FIFOS_RCM) == MNSL_FIFOS_RCM)
        {
        Fifo_InitData_t rcm_init;

        rcm_init.base_ptr = &mnsl->base;
        rcm_init.channel_ptr = &mnsl->pm_channel;
        rcm_init.rx_cb_fptr = &Trcv_RxOnMsgComplete;
        rcm_init.rx_cb_inst = &mnsl->rcm_transceiver;
        rcm_init.tx_encoder_ptr = Enc_GetEncoder(ENC_CONTENT_00);
        rcm_init.rx_encoder_ptr = Enc_GetEncoder(ENC_CONTENT_00);
        /* Note: RCM compressed data format is not supported */

        Fifo_Ctor(&mnsl->rcm_fifo, &rcm_init, &init_ptr->pms.rcm_config);
        Trcv_Ctor(&mnsl->rcm_transceiver, &mnsl->rcm_fifo, MSG_ADDR_EHC_CFG, base_init_data.mns_inst_id, PMP_FIFO_ID_RCM);/* initialize ICM transceiver and set link to PMS instance */

        rcm_ptr = &mnsl->rcm_fifo;
        trcv_rcm_ptr = &mnsl->rcm_transceiver;
    }

    if((init_ptr->pms.active_fifos & MNSL_FIFOS_MCM) == MNSL_FIFOS_MCM)
    {
        Fifo_InitData_t mcm_init;

        mcm_init.base_ptr = &mnsl->base;
        mcm_init.channel_ptr = &mnsl->pm_channel;
        mcm_init.rx_cb_fptr = &Trcv_RxOnMsgComplete;
        mcm_init.rx_cb_inst = &mnsl->mcm_transceiver;

        if (init_ptr->pms.compressed)
        {
            mcm_init.tx_encoder_ptr = Enc_GetEncoder(ENC_CONTENT_81);
            mcm_init.rx_encoder_ptr = Enc_GetEncoder(ENC_CONTENT_80);
        }
        else
        {
            mcm_init.tx_encoder_ptr = Enc_GetEncoder(ENC_CONTENT_00);
            mcm_init.rx_encoder_ptr = Enc_GetEncoder(ENC_CONTENT_00);
        }

        Fifo_Ctor(&mnsl->mcm_fifo, &mcm_init, &init_ptr->pms.mcm_config);
        Trcv_Ctor(&mnsl->mcm_transceiver, &mnsl->mcm_fifo, MSG_ADDR_EHC_APP, base_init_data.mns_inst_id, PMP_FIFO_ID_MCM);/* initialize ICM transceiver and set link to PMS instance */

        mcm_ptr = &mnsl->mcm_fifo;
        trcv_mcm_ptr = &mnsl->mcm_transceiver;
        }

    if ((trcv_mcm_ptr != NULL) || (trcv_rcm_ptr != NULL))       /* initialize AMS if MCM or RCM is active */
    {
        mnsl->ams_allocator.alloc_fptr = init_ptr->ams.rx_alloc_mem_fptr;
        mnsl->ams_allocator.free_fptr = init_ptr->ams.rx_free_mem_fptr;
        Amsp_Ctor(&mnsl->ams_pool, &mnsl->ams_allocator, mnsl->base.mns_inst_id);
        Ams_Ctor(&mnsl->ams, &mnsl->base, trcv_mcm_ptr, trcv_rcm_ptr, &mnsl->ams_pool, AMS_RX_DEF_SIZE_PAYLOAD);
    }

    /* initialize FIFO handler */
    Fifos_Ctor(&mnsl->fifos, &mnsl->base, &mnsl->pm_channel, icm_ptr, mcm_ptr, rcm_ptr);

    /* register event callback */
    mnsl->event_fptr = init_ptr->general.event_fptr;
    Obs_Ctor(&mnsl->pms_observer, mnsl, &Mnsl_OnPmsEvent);
    Fifos_AddEventObserver(&mnsl->fifos, &mnsl->pms_observer);
}

/*! \brief   Synchronizes the PMS
 *  \details Accordingly MNSL_EVENT_SYNC_COMPLETE or MNSL_EVENT_SYNC_FAILED will be notified.
 */
void Mnsl_Synchronize(CMnsl *mnsl)
{
    assert(NULL != mnsl);
    /* initializes the port message service & LLD interface  */
    Fifos_ConfigureSyncParams(&mnsl->fifos, FIFOS_SYNC_RETRIES, FIFOS_SYNC_TIMEOUT);
    Fifos_Synchronize(&mnsl->fifos, true, true);
}

/*! \brief   Un-synchronizes the PMS
 *  \details Accordingly MNSL_EVENT_UNSYNC_COMPLETE or MNSL_EVENT_UNSYNC_FAILED will be notified.
 *           Calling this function if MNSL is already un-synced will report MNSL_EVENT_UNSYNC_FAILED,
 *           since the low-level driver interface is not active.
 *  \param   initial    MNSL is able to trigger un-synchronization in advance of the initial
 *                      synchronization. In this case, timeouts and retries behave like a synchronization
 *                      and on MNSL_EVENT_UNSYNC_COMPLETE the LLD interface will stay running.
 *                      I.e. if set to \c true the un-synchronization behaves like MNS initial un-synchronization.
 *                      If set to \c false, the un-synchronization behaves like MNS final un-synchronization.
 */
void Mnsl_Unsynchronize(CMnsl *mnsl, bool initial)
{    
    assert(NULL != mnsl);
    if (initial == false)
    {
        Fifos_ConfigureSyncParams(&mnsl->fifos, FIFOS_UNSYNC_RETRIES, FIFOS_UNSYNC_TIMEOUT); /* final sync */
    }
    else
    {
        Fifos_ConfigureSyncParams(&mnsl->fifos, FIFOS_SYNC_RETRIES, FIFOS_SYNC_TIMEOUT); /* initial sync */
    }
    
    Fifos_Unsynchronize(&mnsl->fifos, true, initial);
}

/*! \brief  Handles PMS event and notifies MNSL registered event callback
 *  \param  self         Reference to instance
 *  \param  event_code  Event notified by the PMS
 */
static void Mnsl_OnPmsEvent(void *self, void *event_code)
{
    assert(NULL != self);
    Fifos_Event_t *code = (Fifos_Event_t*)event_code;
    CMnsl *mnsl = (CMnsl *)self;    

    if (mnsl->event_fptr != NULL)
    {
        switch (*code)
        {
            case FIFOS_EV_SYNC_LOST:
                Eh_ReportEvent(&mnsl->base.eh, EH_E_SYNC_LOST);        /* event is necessary for AMS cleanup */
                mnsl->event_fptr(MNSL_EVENT_SYNC_LOST, mnsl->inst_ptr);
                break;
            case FIFOS_EV_SYNC_ESTABLISHED:
                mnsl->event_fptr(MNSL_EVENT_SYNC_COMPLETE, mnsl->inst_ptr);
                break;
            case FIFOS_EV_SYNC_FAILED:
                mnsl->event_fptr(MNSL_EVENT_SYNC_FAILED, mnsl->inst_ptr);
                break;
            case FIFOS_EV_UNSYNC_COMPLETE:
                mnsl->event_fptr(MNSL_EVENT_UNSYNC_COMPLETE, mnsl->inst_ptr);
                Eh_ReportEvent(&mnsl->base.eh, EH_E_UNSYNC_COMPLETE);   /* event is necessary for AMS cleanup */
                break;
            case FIFOS_EV_UNSYNC_FAILED:
                mnsl->event_fptr(MNSL_EVENT_UNSYNC_FAILED, mnsl->inst_ptr);
                Eh_ReportEvent(&mnsl->base.eh, EH_E_UNSYNC_FAILED);    /* event is necessary for AMS cleanup */
                break;
            default:
                break;
        }
    }
}

/*! \brief  Service function of MNS Light
 */
void Mnsl_Service(CMnsl *mnsl)
{
    assert(NULL != mnsl);
    bool pending_events;

    TR_INFO((mnsl->inst_id, "[API]", "Mnsl_Service()", 0U));
    Scd_Service(&mnsl->base.scd);                                                        /* Run the scheduler */
    pending_events = Scd_AreEventsPending(&mnsl->base.scd);                              /* Check if events are still pending? */

    if (pending_events != false)                                                        /* At least one event is pending? */
    {
        TR_INFO((mnsl->inst_id, "[API]", "Mnsl_Service(): events still pending", 0U));
        Mnsl_OnServiceRequest(mnsl, NULL);
    }
    else                                                                                /* No event is pending */
    {
        TR_INFO((mnsl->inst_id, "[API]", "Mnsl_Service(): calling Tm_CheckForNextService()", 0U));
        mnsl->base.scd.scd_srv_is_running = true;                                        /* prevent continuous service requests if no app timer is provided */
        Tm_CheckForNextService(&mnsl->base.tm);                                          /* If MNS timers are running: What is the next time that the timer management must be */
        mnsl->base.scd.scd_srv_is_running = false;                                       /* serviced again? */
    }
}

/*! \brief  Reports that the application provided timer has expired.
 */
void Mnsl_ReportTimeout(CMnsl *mnsl)
{
    assert(NULL != mnsl);
    TR_INFO((mnsl->inst_id, "[API]", "Mns_ReportTimeout()", 0U));
    Tm_TriggerService(&mnsl->base.tm);  /* Trigger TM service call */
}

/*! \brief  Callback function which is invoked by the scheduler
 *  \param self                     Parameter not used with single instance API
 *  \param data_ptr                 Currently unused
 */
static void Mnsl_OnServiceRequest(void *self, void *data_ptr)
{
    assert(NULL != self);
    CMnsl *mnsl = (CMnsl *)self;    
    MISC_UNUSED(data_ptr);

    if (mnsl->srv_request_fptr != NULL)
    {
        TR_INFO((mnsl->inst_id, "[API]", "Mnsl_OnServiceRequest(): calling srv_request_fptr()", 0U));
        mnsl->srv_request_fptr(mnsl->inst_ptr);
    }
}

/*! \brief This function is used in combination with the observer \c mns.get_tick_count_obs
 *         which is used to request the current tick count value form the application.
 *  \param self                     Parameter not used with single instance API
 *  \param tick_count_value_ptr     Reference to the requested tick count value. The pointer must 
 *                                  be casted into data type uint16_t.
 */
static void Mnsl_OnGetTickCount(void *self, void *tick_count_value_ptr)
{
    assert(NULL != self);
    CMnsl *mnsl = (CMnsl *)self;    
    *((uint16_t *)tick_count_value_ptr) = mnsl->get_tick_count_fptr();
}

/*! \brief  Callback function which is invoked to start the application timer when the MNSL service
 *          is implemented event driven         
 *  \param  self                The instance
 *  \param  new_time_value_ptr  Reference to the new timer value. The pointer must be casted into 
 *                              data type uint16_t.
 */
static void Mnsl_OnSetApplicationTimer(void *self, void *new_time_value_ptr)
{
    CMnsl *self_ = (CMnsl*)self;
    TR_INFO((mnsl->inst_id, "[API]", "Mnsl_OnSetApplicationTimer(): set_app_timer_fptr(%d)", 1U, *((uint16_t *)new_time_value_ptr)));
    self_->set_app_timer_fptr(*((uint16_t *)new_time_value_ptr), self_->inst_ptr);
}

/*! \brief  Returns the ICM transceiver object
 *  \return Reference to the ICM transceiver 
 */
CTransceiver * Mnsl_GetIcmTransceiver(CMnsl *mnsl)
{
    return &mnsl->icm_transceiver;
}

/*! \brief  Returns the RCM transceiver object
 *  \return Reference to the RCM transceiver 
 */
CTransceiver * Mnsl_GetRcmTransceiver(CMnsl *mnsl)
{
    return &mnsl->rcm_transceiver;
}

/*! \brief  Returns the MCM transceiver object
 *  \return Reference to the MCM transceiver 
 */
CTransceiver * Mnsl_GetMcmTransceiver(CMnsl *mnsl)
{
    return &mnsl->mcm_transceiver;
}

/*! \brief      Returns the AMS transceiver object
 *  \details    It is important not make usage of competing transceivers
 *              AMS, MCM and RCM. Either use AMS exclusively or MCM and RCM.
 *              Important: Do not use AMS in mode \c MNSL_FIFOS_ICM. 
 *  \return     Reference to the AMS
 */
CAms * Mnsl_GetAmsTransceiver(CMnsl *mnsl)
{
    return &mnsl->ams;
}

/*------------------------------------------------------------------------------------------------*/
/* End of file                                                                                    */
/*------------------------------------------------------------------------------------------------*/