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

#include <stdint.h>
#include <stddef.h>
#include "SafeVector.h"
#include "Network.h"
#include "NetworkDevice.h"
#include "DriverConfiguration.h"
#include "MacAddr.h"
#include "Board.h"
#include "Console.h"
#include "NodeDatabase.h"

using namespace std;

static CNetwork *staticInstance = NULL;
CSafeVector<CNetworkDevice *> CNetwork::allNetworkDevices;

CNetwork::CNetwork() : CThread( "CNetwork", false ), allowNetworkRun( false ), 
    vodXml( NULL ), connectionBitMask( 0 ), promiscuous( false ), retryCounter( 0 ),
    retryExecTime( 0 )
{
    searchPath[0] = 0;
    sem_init( &vodXmlMutex, 0, 1 ); //Mutex, initialized to 1 => sem_wait will block at 2nd call
    Start();
}

CNetwork::~CNetwork()
{
    ConsolePrintf( PRIO_LOW, "Destructor of network called\r\n" );
    Stop();
    allowNetworkRun = false;
    CloseAllNetworkDevices();
    sem_wait( &vodXmlMutex );
    if( NULL != vodXml )
    {
        delete vodXml;
        vodXml = NULL;
    }
    sem_post( &vodXmlMutex );
    staticInstance = NULL;
    ConsolePrintf( PRIO_LOW, "end network\r\n" );
}

CNetwork *CNetwork::GetInstance( void )
{
    if( NULL == staticInstance )
    {
        staticInstance = new CNetwork();
    }
    return staticInstance;
}

void CNetwork::AddListener( CNetworkListner *listener )
{
    allListeners.PushBack( listener );
}

void CNetwork::RemoveListener( CNetworkListner *listener )
{
    allListeners.Remove(listener);
}

void CNetwork::SetPromiscuousMode( bool enabled )
{
    promiscuous = enabled;
}

bool CNetwork::LoadConfig( char *szConfigXml )
{
    uint32_t len = strlen(szConfigXml);
    if( NULL == szConfigXml || 0 == len)
    {
        ConsolePrintf( PRIO_ERROR, RED"Path to config file was NULL"RESETCOLOR"\n" );
        return false;
    }
    if ('/' == szConfigXml[0])
    {
        char path[300];
        uint32_t i;
        uint32_t lastSlash = 0;
        for (i = len - 1; i > 0; i--)
        {
            if ('/' == szConfigXml[i])
            {
                lastSlash = i;
                break;
            }
        }
        memcpy(path, szConfigXml, lastSlash);
        path[lastSlash] = '\0';
        memcpy(szConfigXml, &szConfigXml[lastSlash + 1], (len - lastSlash - 1) );
        szConfigXml[len - lastSlash - 1] = '\0';
        return LoadConfig(szConfigXml, path);
    }
    else
    {
        return LoadConfig(szConfigXml, NULL);
    }
}
    
bool CNetwork::LoadConfig( const char *szConfigXml, const char *szSearchPath )
{
    char configPath[300];
    if( NULL == szConfigXml || 0 == strlen(szConfigXml))
    {
        ConsolePrintf( PRIO_ERROR, RED"Path to config file was NULL"RESETCOLOR"\n" );
        return false;
    }
    if (NULL != szSearchPath)
        strncpy( searchPath, szSearchPath, sizeof( searchPath ) );
    if( NULL != searchPath && '/' != szConfigXml[0])
        snprintf( configPath, sizeof( configPath ), "%s/%s", searchPath, szConfigXml );
    else
        strncpy( configPath, szConfigXml, sizeof( configPath ) );

    allowNetworkRun = true;
    
    sem_wait( &vodXmlMutex );
    bool success;
    if( NULL != vodXml )
    {
        delete vodXml;
    }
    vodXml = new CVodXml( configPath );
    success = ( NULL != vodXml );
    sem_post( &vodXmlMutex );
        
    if ( allNetworkDevices.Size() != 0 )
    {
        bool tm;
        uint32_t bw;
        if (vodXml->GetMostParameters( &tm, &bw ) )
        {
            for( uint32_t i = 0; i < allNetworkDevices.Size(); i++ )
            {
                allNetworkDevices[i]->SetAsyncBandwidth(bw);
                allNetworkDevices[i]->SetTimingMaster(tm);
                ShutdownMost(allNetworkDevices[i]);
            }
        }
    }
    
    if( success )
        FindNewNetworkDevices();

    retryCounter = 0;
    return success;
}

bool CNetwork::ActionsPending()
{
    bool isPending = false;
    for( uint32_t i = 0; i < allNetworkDevices.Size(); i++ )
    {
        if( allNetworkDevices[i]->ActionsPending() )
        {
            isPending = true;
            break;
        }
    }
    return isPending;
}

bool CNetwork::ConnectSourceToSink( CMacAddr *SourceMacAddr, TChannelId SourceChannelId, CMacAddr *SinkMacAddr,
    TChannelId SinkChannelId )
{
    CNodeEntry *inNode = CNodeEntry::GetNodeEntry( SourceMacAddr );
    CNodeEntry *outNode = CNodeEntry::GetNodeEntry( SinkMacAddr );
    return ConnectSourceToSink( inNode, outNode, SourceChannelId, SinkChannelId );
}

uint8_t CNetwork::GetAmountOfLocalNodes()
{
    return allNetworkDevices.Size();
}

bool CNetwork::SendMostControlMessage( TMostInstace mostInstance, uint32_t targetAddr, uint32_t nFBlock,
    uint32_t nInst, uint32_t nFunc, uint8_t nOpType, uint32_t nPayloadLen, const uint8_t *Payload )
{
    bool success = false;
    for( uint32_t i = 0; i < allNetworkDevices.Size(); i++ )
    {
        if( allNetworkDevices[i]->GetDeviceIndex() == mostInstance )
        {
            if (targetAddr < 0x300 || targetAddr > 0x3FF || targetAddr == 0x3C8)
            {
                success = allNetworkDevices[i]->SendMostControlMessage( mostInstance, targetAddr, nFBlock, nInst, nFunc,
                    nOpType, nPayloadLen, Payload );
            }
            else
            {
                success = true;
                for (uint16_t j = 0; j < CNodeEntry::GetAmountOfNodeEntries(); j++)
                {
                    CNodeEntry *node = CNodeEntry::GetNodeEntry(j);
                    if (NULL != node && node->deviceInstance == mostInstance &&
                        node->deviceType == targetAddr)
                    {
                         success &= allNetworkDevices[i]->SendMostControlMessage( mostInstance, node->nodeAddress, nFBlock, nInst, nFunc,
                            nOpType, nPayloadLen, Payload );
                    }
                }
            }
            break;
        }
    }
    return success;
}

bool CNetwork::SendMostControlMessage( TMostInstace mostInstance, TDeviceId deviceId, uint8_t devInst,
    uint32_t nFBlock, uint32_t nInst, uint32_t nFunc, uint8_t nOpType, uint32_t nPayloadLen, const uint8_t *Payload )
{
    bool success = false;

    CNodeEntry *entry = CNodeEntry::GetNodeEntry( mostInstance, deviceId, devInst );
    if( NULL == entry )
        return success;

    for( uint32_t i = 0; i < allNetworkDevices.Size(); i++ )
    {
        if( allNetworkDevices[i]->GetDeviceIndex() == mostInstance )
        {
            success = allNetworkDevices[i]->SendMostControlMessage( mostInstance, entry->nodeAddress, nFBlock, nInst,
                nFunc, nOpType, nPayloadLen, Payload );
            break;
        }
    }
    return success;
}

bool CNetwork::ExecuteXmlScriptFromFile( TMostInstace mostInstance, uint32_t targetAddr, const char *szFile )
{
    if (NULL == szFile)
        return false;
    bool success = false;
    char scriptPath[300];
    const char *pPath = szFile;
    if( NULL != searchPath && '/' != szFile[0])
    {
        snprintf( scriptPath, sizeof( scriptPath ), "%s/%s", searchPath, szFile );
        pPath = scriptPath;
    }
    for( uint32_t i = 0; i < allNetworkDevices.Size(); i++ )
    {
        if( allNetworkDevices[i]->GetDeviceIndex() == mostInstance )
        {
            allNetworkDevices[i]->ExecuteMcmScript( targetAddr, pPath );
            success = true;
            break;
        }
    }
    return success;
}

bool CNetwork::ExecuteXmlScriptFromFile( TMostInstace mostInstance, TDeviceId deviceId, uint8_t devInst,
    const char *szFile )
{
    CNodeEntry *entry = CNodeEntry::GetNodeEntry( mostInstance, deviceId, devInst );
    if( NULL == entry )
        return false;

    return ExecuteXmlScriptFromFile( mostInstance, entry->nodeAddress, szFile );
}

bool CNetwork::ExecuteXmlScriptFromMemory( TMostInstace mostInstance, uint32_t targetAddr, const char *szBuffer,
    uint32_t nBufferLen )
{
    bool success = false;
    for( uint32_t i = 0; i < allNetworkDevices.Size(); i++ )
    {
        if( allNetworkDevices[i]->GetDeviceIndex() == mostInstance )
        {
            allNetworkDevices[i]->ExecuteMcmScript( targetAddr, szBuffer, nBufferLen );
            success = true;
            break;
        }
    }
    return success;
}

bool CNetwork::ExecuteXmlScriptFromMemory( TMostInstace mostInstance, TDeviceId deviceId, uint8_t devInst,
    const char *szBuffer, uint32_t nBufferLen )
{
    CNodeEntry *entry = CNodeEntry::GetNodeEntry( mostInstance, deviceId, devInst );
    if( NULL == entry )
        return false;

    return ExecuteXmlScriptFromMemory( mostInstance, entry->nodeAddress, szBuffer, nBufferLen );
}

uint32_t CNetwork::GetCounterPartOfRoute( const Route_t *pInRoute, Route_t **pOutRouteArray )
{
    if( ( NULL == pInRoute ) || ( NULL == pOutRouteArray ) )
    {
        ConsolePrintf( PRIO_ERROR, RED"GetCounterPartOfRoute was called with wrong parameters"RESETCOLOR"\n" );
        return 0;
    }
    *pOutRouteArray = NULL;
    CSafeVector<CRouteTerminal *> connectedTerminals;
    CRouteTerminal terminal;
    terminal.deviceType = pInRoute->deviceType;
    terminal.instance = pInRoute->instance;
    terminal.channelId = pInRoute->channelId;

    sem_wait( &vodXmlMutex );
    bool found = vodXml->GetRouteTerminals( &terminal, connectedTerminals );
    sem_post( &vodXmlMutex );
    if( !found )
        return 0;
    uint32_t arrayLen = connectedTerminals.Size();
    *pOutRouteArray = ( Route_t * )calloc( arrayLen, sizeof( Route_t ) );
    if( NULL == *pOutRouteArray )
    {
        ConsolePrintf( PRIO_ERROR, RED"GetCounterPartOfRoute failed to allocate memory"RESETCOLOR"\n" );
        return 0;
    }
    for( uint32_t i = 0; i < arrayLen; i++ )
    {
        ( *pOutRouteArray )[i].deviceType = connectedTerminals[i]->deviceType;
        ( *pOutRouteArray )[i].instance = connectedTerminals[i]->instance;
        ( *pOutRouteArray )[i].channelId = connectedTerminals[i]->channelId;
    }
    return arrayLen;
}

bool CNetwork::GetRingBreakDiagnosisResult( TMostInstace mostInstance, uint32_t targetAddr )
{
    bool success = false;
    for( uint32_t i = 0; i < allNetworkDevices.Size(); i++ )
    {
        if( allNetworkDevices[i]->GetDeviceIndex() == mostInstance )
        {
            allNetworkDevices[i]->GetRingBreakResultV3( 1, 0 );
            success = true;
            break;
        }
    }
    return success;
}

void CNetwork::EnableMost(bool enabled)
{
    allowNetworkRun = enabled;
    for( uint32_t i = 0; i < allNetworkDevices.Size(); i++ )
    {
        CNetworkDevice *device = allNetworkDevices[i];
        if (enabled)
        {
            if (NetworkState_Available == device->GetNetState())
            {
                ConsolePrintf(PRIO_ERROR, RED"MOST is already in NET ON state"RESETCOLOR"\n");
                return;
            }
            device->SetNetstate( NetworkState_Unknown );
            bool isTimingMaster = device->IsTimingMaster();
            uint32_t asyncBandwidth = device->GetAsyncBandwidth();
            if( isTimingMaster )
                device->MostNetworkStartupV3( 0xFFFF, asyncBandwidth );
        }
        else
        {
            device->SetNetstate( NetworkState_ShutdownInProgress );
            device->ClearAllPendingActions();
            device->MostNetworkShutdownV3();
        }
    }
}

void CNetwork::Run()
{
    sleep( 5 );
    if (retryCounter > 0 && (GetTickCount() - retryExecTime >= 60000))
        retryCounter = 0;
    if( allowNetworkRun )
        FindNewNetworkDevices();
}

/*---------------------------------------------*/
/*   Empty implementation of CNetworkListner   */
/*---------------------------------------------*/

void CNetworkListner::OnNetworkState( uint8_t mostInstance, bool available, uint8_t maxPos, uint16_t packetBW )
{
    ConsolePrintf( PRIO_LOW, "CNetwork::OnNetworkState\n" );
}

void CNetworkListner::OnChannelAvailable( CMacAddr *macAddr, TDeviceId deviceId, uint8_t devInst, TChannelId channelId,
    TMostInstace mostInstance, EPDataType_t dataType, TBlockwidth reservedBandwidth, bool isSourceDevice,
    bool inSocketCreated, bool outSocketCreated, bool socketsConnected, int32_t bufferSize,
    uint32_t mostConnectionLabel, int16_t splittedOffset, int16_t splittedMaxBandwidth, uint16_t packetsPerXact,
    const char *deviceName )
{
    ConsolePrintf( PRIO_LOW, "CNetwork::OnCharacterDeviceAvailable(%s)\n", deviceName );
}

void CNetworkListner::OnChannelUnavailable( CMacAddr *macAddr, TChannelId channelId, uint8_t mostInstance )
{
    ConsolePrintf( PRIO_LOW, "CNetwork::OnChannelUnavailable\n" );
}

void CNetworkListner::OnMostControlMessage( uint8_t devInst, uint32_t sourceAddr, uint32_t targetAddr,
    uint32_t nFBlock, uint32_t nInst, uint32_t nFunc, uint8_t nOpType, uint32_t nPayloadLen, const uint8_t *Payload )
{
    ConsolePrintf( PRIO_LOW, "CNetwork::OnMostControlMessage\n" );
}

void CNetworkListner::OnRingBreakDiagnosisResultV3( uint8_t devInst, uint16_t nodeAddress, uint8_t result,
    uint8_t position, uint8_t status, uint16_t id )
{
    ConsolePrintf( PRIO_LOW, "CNetwork::OnRingBreakDiagnosisResultV2\n" );
}