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

#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include "SafeVector.h"
#include "MacAddr.h"
#include "Types.h"
#include "Console.h"
#include "ConnectionInfo.h"

#define MY_IPC_HEADER           "most mac dev-type instance channel-id type bandwidth direction con-label buffer state offset offset-max p-xact dev-name"
#define MY_IPC_HEADER_LINE_END  MY_IPC_HEADER"\n"

CConnectionInfo::CConnectionInfo() : deviceType( 0 ), deviceInstance( 0 ), channelId( 0 ), mostInstance( 0 ),
    dataType( EP_Unknown ), reservedBandwidth( 0 ), bufferSize( 0 ),
    isTX( false), inSocketCreated( false ), outSocketCreated( false ), socketsConnected( false ),
    mostConnectionLabel( 0xFFFFFFFF ), splittedOffset( 0 ), splittedMaxBandwidth( 0 ), packetsPerXact( 0 )
{
    macAddr = new CMacAddr();
    deviceName[0] = '\0';
}

CConnectionInfo::CConnectionInfo( CMacAddr *macAddr, TChannelId cId, uint8_t mInst,
    bool isTx ) : deviceType( 0 ), deviceInstance( 0 ), channelId( cId ), mostInstance(mInst), 
                  dataType( EP_Unknown ), reservedBandwidth( 0 ), bufferSize( 0 ), isTX( isTx ), 
                  inSocketCreated( false ), outSocketCreated( false ), socketsConnected( false ),
                  mostConnectionLabel( 0xFFFFFFFF ), splittedOffset( -1 ), splittedMaxBandwidth( -1 ), 
                  packetsPerXact( 0 )
{
    this->macAddr = new CMacAddr( macAddr ); //Copy the object
    deviceName[0] = '\0';
}

CConnectionInfo::~CConnectionInfo()
{
    if( NULL != macAddr )
    {
        delete macAddr;
        macAddr = NULL;
    }
}

CConnectionInfoContainer::CConnectionInfoContainer()
{
}

CConnectionInfoContainer::~CConnectionInfoContainer()
{
    DestroyAllInfos();
}

uint32_t CConnectionInfoContainer::GetAllInfoAmount()
{
    return allInfos.Size();
}

CConnectionInfo *CConnectionInfoContainer::GetInfo( uint32_t index )
{
    if( index >= allInfos.Size() )
    {
        ConsolePrintf( PRIO_ERROR,
            RED"CConnectionInfoContainer::GetInfo was called with index out of range"RESETCOLOR"\n" );
        return NULL;
    }
    return allInfos[index];
}

bool CConnectionInfoContainer::Compare( CConnectionInfo *c, CConnectionInfo *t )
{
    if( NULL == c || NULL == t )
    {
        ConsolePrintf( PRIO_ERROR, RED"Compare parameter error"RESETCOLOR"\n" );
        return false;
    }
    return ( ( c->bufferSize == t->bufferSize )
        && ( c->channelId == t->channelId )
        && ( c->dataType == t->dataType )
        && ( c->deviceType == t->deviceType )
        && ( c->mostConnectionLabel == t->mostConnectionLabel )
        && ( c->mostInstance == t->mostInstance )
        && ( c->reservedBandwidth == t->reservedBandwidth )
        && ( 0 == strcmp( c->deviceName, t->deviceName ) )
        && ( c->isTX == t->isTX )
        && ( *c->macAddr == *t->macAddr )
        && ( c->packetsPerXact == t->packetsPerXact )
        );
}

bool CConnectionInfoContainer::Compare( CConnectionInfoContainer *remote )
{
    if( NULL == remote ||
        ( remote->GetAllInfoAmount() != GetAllInfoAmount() ) )
        return false;
    bool equal = true;
    for( uint32_t i = 0; equal && i < allInfos.Size(); i++ )
    {
        equal = false;
        CConnectionInfo *t = allInfos[i];
        if( NULL == t )
            break;
        for( uint32_t j = 0; !equal && j < remote->GetAllInfoAmount(); j++ )
        {
            CConnectionInfo *c = remote->GetInfo( j );
            if( NULL == c )
                break;
            if( Compare( c, t ) )
            {
                equal = true;
                break;
            }
        }
    }
    return equal;
}

bool CConnectionInfoContainer::ExistsEntry( CConnectionInfo *c )
{
    if( NULL == c )
        return false;
    bool exists = false;
    for( uint32_t i = 0; !false && i < allInfos.Size(); i++ )
    {
        CConnectionInfo *t = allInfos[i];
        if( NULL == t )
            break;
        if( Compare( c, t ) )
        {
            exists = true;
            break;
        }
    }
    return exists;
}

CConnectionInfo *CConnectionInfoContainer::GetInfo( CMacAddr *macAddr, TChannelId channelId, uint8_t mostInstance )
{
    CConnectionInfo *instance = NULL;
    if( NULL == macAddr )
    {
        return NULL;
    }
    for( uint32_t i = 0; i < allInfos.Size(); i++ )
    {
        CConnectionInfo *temp = allInfos[i];
        if( NULL != temp && *temp->macAddr == *macAddr
            && temp->channelId == channelId
            && temp->mostInstance == mostInstance )
        {
            instance = temp;
            break;
        }
    }
    return instance;
}

uint8_t CConnectionInfoContainer::GetAmountOfDevices( TMostInstace mostInstance, TDeviceId deviceId )
{
    uint8_t amount = 0;
    CSafeVector<CMacAddr *> allMacs;
    for( uint32_t i = 0; i < allInfos.Size(); i++ )
    {
        CConnectionInfo *temp = allInfos[i];
        if( NULL != temp && temp->deviceType == deviceId
            && temp->mostInstance == mostInstance
            && NULL != temp->macAddr )
        {
            bool update = true;
            for( uint32_t j = 0; j < allMacs.Size(); j++ )
            {
                if( *allMacs[j] == *temp->macAddr )
                {
                    update = false;
                    break;
                }
            }
            if( update )
            {
                allMacs.PushBack( temp->macAddr );
                ++amount;
            }
        }
    }
    return amount;
}

CConnectionInfo *CConnectionInfoContainer::GetInfo( CMacAddr *macAddr, TChannelId channelId, uint8_t mostInstance,
    bool isTx )
{
    CConnectionInfo *instance = GetInfo( macAddr, channelId, mostInstance );
    if( NULL == instance )
    {
        instance = new CConnectionInfo( macAddr, channelId, mostInstance, isTx );
        allInfos.PushBack( instance );
    }
    return instance;
}

void CConnectionInfoContainer::AddInfo( CConnectionInfo *info )
{
    if( NULL == info )
    {
        ConsolePrintf( PRIO_ERROR, RED"AddInfo parameter error"RESETCOLOR"\n" );
        return;
    }
    allInfos.PushBack( info );
}

void CConnectionInfoContainer::DestroyAllInfos()
{
    allInfos.RemoveAll(true);
}

void CConnectionInfoContainer::DestroyInfo( CMacAddr *macAddr, TChannelId channelId, uint8_t mostInstance )
{
    if( NULL == macAddr )
    {
        ConsolePrintf( PRIO_ERROR, RED"DestroyInstance was called with invalid parameters."RESETCOLOR"\n" );
        return;
    }
    for( uint32_t i = 0; i < allInfos.Size(); i++ )
    {
        CConnectionInfo *temp = allInfos[i];
        if( NULL != temp && *temp->macAddr == *macAddr
            && temp->channelId == channelId
            && temp->mostInstance == mostInstance )
        {
            allInfos.Remove(temp);
            delete temp;
            break;
        }
    }
}

void CConnectionInfoContainer::PrintTable( bool hideTx, bool hideRx )
{
    char macString[32];
    char sizeString[10];
    char stateString[32];
    char offsetString[32];
    char lastDeviceName[64];
    const char *typeString;
    int16_t lastMaxOffset = -1;

    lastDeviceName[0] = '\0';
    ConsolePrintfStart( PRIO_HIGH,
        "Index | MOST | MAC-ADDRESS       | DEV  | INST | ID  | Type  | BW  | Dir | Con-Label | Buffer | State  |DEVICE-NAME \n" );
    ConsolePrintfContinue(
        "------|------|-------------------|------|------|-----|-------|-----|-----|-----------|--------|--------|-------------\n" );
    for( uint32_t i = 0; i < allInfos.Size(); i++ )
    {
        CConnectionInfo *temp = allInfos[i];
        if ( NULL == temp )
            continue;

        if( hideTx && temp->isTX )
            continue;

        if( hideRx && !temp->isTX )
            continue;

        switch( temp->dataType )
        {
        case EP_Synchron:
            typeString = "SYNC ";
            break;
        case EP_Asynchron:
            typeString = "ASYNC";
            break;
        case EP_Control:
            typeString = "CTRL ";
            break;
        case EP_Isochron:
            typeString = "ISOC ";
            break;
        case EP_Unused:
            typeString = "EMPTY";
            break;
        case EP_Unknown:
        default:
            typeString = "ERROR";
            break;
        }

        if( NULL == temp->macAddr || NULL == temp->macAddr->ToString() )
            strncpy( macString, "Unknown MAC Addr.", sizeof( macString ) );
        else
            strncpy( macString, temp->macAddr->ToString(), sizeof( macString ) );

        if( 0 > temp->bufferSize )
            strncpy( sizeString, "None", sizeof( sizeString ) );
        else
            snprintf( sizeString, sizeof ( sizeString ), "%04d", temp->bufferSize );


        const char *goodStr = GREEN"X";
        const char *badStr = RED"o";
        snprintf( stateString, sizeof ( stateString ), "%s%s%s"RESETCOLOR, ( temp->inSocketCreated ? goodStr :
            badStr ), ( temp->outSocketCreated ? goodStr : badStr ), ( temp->socketsConnected ? goodStr :
            badStr ) );

        offsetString[0] = '\0';
        if( -1 != temp->splittedOffset && -1 != temp->splittedMaxBandwidth )
        {
            if( 0 == temp->splittedOffset )
            {
                lastMaxOffset = temp->splittedMaxBandwidth;
                if( 0 != strlen( temp->deviceName ) )
                    strncpy( lastDeviceName, temp->deviceName, sizeof( lastDeviceName ) );
            }
            snprintf( offsetString, sizeof( offsetString ), " (offset %d/%d)",
                temp->splittedOffset, ( 0 == temp->splittedOffset ? temp->splittedMaxBandwidth : lastMaxOffset ) );
        }

        ConsolePrintfContinue( "%02d    ", i );
        ConsolePrintfContinue( "| %02d   ", temp->mostInstance );
        ConsolePrintfContinue( "| %s ", macString );
        ConsolePrintfContinue( "| %03X  ", temp->deviceType );
        ConsolePrintfContinue( "| %02d   ", temp->deviceInstance );
        ConsolePrintfContinue( "| %03d ", temp->channelId );
        ConsolePrintfContinue( "| %s ", typeString );
        ConsolePrintfContinue( "| %03d ", temp->reservedBandwidth );
        ConsolePrintfContinue( "| %s  ", ( temp->isTX ? "TX" : "RX" ) );
        ConsolePrintfContinue( "| %03X       ", ( temp->mostConnectionLabel != 0xFFFFFFFF ) ? temp->
            mostConnectionLabel : 0xFFF );
        ConsolePrintfContinue( "| %s   ", sizeString );
        ConsolePrintfContinue( "| %s    ", stateString );
        ConsolePrintfContinue( "| %s", ( '\0' == offsetString[0] ) ? temp->deviceName : lastDeviceName );
        ConsolePrintfContinue( "%s\n", offsetString );
    }
    ConsolePrintfExit(
        "------|------|-------------------|------|------|-----|-------|-----|-----|-----------|--------|--------|-------------\n" );
}

uint32_t CConnectionInfoContainer::SerializeToString( char *pBuffer, uint32_t bufferLen )
{
    if( NULL == pBuffer || 0 == bufferLen )
    {
        ConsolePrintf( PRIO_ERROR, RED"SerializeToString was called with wrong parameters"RESETCOLOR"\n" );
        return 0;
    }
    uint32_t bytesUsed = strlen( pBuffer );
    if (bytesUsed > bufferLen)
    {
        ConsolePrintf(PRIO_ERROR, RED"CConnectionInfoContainer::SerializeToString given buffer"\
            " is too small!"RESETCOLOR);
        return 0;
    }
    const char *typeString = NULL;
    char stringBuffer[200];
    strncpy( pBuffer, MY_IPC_HEADER_LINE_END, bufferLen );
    
    for( uint32_t i = 0; i < allInfos.Size(); i++ )
    {
        CConnectionInfo *sourceInfo = allInfos[i];
        if ( NULL == sourceInfo )
            continue;
        switch( sourceInfo->dataType )
        {
        case EP_Synchron:
            typeString = "SYNC";
            break;
        case EP_Asynchron:
            typeString = "ASYNC";
            break;
        case EP_Control:
            typeString = "CTRL";
            break;
        case EP_Isochron:
            typeString = "ISOC";
            break;
        case EP_Unused:
            typeString = "EMPTY";
            break;
        case EP_Unknown:
        default:
            typeString = "ERROR";
            break;
        }

        snprintf( stringBuffer, sizeof ( stringBuffer ), "%d %s %d %d %d %s %d %s %d %d %c%c%c %d %d %d %s\n",
            sourceInfo->mostInstance, sourceInfo->macAddr->ToString(), sourceInfo->deviceType,
            sourceInfo->deviceInstance, sourceInfo->channelId, typeString,
            sourceInfo->reservedBandwidth, ( sourceInfo->isTX ? "TX" : "RX" ), sourceInfo->mostConnectionLabel,
            sourceInfo->bufferSize, ( sourceInfo->inSocketCreated ? 'X' : '0' ), ( sourceInfo->outSocketCreated ? 'X' :
            '0' ), ( sourceInfo->socketsConnected ? 'X' : '0' ), sourceInfo->splittedOffset,
            sourceInfo->splittedMaxBandwidth, sourceInfo->packetsPerXact, sourceInfo->deviceName );
        strncat( pBuffer, stringBuffer, ( bufferLen - bytesUsed ) );
        bytesUsed = strlen( pBuffer );
    }

    return strlen( pBuffer ) + 1;
}

bool CConnectionInfoContainer::DeserializeFromString( char *pBuffer )
{
    if( NULL == pBuffer || 0 == strlen( pBuffer ) )
    {
        ConsolePrintf( PRIO_ERROR, RED"DeserializeFromString was called with wrong parameters"RESETCOLOR"\n" );
        return false;
    }
    DestroyAllInfos();
    char *helpPtr;
    char *token = strtok_r( pBuffer, "\n", &helpPtr );
    bool first = true;
    while( NULL != token )
    {
        if( first )
        {
            first = false;
            if( 0 != strcmp( MY_IPC_HEADER, token ) )
            {
                ConsolePrintf( PRIO_ERROR,
                    RED"DeserializeFromString: Incompatible header found, aborting\n"\
                    "Expected:'%s'\n"\
                    "Got:     '%s'"\
                    RESETCOLOR"\n", MY_IPC_HEADER, token );
                return false;
            }
        }
        else
        {
            CConnectionInfo *info = new CConnectionInfo();
            uint32_t mostInstance;
            char macString[20];
            uint32_t deviceType;
            uint32_t deviceInstance;
            uint32_t channelId;
            char typeString[10];
            uint32_t reservedBandwidth;
            char deviceDirection[10];
            uint32_t mostConnectionLabel;
            uint32_t bufferSize;
            int32_t offset;
            int32_t maxOffset;
            uint32_t xact;
            char connectString[10];
            sscanf( token, "%d %s %d %d %d %s %d %s %d %d %s %d %d %d %s\n", &mostInstance,
                macString, &deviceType, &deviceInstance, &channelId, typeString, &reservedBandwidth,
                deviceDirection, &mostConnectionLabel, &bufferSize, connectString, &offset, &maxOffset,
                &xact, info->deviceName );

            info->mostInstance = mostInstance;
            info->deviceType = deviceType;
            info->deviceInstance = deviceInstance;
            info->channelId = channelId;
            info->reservedBandwidth = reservedBandwidth;
            info->mostConnectionLabel = mostConnectionLabel;
            info->bufferSize = bufferSize;
            info->splittedOffset = offset;
            info->splittedMaxBandwidth = maxOffset;
            info->packetsPerXact = xact;

            info->isTX = strstr( deviceDirection, "TX" );

            if( NULL != connectString && 3 == strlen( connectString ) )
            {
                info->inSocketCreated = connectString[0] == 'X';
                info->outSocketCreated = connectString[1] == 'X';
                info->socketsConnected = connectString[2] == 'X';
            }

            if( NULL == info->macAddr )
                info->macAddr = new CMacAddr();
            info->macAddr->CopyValuesFromString( macString );

            if( strstr( typeString, "ASYNC" ) )
            {
                info->dataType = EP_Asynchron;
            }
            else if( strstr( typeString, "SYNC" ) )
            {
                info->dataType = EP_Synchron;
            }
            else if( strstr( typeString, "CTRL" ) )
            {
                info->dataType = EP_Control;
            }
            else if( strstr( typeString, "ISOC" ) )
            {
                info->dataType = EP_Isochron;
            }
            else if( strstr( typeString, "EMPTY" ) )
            {
                info->dataType = EP_Unused;
            }
            else
            {
                info->dataType = EP_Unknown;
            }
            allInfos.PushBack( info );
        }
        token = strtok_r( NULL, "\n", &helpPtr );
    }
    return true;
}

uint32_t CConnectionInfoContainer::GetAmountOfCDevs()
{
    uint32_t amount = 0;
    for( uint32_t i = 0; i < allInfos.Size(); i++ )
    {
        CConnectionInfo *temp = allInfos[i];
        if( NULL != temp && NULL != temp->deviceName && 0 != strlen( temp->deviceName ) )
            ++amount;
    }
    return amount;
}

CConnectionInfo *CConnectionInfoContainer::GetConnectionInfoOfCdev( uint32_t cdevInst )
{
    uint32_t amount = 0;
    CConnectionInfo *info = NULL;
    for( uint32_t i = 0; i < allInfos.Size(); i++ )
    {
        CConnectionInfo *temp = allInfos[i];
        if( NULL != temp && NULL != temp->deviceName && 0 != strlen( temp->deviceName ) )
        {
            if( cdevInst == amount )
            {
                info = temp;
                break;
            }
            ++amount;
        }
    }
    return info;
}

CConnectionInfo *CConnectionInfoContainer::GetConnectionInfoOfCdev( uint32_t cdevInst, uint32_t splittedIndex )
{
    uint32_t amountCdev = 0;
    uint32_t amountSplit = 0;
    CConnectionInfo *infoCdev = NULL;
    CConnectionInfo *infoSplitted = NULL;
    for( uint32_t i = 0; i < allInfos.Size(); i++ )
    {
        CConnectionInfo *temp = allInfos[i];
        if( NULL == infoCdev )
        {
            if( NULL != temp && NULL != temp->deviceName && 0 != strlen( temp->deviceName ) )
            {
                if( cdevInst == amountCdev )
                {
                    infoCdev = temp;
                    if( 0 != infoCdev->splittedOffset )
                    {
                        ConsolePrintf( PRIO_ERROR,
                            RED"CConnectionInfoContainer::GetConnectionInfoOfCdev"\
                            " was called for a non splitted CDEV"RESETCOLOR"\n" );
                        break;
                    }
                }
                ++amountCdev;
            }
        }
        else
        {
            if( ( 0 == temp->splittedOffset ) || ( -1 == temp->splittedOffset ) )
                break;
            if( amountSplit == splittedIndex )
            {
                infoSplitted = temp;
                break;
            }
            ++amountSplit;
        }
    }
    return infoSplitted;
}

uint32_t CConnectionInfoContainer::GetAmountOfSinks()
{
    uint32_t amount = 0;
    for( uint32_t i = 0; i < allInfos.Size(); i++ )
    {
        CConnectionInfo *sourceInfo = allInfos[i];
        if( NULL == sourceInfo || NULL == sourceInfo->macAddr || NULL == sourceInfo->macAddr->ToString() )
            continue;
        if( NULL == sourceInfo->deviceName || 0 == strlen( sourceInfo->deviceName ) )
            continue;
        if( !sourceInfo->socketsConnected )
            continue;
        for( uint32_t j = 0; j < allInfos.Size(); j++ )
        {
            CConnectionInfo *sinkInfo = allInfos[j];
            if( i == j )
                continue;
            if( NULL == sinkInfo->macAddr || NULL == sinkInfo->macAddr->ToString() )
                continue;
            if( !sinkInfo->socketsConnected )
                continue;
            if( sourceInfo->mostConnectionLabel != sinkInfo->mostConnectionLabel )
                continue;
            if( sourceInfo->mostInstance != sinkInfo->mostInstance )
                continue;
            ++amount;
        }
    }
    return amount;
}

CConnectionInfo *CConnectionInfoContainer::GetConnectionInfoOfSink( uint32_t sinkInst )
{
    uint32_t amount = 0;
    CConnectionInfo *info = NULL;
    for( uint32_t i = 0; NULL == info && i < allInfos.Size(); i++ )
    {
        CConnectionInfo *sourceInfo = allInfos[i];
        if( NULL == sourceInfo || NULL == sourceInfo->macAddr || NULL == sourceInfo->macAddr->ToString() )
            continue;
        if( NULL == sourceInfo->deviceName || 0 == strlen( sourceInfo->deviceName ) )
            continue;
        if( !sourceInfo->socketsConnected )
            continue;
        for( uint32_t j = 0; NULL == info && j < allInfos.Size(); j++ )
        {
            CConnectionInfo *sinkInfo = allInfos[j];
            if( i == j )
                continue;
            if( NULL == sinkInfo->macAddr || NULL == sinkInfo->macAddr->ToString() )
                continue;
            if( !sinkInfo->socketsConnected )
                continue;
            if( sourceInfo->mostConnectionLabel != sinkInfo->mostConnectionLabel )
                continue;
            if( sourceInfo->mostInstance != sinkInfo->mostInstance )
                continue;
            if( sinkInst == amount )
            {
                info = sinkInfo;
            }
            ++amount;
        }
    }
    return info;
}

CConnectionInfo *CConnectionInfoContainer::GetConnectionInfoByMacAddress( const uint8_t *mac )
{
    if( NULL == mac )
        return NULL;
    char macAddr[20];
    snprintf( ( char * )&macAddr, sizeof( macAddr ), "%02X-%02X-%02X-%02X-%02X-%02X", mac[0], mac[1], mac[2], mac[3],
        mac[4], mac[5] );
    CConnectionInfo *info = NULL;
    for( uint32_t i = 0; i < allInfos.Size(); i++ )
    {
        CConnectionInfo *temp = allInfos[i];
        if( NULL != temp && NULL != temp->macAddr )
        {
            if( 0 == strcmp( temp->macAddr->ToString(), macAddr ) )
            {
                info = temp;
                break;
            }
        }
    }
    return info;
}

CConnectionInfo *CConnectionInfoContainer::GetRemoteConnectionInfoByMacAddress( const uint8_t *mac )
{
    if( NULL == mac )
        return NULL;
    char macAddr[20];
    snprintf( ( char * )&macAddr, sizeof( macAddr ), "%02X-%02X-%02X-%02X-%02X-%02X", mac[0], mac[1], mac[2], mac[3],
        mac[4], mac[5] );
    CConnectionInfo *remoteInfo = NULL;
    CConnectionInfo *info = GetConnectionInfoByMacAddress( mac );
    if( NULL == info )
        return NULL;
    for( uint32_t i = 0; i < allInfos.Size(); i++ )
    {
        CConnectionInfo *temp = allInfos[i];
        if( NULL != temp && NULL != temp->macAddr )
        {
            if( 0 == strcmp( temp->macAddr->ToString(), macAddr ) )
                continue;
            if( ( temp->mostInstance == info->mostInstance ) &&
                ( temp->mostConnectionLabel == info->mostConnectionLabel ) &&
                temp->socketsConnected )
            {
                remoteInfo = temp;
                break;
            }
        }
    }
    return remoteInfo;
}