/*
 * 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.
 */
/*----------------------------------------------------------*/

//#define CON_TRACE

#define DISABLE_PARALLEL_MODE

#include <assert.h>
#include "IndustrialStack.h"

CIndustrialStack::CIndustrialStack(uint8_t deviceApi, int controlRxHandle, int controlTxHandle)
{
    msnw = new CISNetServiceWrapper(deviceApi, controlRxHandle, controlTxHandle);
    msnw->AddListener(this);
    GetQueue(0x3C8); //Make sure that Broadcast queue is the first one
}

CIndustrialStack::~CIndustrialStack()
{   
    delete msnw;
    deviceQueues.RemoveAll(true);
    intEventListeners.RemoveAll(false);
}

void CIndustrialStack::EnqueueElement(uint16_t nodeAddress, IISElement *element)
{
    CISDeviceQueue *dev = GetQueue(nodeAddress);
    assert(NULL != dev);
    element->AddReference();
    dev->elements.PushBack(element);
}

void CIndustrialStack::ServiceStack(uint32_t time)
{
    for (uint32_t i = 0; i < deviceQueues.Size(); i++)
    {
        assert(NULL != deviceQueues[i]);
        if (deviceQueues[i]->elements.Size() == 0)
            continue;
        IISElement *element = deviceQueues[i]->elements[0];
        assert(NULL != element);
        ISReturn_t state = element->Service(this, time);
        if (state == ISReturn_NoChange)
            continue;
        deviceQueues[i]->elements.PopFront();
        HandleStateChange(state, element);
        element->RemoveReference();
        msnw->ServiceMns();
    }
    msnw->ServiceMns();
}

bool CIndustrialStack::ElementsPending()
{
    for (uint32_t i = 0; i < deviceQueues.Size(); i++)
    {
        assert(NULL != deviceQueues[i]);
        if (deviceQueues[i]->elements.Size() != 0)
            return true;
    }
    return false;
}

bool CIndustrialStack::ElementsPending(uint16_t ingoreNodeAddress)
{
    for (uint32_t i = 0; i < deviceQueues.Size(); i++)
    {
        CISDeviceQueue *q = deviceQueues[i];
        assert(NULL != q);
        if (q->GetNodeAddress() == ingoreNodeAddress)
            continue;
        if (q->elements.Size() != 0)
            return true;
    }
    return false;
}

void CIndustrialStack::ClearAllElements()
{
    for (uint32_t i = 0; i < deviceQueues.Size(); i++)
    {
        assert(NULL != deviceQueues[i]);
        deviceQueues[i]->elements.RemoveAll(true);
    }
}

bool CIndustrialStack::SendMostMessage(CISMostMsg *message)
{
    assert(NULL != msnw);
    assert(NULL != message);
    if (NULL == message)
        return false;
#ifdef CON_TRACE
    ConsolePrintfStart(PRIO_HIGH, "IS TX, node:0x%X," \
        " fblock:0x%X, func:0x%X, op:0x%X pl:0x[", message->TargetAddress,
        message->FBlock, message->Func, message->OpType);
    for (uint32_t i = 0; i < message->PayloadLen; i++)
        ConsolePrintfContinue(" %02X", message->Payload[i]);
    ConsolePrintfExit(" ]\n");
#endif
    bool success = msnw->SendMostMessage(message);
    for (uint32_t i = 0; success && i < intEventListeners.Size(); i++)
    {
        intEventListeners[i]->OnMostMessage(this, message);
    }
    return success;
}

void CIndustrialStack::Unsynchronize()
{
    assert(NULL != msnw);
    return msnw->Unsynchronize();
}

void CIndustrialStack::AddInternalEventListener(CSInternalEvent* callback)
{
    intEventListeners.PushBack(callback);
}

/*--------------------------
 * Callback from MSN Wrapper
 *-------------------------- 
 */

void CIndustrialStack::OnReceivedMostMessage(CISMostMsg *rcvMessage)
{
    assert(NULL != rcvMessage);
    assert(rcvMessage->IsValid);
#ifdef CON_TRACE
    ConsolePrintfStart(PRIO_HIGH, "IS RX, node:0x%X," \
        " fblock:0x%X, func:0x%X, op:0x%X pl:0x[", rcvMessage->SourceAddress,
        rcvMessage->FBlock, rcvMessage->Func, rcvMessage->OpType);
    for (uint32_t i = 0; i < rcvMessage->PayloadLen; i++)
        ConsolePrintfContinue(" %02X", rcvMessage->Payload[i]);
    ConsolePrintfExit(" ]\n");
#endif    
    if (CISOpType_ERROR == rcvMessage->OpType
        || CISOpType_ERRORACK == rcvMessage->OpType)
    {
        //Ignore Destroy Resource Errors
        if (rcvMessage->Func != 0x800)
        {
            ConsolePrintfStart(PRIO_ERROR, RED"Received error message, node:0x%X," \
                " fblock:0x%X, func:0x%X, op:0x%X pl:0x[", rcvMessage->SourceAddress,
                rcvMessage->FBlock, rcvMessage->Func, rcvMessage->OpType);
            for (uint32_t i = 0; i < rcvMessage->PayloadLen; i++)
                ConsolePrintfContinue(" %02X", rcvMessage->Payload[i]);
            ConsolePrintfExit(" ]"RESETCOLOR"\n");
        }
    }
    for (uint32_t i = 0; i < deviceQueues.Size(); i++)
    {
        assert(NULL != deviceQueues[i]);
        IISElement *element = deviceQueues[i]->elements[0];
        if (NULL == element)
            continue;
        ISReturn_t state = element->OnMostMessage(this, rcvMessage);
        if (state == ISReturn_NoChange)
            continue;
        deviceQueues[i]->elements.PopFront();
        HandleStateChange(state, element);
        element->RemoveReference();
    }
    for (uint32_t i = 0; i < intEventListeners.Size(); i++)
    {
        intEventListeners[i]->OnMostMessage(this, rcvMessage);
    }
}

void CIndustrialStack::OnSyncStateChanged(bool isSynced)
{
    for (uint32_t i = 0; i < intEventListeners.Size(); i++)
    {
        assert(NULL != intEventListeners[i]);
        intEventListeners[i]->OnSyncStateChanged(this, isSynced);
    }
}

void CIndustrialStack::OnControlReadEnd()
{
    for (uint32_t i = 0; i < intEventListeners.Size(); i++)
    {
        assert(NULL != intEventListeners[i]);
        intEventListeners[i]->OnControlReadEnd(this);
    }
}

/*--------------------------
 * Private helper functions:
 *-------------------------- 
 */

#ifdef DISABLE_PARALLEL_MODE
CISDeviceQueue *CIndustrialStack::GetQueue(uint16_t nodeAddress)
{
    CISDeviceQueue *dev = deviceQueues[0];
    if (NULL == dev)
    {
        ConsolePrintf(PRIO_HIGH, GREEN"Creating new Device Queue"RESETCOLOR"\n");
        dev = new CISDeviceQueue(0);
        deviceQueues.PushBack(dev);
    }
    return dev;
}
#else
CISDeviceQueue *CIndustrialStack::GetQueue(uint16_t nodeAddress)
{
    CISDeviceQueue *dev = NULL;
    if (nodeAddress >= 0x400 && nodeAddress <= 0x4FF)
        nodeAddress -= 0x300;
    for (uint32_t i = 0; i < deviceQueues.Size(); i++)
    {
        if (deviceQueues[i]->GetNodeAddress() == nodeAddress)
        {
            dev = deviceQueues[i];
            break;
        }
    }
    if (NULL == dev)
    {
        ConsolePrintf(PRIO_HIGH, GREEN"Creating new Device Queue for"\
            " node address:0x%02X"RESETCOLOR"\n", nodeAddress);
        dev = new CISDeviceQueue(nodeAddress);
        deviceQueues.PushBack(dev);
    }
    return dev;
}
#endif

void CIndustrialStack::HandleStateChange(ISReturn_t state, IISElement * element)
{
    assert(NULL != element);
    switch (state)
    {
    case ISReturn_Failure:
    case ISReturn_Timeout:
        ConsolePrintf(PRIO_ERROR, RED"Job '%s' %s"RESETCOLOR"\n",
            element->ElementName, state == ISReturn_Failure ? "failed" : "timed out");
    case ISReturn_Success:
        if (NULL != element->Callback)
            element->Callback->ElementProcessed(this, state, element);
        break;
    case ISReturn_NoChange:
        //Nothing to do
        break;
    default:
        assert(false);
    }
}