#include "data_link_layer.h"

#include "bits.h"
#include "platform.h"
#include "device_object.h"
#include "cemi_server.h"
#include "cemi_frame.h"


void DataLinkLayerCallbacks::activity(uint8_t info)
{
    if (_activityCallback)
        _activityCallback(info);
}

void DataLinkLayerCallbacks::setActivityCallback(ActivityCallback activityCallback)
{
    _activityCallback = activityCallback;
}

DataLinkLayer::DataLinkLayer(DeviceObject& devObj, NetworkLayerEntity& netLayerEntity, Platform& platform) :
    _deviceObject(devObj), _networkLayerEntity(netLayerEntity), _platform(platform)
{
#ifdef KNX_ACTIVITYCALLBACK
    _netIndex = netLayerEntity.getEntityIndex();
#endif
}

#ifdef USE_CEMI_SERVER

void DataLinkLayer::cemiServer(CemiServer& cemiServer)
{
    _cemiServer = &cemiServer;
}

#ifdef KNX_TUNNELING
void DataLinkLayer::dataRequestToTunnel(CemiFrame& frame)
{
    println("default dataRequestToTunnel");
}

void DataLinkLayer::dataConfirmationToTunnel(CemiFrame& frame)
{
    println("default dataConfirmationToTunnel");
}

void DataLinkLayer::dataIndicationToTunnel(CemiFrame& frame)
{
    println("default dataIndicationToTunnel");
}

bool DataLinkLayer::isTunnelAddress(uint16_t addr)
{
    println("default IsTunnelAddress");
    return false;
}
#endif

void DataLinkLayer::dataRequestFromTunnel(CemiFrame& frame)
{
    _cemiServer->dataConfirmationToTunnel(frame);

    frame.messageCode(L_data_ind);

    // Send to local stack ( => cemiServer for potential other tunnel and network layer for routing)
    frameReceived(frame);

#ifdef KNX_TUNNELING
    // TunnelOpti
    // Optimize performance when receiving unicast data over tunnel wich is not meant to be used on the physical TP line
    // dont send to knx when
    // frame is individual adressed AND
    // destionation == PA of Tunnel-Server  OR
    // destination == PA of a Tunnel OR (TODO)
    // destination is not the TP/secondary line/segment but IP/primary (TODO)

    if (frame.addressType() == AddressType::IndividualAddress)
    {
        if (frame.destinationAddress() == _deviceObject.individualAddress())
            return;

        if (isRoutedPA(frame.destinationAddress()))
            return;

        if (isTunnelingPA(frame.destinationAddress()))
            return;
    }

#endif

    // Send to KNX medium
    sendFrame(frame);
}
#endif

void DataLinkLayer::dataRequest(AckType ack, AddressType addrType, uint16_t destinationAddr, uint16_t sourceAddr, FrameFormat format, Priority priority, NPDU& npdu)
{
    // Normal data requests and broadcasts will always be transmitted as (domain) broadcast with domain address for open media (e.g. RF medium)
    // The domain address "simulates" a closed medium (such as TP) on an open medium (such as RF or PL)
    // See 3.2.5 p.22
    sendTelegram(npdu, ack, destinationAddr, addrType, sourceAddr, format, priority, Broadcast);
}

void DataLinkLayer::systemBroadcastRequest(AckType ack, FrameFormat format, Priority priority, NPDU& npdu, uint16_t sourceAddr)
{
    // System Broadcast requests will always be transmitted as broadcast with KNX serial number for open media (e.g. RF medium)
    // See 3.2.5 p.22
    sendTelegram(npdu, ack, 0, GroupAddress, sourceAddr, format, priority, SysBroadcast);
}

void DataLinkLayer::dataConReceived(CemiFrame& frame, bool success)
{
    MessageCode backupMsgCode = frame.messageCode();
    frame.messageCode(L_data_con);
    frame.confirm(success ? ConfirmNoError : ConfirmError);
    AckType ack = frame.ack();
    AddressType addrType = frame.addressType();
    uint16_t destination = frame.destinationAddress();
    uint16_t source = frame.sourceAddress();
    FrameFormat type = frame.frameType();
    Priority priority = frame.priority();
    NPDU& npdu = frame.npdu();
    SystemBroadcast systemBroadcast = frame.systemBroadcast();

#ifdef USE_CEMI_SERVER

    // if the confirmation was caused by a tunnel request then
    // do not send it to the local stack
    if (frame.sourceAddress() == _cemiServer->clientAddress())
    {
        // Stop processing here and do NOT send it the local network layer
        return;
    }

#endif

    if (addrType == GroupAddress && destination == 0)
        if (systemBroadcast == SysBroadcast)
            _networkLayerEntity.systemBroadcastConfirm(ack, type, priority, source, npdu, success);
        else
            _networkLayerEntity.broadcastConfirm(ack, type, priority, source, npdu, success);
    else
        _networkLayerEntity.dataConfirm(ack, addrType, destination, type, priority, source, npdu, success);

    frame.messageCode(backupMsgCode);
}

void DataLinkLayer::frameReceived(CemiFrame& frame)
{
    AckType ack = frame.ack();
    AddressType addrType = frame.addressType();
    uint16_t destination = frame.destinationAddress();
    uint16_t source = frame.sourceAddress();
    FrameFormat type = frame.frameType();
    Priority priority = frame.priority();
    NPDU& npdu = frame.npdu();
    uint16_t ownAddr = _deviceObject.individualAddress();
    SystemBroadcast systemBroadcast = frame.systemBroadcast();

#ifdef USE_CEMI_SERVER
    // Do not send our own message back to the tunnel
#ifdef KNX_TUNNELING

    //we dont need to check it here
    // send inbound frames to the tunnel if we are the secondary (TP) interface
    if ( _networkLayerEntity.getEntityIndex() == 1)
        _cemiServer->dataIndicationToTunnel(frame);

#else

    if (frame.sourceAddress() != _cemiServer->clientAddress())
    {
        _cemiServer->dataIndicationToTunnel(frame);
    }

#endif
#endif

    // print("Frame received destination: ");
    // print(destination, 16);
    // println();
    // print("frameReceived: frame valid? :");
    // println(npdu.frame().valid() ? "true" : "false");
    if (source == ownAddr)
        _deviceObject.individualAddressDuplication(true);

    if (addrType == GroupAddress && destination == 0)
    {
        if (systemBroadcast == SysBroadcast)
            _networkLayerEntity.systemBroadcastIndication(ack, type, npdu, priority, source);
        else
            _networkLayerEntity.broadcastIndication(ack, type, npdu, priority, source);
    }
    else
    {
        _networkLayerEntity.dataIndication(ack, addrType, destination, type, npdu, priority, source);
    }
}

bool DataLinkLayer::sendTelegram(NPDU& npdu, AckType ack, uint16_t destinationAddr, AddressType addrType, uint16_t sourceAddr, FrameFormat format, Priority priority, SystemBroadcast systemBroadcast, bool doNotRepeat)
{
    CemiFrame& frame = npdu.frame();
    // print("Send telegram frame valid ?: ");
    // println(frame.valid()?"true":"false");
    frame.messageCode(L_data_ind);
    frame.destinationAddress(destinationAddr);
    frame.sourceAddress(sourceAddr);
    frame.addressType(addrType);
    frame.priority(priority);
    frame.repetition(doNotRepeat ? NoRepitiion : RepetitionAllowed);
    frame.systemBroadcast(systemBroadcast);

    if (npdu.octetCount() <= 15)
        frame.frameType(StandardFrame);
    else
        frame.frameType(format);


    if (!frame.valid())
    {
        println("invalid frame");
        return false;
    }

    //    if (frame.npdu().octetCount() > 0)
    //    {
    //        _print("<- DLL ");
    //        frame.apdu().printPDU();
    //    }

    bool sendTheFrame = true;
    bool success = true;

#ifdef KNX_TUNNELING
    // TunnelOpti
    // Optimize performance when sending unicast data over tunnel wich is not meant to be used on the physical TP line
    // dont send to knx when
    // a) we are the secondary interface (e.g. TP) AND
    // b) destination == PA of a Tunnel (TODO)

    if (_networkLayerEntity.getEntityIndex() == 1 && addrType == AddressType::IndividualAddress)   // don't send to tp if we are the secondary (TP) interface AND the destination is a tunnel-PA
    {
        if (isTunnelingPA(destinationAddr))
            sendTheFrame = false;
    }

#endif

    // The data link layer might be an open media link layer
    // and will setup rfSerialOrDoA, rfInfo and rfLfn that we also
    // have to send through the cEMI server tunnel
    // Thus, reuse the modified cEMI frame as "frame" is only passed by reference here!
    if (sendTheFrame)
        success = sendFrame(frame);

#ifdef USE_CEMI_SERVER
    CemiFrame tmpFrame(frame.data(), frame.totalLenght());
    // We can just copy the pointer for rfSerialOrDoA as sendFrame() sets
    // a pointer to const uint8_t data in either device object (serial) or
    // RF medium object (domain address)
#ifdef USE_RF
    tmpFrame.rfSerialOrDoA(frame.rfSerialOrDoA());
    tmpFrame.rfInfo(frame.rfInfo());
    tmpFrame.rfLfn(frame.rfLfn());
#endif
    tmpFrame.confirm(ConfirmNoError);

    if (_networkLayerEntity.getEntityIndex() == 1)   // only send to tunnel if we are the secondary (TP) interface
        _cemiServer->dataIndicationToTunnel(tmpFrame);

#endif

    return success;
}

uint8_t* DataLinkLayer::frameData(CemiFrame& frame)
{
    return frame._data;
}

#ifdef KNX_TUNNELING
bool DataLinkLayer::isTunnelingPA(uint16_t pa)
{
    uint8_t numAddresses = 0;
    uint16_t* addresses = _ipParameters->additionalIndivualAddresses(numAddresses);

    for (uint8_t i = 0; i < numAddresses; i++)
    {
        if (pa == addresses[i])
            return true;
    }

    return false;
}

bool DataLinkLayer::isRoutedPA(uint16_t pa)
{
    uint16_t ownpa = _deviceObject.individualAddress();
    uint16_t own_sm;

    if ((ownpa & 0x0F00) == 0x0)
        own_sm = 0xF000;
    else
        own_sm = 0xFF00;

    return (pa & own_sm) != ownpa;
}
#endif

