/*
 * Video On Demand Samples
 *
 * 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.
 *
 */

/*----------------------------------------------------------*/
/*! \file
 *  \brief  This file contains the CIndustrialStack class (NetService part).
 */
/*----------------------------------------------------------*/

#define ENABLE_INIC_WATCHDOG true

#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include "IndustrialStack_MNS.h"
#include "IndustrialStack_Types.h"
#include "Board.h"
#include "Console.h"

static bool CheckMnswPointer(void *inst_ptr, const char *functionName);
static void Lld_CtrlStartC( Mns_Lld_Api_t *callbacks_ptr, void *ns_ptr, void *inst_ptr );
static void Lld_CtrlStopC( void *inst_ptr );
static void Lld_CtrlTxTransmitC( Mns_Lld_TxMsg_t *msg_ptr, void *inst_ptr );
static void Lld_CtrlRxMsgAvailableC( void *inst_ptr );
static uint16_t GetTickCountWordC();
static void OnMnslEventC( Mnsl_Event_t event_code, void *inst_ptr );
static void OnMnslServiceC( void *inst_ptr );
static void *OnMnslAmsAllocMemC( void *inst_ptr, uint16_t mem_size, Mns_Ams_MemUsage_t type, void** custom_info_pptr );
static void OnMnslAmsFreeMemC( void *inst_ptr, void *mem_ptr, Mns_Ams_MemUsage_t type, void* custom_info_ptr );
static void OnIcmRx( void *inst_ptr, Msg_MostTel_t *pRx );
static void OnRcmRx( void *inst_ptr, Msg_MostTel_t *pRx );
static void OnMcmRx( void *inst_ptr, Msg_MostTel_t *pRx );
static void OnTransmissionStatus(void *self, Msg_MostTel_t *tel_ptr, Mns_MsgTxStatus_t status);

CISNetServiceWrapper::CISNetServiceWrapper(uint8_t deviceApi, int controlRxHandle, int controlTxHandle) 
    : testPattern(MNSW_TESTPATTERN), inicWriteError( false ), pMnsInterface(NULL), pMnsInst(NULL)
    , isSynced(false), wrapperCB(NULL)
{
    if( 2 != deviceApi && 3 != deviceApi )
    {
        ConsolePrintf( PRIO_ERROR, RED"CISNetServiceWrapper was called with deviceApi set"\
            " to %d. This is not supported by this branch of NetworkManager"\
            RESETCOLOR"\n", deviceApi );
    }
    
    lld = new CIndustrialStackLld(controlRxHandle, controlTxHandle);
    lld->listener = this;
    
    memset(&pLldCb, 0, sizeof(pLldCb));
    pLldCb.start_fptr = &Lld_CtrlStartC;
    pLldCb.stop_fptr = &Lld_CtrlStopC;
    pLldCb.rx_available_fptr = &Lld_CtrlRxMsgAvailableC;
    pLldCb.tx_transmit_fptr = &Lld_CtrlTxTransmitC;
    
    Mnsl_InitData_t initData;
    Mnsl_SetDefaultConfig(&initData, ENABLE_INIC_WATCHDOG);
    initData.lld.start_fptr =  &Lld_CtrlStartC;
    initData.lld.stop_fptr = &Lld_CtrlStopC;
    initData.lld.rx_available_fptr = &Lld_CtrlRxMsgAvailableC;
    initData.lld.tx_transmit_fptr = &Lld_CtrlTxTransmitC;
    
    initData.general.get_tickcount_fptr = &GetTickCountWordC;
    initData.general.event_fptr = &OnMnslEventC;
    initData.general.request_service_fptr = &OnMnslServiceC;
    
    initData.pms.active_fifos = MNSL_FIFOS_MCM_ICM_RCM;
    initData.pms.compressed = (2 == deviceApi);
    
    initData.ams.rx_alloc_mem_fptr = &OnMnslAmsAllocMemC;
    initData.ams.rx_free_mem_fptr = &OnMnslAmsFreeMemC;
    
    Mnsl_Init( &mnsl, &initData, this );
    
    icm_inst_ptr = Mnsl_GetIcmTransceiver( &mnsl );           // Retrieve ICM instance
    rcm_inst_ptr = Mnsl_GetRcmTransceiver( &mnsl );           // Retrieve RCM instance
    mcm_inst_ptr = Mnsl_GetMcmTransceiver( &mnsl );           // Retrieve MCM instance

    Trcv_RxAssignReceiver( icm_inst_ptr, &OnIcmRx, this );    // Assign ICM Receiver Callback
    Trcv_RxAssignReceiver( rcm_inst_ptr, &OnRcmRx, this );    // Assign ICM Receiver Callback
    Trcv_RxAssignReceiver( mcm_inst_ptr, &OnMcmRx, this );    // Assign ICM Receiver Callback

    Mnsl_Synchronize( &mnsl );
}

CISNetServiceWrapper::~CISNetServiceWrapper()
{
    if (NULL != lld)
    {
        delete lld;
        lld = NULL;
    }
}

void CISNetServiceWrapper::ServiceMns()
{
    assert(NULL != lld);
    uint16_t wLen;
    while( 0 != ( wLen = lld->DataAvailable() ) )
    {
        if (NULL != pMnsInterface)
        {
            Mns_Lld_RxMsg_t *pRxMsg = pMnsInterface->rx_allocate_fptr( pMnsInst, wLen );
            if( pRxMsg )
            {
                if (wLen != lld->Read( pRxMsg->data_ptr, wLen ))
                    ConsolePrintf( PRIO_ERROR, RED"! LLD read error"RESETCOLOR"\n" ); //Must not happen
                pRxMsg->data_size = wLen;
                pMnsInterface->rx_receive_fptr( pMnsInst, pRxMsg );
                Mnsl_Service( &mnsl );
            }
            else
                ConsolePrintf( PRIO_ERROR, RED"! out of message memory"RESETCOLOR"\n" );
        }
    }
    Mnsl_Service( &mnsl );
}

void CISNetServiceWrapper::AddListener(IISNetServiceWrapperCB *rcvListener)
{
    wrapperCB = rcvListener;
}

bool CISNetServiceWrapper::SendMostMessage(CISMostMsg *msg)
{
    assert(NULL != msg);
    assert(msg->IsValid);
    
    CTransceiver *tr = NULL;
    
    if (0x1 == msg->TargetAddress || 0x100 == msg->TargetAddress)
        tr = icm_inst_ptr;
    else if (0x0 == msg->FBlock || 0x1 == msg->FBlock)
        tr = rcm_inst_ptr;
    else
        tr = mcm_inst_ptr;
    
    Msg_MostTel_t *tel = Trcv_TxAllocateMsg( tr, msg->PayloadLen );
    if( NULL == tel )
    {
        ConsolePrintf( PRIO_ERROR, RED"Trcv_TxAllocateMsg failed. Len:"\
            " %u, Message-Type=%s"RESETCOLOR"\n", msg->PayloadLen,
            (tr == icm_inst_ptr ? "ICM" : tr == rcm_inst_ptr ? "RCM" : "MCM"));
        return false;
    }
    tel->source_addr = msg->SourceAddress;
    tel->destination_addr = msg->TargetAddress;
    tel->id.fblock_id = msg->FBlock;
    tel->id.function_id = msg->Func;
    tel->id.instance_id = msg->Inst;
    tel->id.op_type = (Mns_OpType_t)msg->OpType;
    tel->tel.tel_len = msg->PayloadLen;
    tel->tel.tel_id = 0;
    if (0 != msg->PayloadLen)
    {
        if (NULL != tel->tel.tel_data_ptr)
        {
            memcpy(tel->tel.tel_data_ptr, msg->Payload, msg->PayloadLen);
        } else {
            ConsolePrintf(PRIO_ERROR, RED"CISNetServiceWrapper::SendMostMessage,"\
                " packet has NULL pointer payload"RESETCOLOR"\n");
            Trcv_TxReleaseMsg(tel);
            return false;
        }
    }
    Trcv_TxSendMsgExt( tr, tel, OnTransmissionStatus, NULL );
    return true;
}

void CISNetServiceWrapper::Unsynchronize()
{
    Mnsl_Unsynchronize( &mnsl, true );
}

void CISNetServiceWrapper::OnReadThreadEnd(CIndustrialStackLld *lld)
{
    if (NULL != wrapperCB)
        wrapperCB->OnControlReadEnd();
}

void CISNetServiceWrapper::OnCtrlTxTransmit( Mns_Lld_TxMsg_t *msg_ptr)
{
    assert(NULL != msg_ptr);
    assert(NULL != pMnsInterface);
    if( msg_ptr && pMnsInterface )
    {
        assert(NULL != lld);
        Mns_Mem_Buffer_t *pMemBuf;
#define MAX_DATA_LEN    72
        uint8_t data[MAX_DATA_LEN];
        uint8_t *pW = data;
        for( pMemBuf = msg_ptr->memory_ptr; pMemBuf != NULL;
            pMemBuf = pMemBuf->next_buffer_ptr )
        {
            if( pW + pMemBuf->data_size >= data + MAX_DATA_LEN )
            {
                ConsolePrintf( PRIO_ERROR, RED"invalid size"RESETCOLOR"\n" );
                return;
            }
            memcpy( pW, pMemBuf->data_ptr, pMemBuf->data_size );
            pW += pMemBuf->data_size;
        }

        if( !lld->Write( pW - data, data ) )
        {
            if (!inicWriteError)
            {
                inicWriteError = true;
                ConsolePrintf( PRIO_ERROR, RED"! Unable to write to INIC!"RESETCOLOR"\n" );
            }
        }
        else inicWriteError = false;

        pMnsInterface->tx_release_fptr( pMnsInst, msg_ptr );
    }
}

void CISNetServiceWrapper::OnMnslEvent( Mnsl_Event_t event_code )
{
    bool oldSyncState = isSynced;
    switch( event_code )
    {
    case MNSL_EVENT_SYNC_COMPLETE:
        isSynced = true;
        ConsolePrintf( PRIO_MEDIUM, "MNSL Event Callback notifies: MNSL_EVENT_SYNC_COMPLETE\n" );
        break;
    case MNSL_EVENT_SYNC_FAILED:
        isSynced = false;
        ConsolePrintf( PRIO_ERROR, YELLOW"MNSL Event Callback notifies: MNSL_EVENT_SYNC_FAILED" \
                ", retrying..\nMake sure that local INIC runs Firmware V2.3.0 or later!"RESETCOLOR"\n" );
        Mnsl_Synchronize( &mnsl );
        return; /* Do not report the event */
    case MNSL_EVENT_SYNC_LOST:
        isSynced = false;
        ConsolePrintf( PRIO_ERROR, "MNSL Event Callback notifies: MNSL_EVENT_SYNC_LOST\n" );
        break;
    case MNSL_EVENT_UNSYNC_COMPLETE:
        isSynced = false;
        ConsolePrintf( PRIO_MEDIUM, YELLOW"MNSL Event Callback notifies: MNSL_EVENT_UNSYNC_COMPLETE, syncing again.."RESETCOLOR"\n" );
        Mnsl_Synchronize( &mnsl );
        return; /* Do not report the event */
    case MNSL_EVENT_UNSYNC_FAILED:
        isSynced = false;
        ConsolePrintf( PRIO_ERROR, RED"MNSL Event Callback notifies: MNSL_EVENT_UNSYNC_FAILED"RESETCOLOR"\n" );
        Mnsl_Synchronize( &mnsl );
        break;
    default:
        ConsolePrintf( PRIO_ERROR, "MNSL Event Callback notifies: UNKNOWN CODE\n" );
        break;
    }
    if (NULL != wrapperCB && oldSyncState != isSynced)
    {
        wrapperCB->OnSyncStateChanged(isSynced);
    }
}

void CISNetServiceWrapper::OnMessage( Msg_MostTel_t *pRx, CTransceiver *pTr )
{
    assert(NULL != pRx);
    assert(pRx->tel.tel_len <= MAX_PAYLOAD_SIZE);
    if (NULL == wrapperCB)
        return;
    CISMostMsg msg;
    msg.IsValid = true;
    msg.SourceAddress = pRx->source_addr;
    msg.TargetAddress = pRx->destination_addr;
    msg.FBlock = pRx->id.fblock_id;
    msg.Func = pRx->id.function_id;
    msg.Inst = pRx->id.instance_id;
    msg.OpType = (CISOpType_t) pRx->id.op_type;
    msg.PayloadLen = pRx->tel.tel_len;
    memcpy(msg.Payload, pRx->tel.tel_data_ptr, msg.PayloadLen);
    Trcv_RxReleaseMsg(pTr, pRx);
    wrapperCB->OnReceivedMostMessage(&msg);
}

/*----------------------------------------
 * Private helper functions and C wrapper:
 *----------------------------------------
 */
static bool CheckMnswPointer(void *inst_ptr, const char *functionName)
{
    if( NULL == inst_ptr || MNSW_TESTPATTERN != ( ( CISNetServiceWrapper * )inst_ptr )->testPattern )
    {
        ConsolePrintf( PRIO_ERROR, RED"Parameter bug in %s"RESETCOLOR"\n", functionName );
        assert(false);
        return true;
    }
    return false;
}
static void Lld_CtrlStartC( Mns_Lld_Api_t *callbacks_ptr, void *ns_ptr, void *inst_ptr )
{
    if (CheckMnswPointer(inst_ptr, "Lld_CtrlStart")) return;
    CISNetServiceWrapper *mnsw = ( CISNetServiceWrapper * )inst_ptr;
    mnsw->pMnsInterface = callbacks_ptr;
    mnsw->pMnsInst = ns_ptr;
}
static void Lld_CtrlStopC( void *inst_ptr )
{
    if (CheckMnswPointer(inst_ptr, "Lld_CtrlStop")) return;
    CISNetServiceWrapper *mnsw = ( CISNetServiceWrapper * )inst_ptr;
    mnsw->pMnsInterface = 0;
    mnsw->pMnsInst = 0;
}
static void Lld_CtrlTxTransmitC( Mns_Lld_TxMsg_t *msg_ptr, void *inst_ptr )
{
    if (CheckMnswPointer(inst_ptr, "Lld_CtrlTxTransmit")) return;
    CISNetServiceWrapper *mnsw = ( CISNetServiceWrapper * )inst_ptr;
    mnsw->OnCtrlTxTransmit(msg_ptr);
}
static void Lld_CtrlRxMsgAvailableC( void *inst_ptr )
{
    //Unused
}
static uint16_t GetTickCountWordC()
{
    return GetTickCountWord();
}
static void OnMnslEventC( Mnsl_Event_t event_code, void *inst_ptr )
{
    if (CheckMnswPointer(inst_ptr, "OnMnslEvent")) return;
    CISNetServiceWrapper *mnsw = ( CISNetServiceWrapper * )inst_ptr;
    mnsw->OnMnslEvent(event_code);
}
static void OnMnslServiceC( void *inst_ptr )
{
    //Unused
}
static void *OnMnslAmsAllocMemC( void *inst_ptr, uint16_t mem_size, Mns_Ams_MemUsage_t type, void** custom_info_pptr )
{
    if( 0 == mem_size )
        return NULL;
    return calloc( mem_size, 1 );
}
static void OnMnslAmsFreeMemC( void *inst_ptr, void *mem_ptr, Mns_Ams_MemUsage_t type, void* custom_info_ptr )
{
    if( NULL != mem_ptr )
        free( mem_ptr );
}
static void OnIcmRx( void *inst_ptr, Msg_MostTel_t *pRx )
{
    if (CheckMnswPointer(inst_ptr, "OnIcmRx")) return;
    CISNetServiceWrapper *mnsw = ( CISNetServiceWrapper * )inst_ptr;
    mnsw->OnMessage(pRx, mnsw->icm_inst_ptr);
}
static void OnRcmRx( void *inst_ptr, Msg_MostTel_t *pRx )
{
    if (CheckMnswPointer(inst_ptr, "OnRcmRx")) return;
    CISNetServiceWrapper *mnsw = ( CISNetServiceWrapper * )inst_ptr;
    mnsw->OnMessage(pRx, mnsw->rcm_inst_ptr);
}
static void OnMcmRx( void *inst_ptr, Msg_MostTel_t *pRx )
{
    if (CheckMnswPointer(inst_ptr, "OnMcmRx")) return;
    CISNetServiceWrapper *mnsw = ( CISNetServiceWrapper * )inst_ptr;
    mnsw->OnMessage(pRx, mnsw->mcm_inst_ptr);
}

static void OnTransmissionStatus(void *self, Msg_MostTel_t *tel_ptr, Mns_MsgTxStatus_t status)
{
    if (MNS_MSG_STAT_OK != status)
    {
        ConsolePrintf(PRIO_ERROR, RED"Transmission failed for addr=0x%X FBlock=0x%X " \
            "Function=0x%X, error-code=0x%X"RESETCOLOR"\n", tel_ptr->destination_addr, 
            tel_ptr->id.fblock_id, tel_ptr->id.function_id, status);
    }
    Trcv_TxReleaseMsg(tel_ptr);
}

#define TRACE_BUFFER_SZ 200
#include <stdarg.h>
#include <stdio.h>
void My_TraceInfo(uint8_t mns_inst_id, const char module_str[], const char entry_str[], uint16_t vargs_cnt, ...)
{
    char_t outbuf[TRACE_BUFFER_SZ];
    va_list argptr;
    uint16_t timestamp = GetTickCountWord();
    va_start(argptr, vargs_cnt);
    vsprintf(outbuf, entry_str, argptr);
    va_end(argptr);
    ConsolePrintf(PRIO_HIGH, YELLOW"[%u] | %u | Info | %s | %s"RESETCOLOR"\n", mns_inst_id, timestamp, module_str, outbuf);
}

void My_TraceError(uint8_t mns_inst_id, const char module_str[], const char entry_str[], uint16_t vargs_cnt, ...)
{
    char_t outbuf[TRACE_BUFFER_SZ];
    va_list argptr;
    uint16_t timestamp = GetTickCountWord();
    va_start(argptr, vargs_cnt);
    vsprintf(outbuf, entry_str, argptr);
    va_end(argptr);
    ConsolePrintf(PRIO_ERROR, RED"[%u] | %u | Error | %s | %s"RESETCOLOR"\n", mns_inst_id, timestamp, module_str, outbuf);
}