/**
 ****************************************************************************************
 *
 * @file data.c
 *
 * @brief FTDF data send/receive functions
 *
 * Copyright (c) 2016, Dialog Semiconductor
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL,  SPECIAL,  EXEMPLARY,  OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************************
 */

#include <stdlib.h>

#include <ftdf.h>
#include "internal.h"
#include "regmap.h"

#ifndef FTDF_LITE
void FTDF_processDataRequest(FTDF_DataRequest *dataRequest)
{
#ifndef FTDF_NO_TSCH

    if (FTDF_pib.tschEnabled &&
        FTDF_tschSlotLink->request != dataRequest)
    {
        FTDF_Status status;

        if (dataRequest->dstAddrMode == FTDF_SHORT_ADDRESS &&
            !dataRequest->indirectTX)
        {
            status = FTDF_scheduleTsch((FTDF_MsgBuffer *) dataRequest);

            if (status == FTDF_SUCCESS)
            {
                return;
            }
        }
        else
        {
            status = FTDF_INVALID_PARAMETER;
        }

        FTDF_sendDataConfirm(dataRequest,
                             status,
                             0,
                             0,
                             0,
                             NULL);

        return;
    }

#endif /* FTDF_NO_TSCH */

    int              queue;
    FTDF_AddressMode dstAddrMode = dataRequest->dstAddrMode;
    FTDF_PANId       dstPANId    = dataRequest->dstPANId;
    FTDF_Address     dstAddr     = dataRequest->dstAddr;

    // Search for an existing indirect queue
    for (queue = 0; queue < FTDF_NR_OF_REQ_BUFFERS; queue++)
    {
        if (dstAddrMode == FTDF_SHORT_ADDRESS)
        {
            if (FTDF_txPendingList[ queue ].addrMode == dstAddrMode &&
                FTDF_txPendingList[ queue ].addr.shortAddress == dstAddr.shortAddress)
            {
                break;
            }
        }
        else if (dstAddrMode == FTDF_EXTENDED_ADDRESS)
        {
            if (FTDF_txPendingList[ queue ].addrMode == dstAddrMode &&
                FTDF_txPendingList[ queue ].addr.extAddress == dstAddr.extAddress)
            {
                break;
            }
        }
    }

    if (dataRequest->indirectTX)
    {
        FTDF_Status status = FTDF_SUCCESS;

        if (queue < FTDF_NR_OF_REQ_BUFFERS)
        {
            // Queue request in existing queue
            status = FTDF_queueReqHead((FTDF_MsgBuffer *) dataRequest, &FTDF_txPendingList[ queue ].queue);

            if (status == FTDF_SUCCESS)
            {
                FTDF_addTxPendingTimer((FTDF_MsgBuffer *) dataRequest,
                                       queue,
                                       FTDF_pib.transactionPersistenceTime * FTDF_BASE_SUPERFRAME_DURATION,
                                       FTDF_sendTransactionExpired);
                return;
            }
        }

        if (dstAddrMode != FTDF_EXTENDED_ADDRESS &&
            dstAddrMode != FTDF_SHORT_ADDRESS)
        {
            status = FTDF_INVALID_PARAMETER;
        }

        if (status != FTDF_SUCCESS)
        {
            // Queueing of indirect transfer was successful
            FTDF_sendDataConfirm(dataRequest,
                                 status,
                                 0,
                                 0,
                                 0,
                                 NULL);

            return;
        }

        // Search for an empty indirect queue
        for (queue = 0; queue < FTDF_NR_OF_REQ_BUFFERS; queue++)
        {
            if (FTDF_txPendingList[ queue ].addrMode == FTDF_NO_ADDRESS)
            {
                FTDF_txPendingList[ queue ].addrMode = dstAddrMode;
                FTDF_txPendingList[ queue ].PANId    = dstPANId;
                FTDF_txPendingList[ queue ].addr     = dstAddr;

                status                               = FTDF_queueReqHead((FTDF_MsgBuffer *) dataRequest,
                                                                         &FTDF_txPendingList[ queue ].queue);

                if (status == FTDF_SUCCESS)
                {
                    FTDF_addTxPendingTimer((FTDF_MsgBuffer *) dataRequest,
                                           queue,
                                           FTDF_pib.transactionPersistenceTime * FTDF_BASE_SUPERFRAME_DURATION,
                                           FTDF_sendTransactionExpired);
                    return;
                }
                else
                {
                    break;
                }
            }
        }

        // Did not find an existing or an empty queue
        FTDF_sendDataConfirm(dataRequest, FTDF_TRANSACTION_OVERFLOW,
                             0,
                             0,
                             0,
                             NULL);

        return;
    }

    if (FTDF_reqCurrent == NULL)
    {
        FTDF_reqCurrent = (FTDF_MsgBuffer *) dataRequest;
    }
    else
    {
        if (FTDF_queueReqHead((FTDF_MsgBuffer *) dataRequest, &FTDF_reqQueue) == FTDF_TRANSACTION_OVERFLOW)
        {
            FTDF_sendDataConfirm(dataRequest, FTDF_TRANSACTION_OVERFLOW,
                                 0,
                                 0,
                                 0,
                                 NULL);
        }

        return;
    }

    FTDF_FrameHeader    *frameHeader    = &FTDF_fh;
    FTDF_SecurityHeader *securityHeader = &FTDF_sh;

    frameHeader->frameType = dataRequest->sendMultiPurpose ? FTDF_MULTIPURPOSE_FRAME : FTDF_DATA_FRAME;

    FTDF_Boolean framePending;

    if (queue < FTDF_NR_OF_REQ_BUFFERS)
    {
        framePending = FTDF_TRUE;
    }
    else
    {
        framePending = FTDF_FALSE;
    }

    frameHeader->options =
        (dataRequest->securityLevel > 0 ? FTDF_OPT_SECURITY_ENABLED : 0) |
        (dataRequest->ackTX ? FTDF_OPT_ACK_REQUESTED : 0) |
        (framePending ? FTDF_OPT_FRAME_PENDING : 0) |
        ((dataRequest->frameControlOptions & FTDF_PAN_ID_PRESENT) ? FTDF_OPT_PAN_ID_PRESENT : 0) |
        ((dataRequest->frameControlOptions & FTDF_IES_INCLUDED) ? FTDF_OPT_IES_PRESENT : 0) |
        ((dataRequest->frameControlOptions & FTDF_SEQ_NR_SUPPRESSED) ? FTDF_OPT_SEQ_NR_SUPPRESSED : 0);

    if (FTDF_pib.leEnabled || FTDF_pib.tschEnabled)
    {
        frameHeader->options |= FTDF_OPT_ENHANCED;
    }

    frameHeader->srcAddrMode         = dataRequest->srcAddrMode;
    frameHeader->srcPANId            = FTDF_pib.PANId;
    frameHeader->dstAddrMode         = dataRequest->dstAddrMode;
    frameHeader->dstPANId            = dataRequest->dstPANId;
    frameHeader->dstAddr             = dataRequest->dstAddr;

    securityHeader->securityLevel    = dataRequest->securityLevel;
    securityHeader->keyIdMode        = dataRequest->keyIdMode;
    securityHeader->keyIndex         = dataRequest->keyIndex;
    securityHeader->keySource        = dataRequest->keySource;
    securityHeader->frameCounter     = FTDF_pib.frameCounter;
    securityHeader->frameCounterMode = FTDF_pib.frameCounterMode;

#ifndef FTDF_NO_TSCH

    if (FTDF_pib.tschEnabled)
    {
        frameHeader->SN = FTDF_processTschSN((FTDF_MsgBuffer *)dataRequest,
                                             FTDF_pib.DSN,
                                             &dataRequest->requestSN);
    }
    else
#endif /* FTDF_NO_TSCH */
    {
        frameHeader->SN = FTDF_pib.DSN;
    }

    FTDF_Octet *txPtr = (FTDF_Octet *) FTDF_GET_REG_ADDR(RETENTION_RAM_TX_FIFO) +
                        (FTDF_BUFFER_LENGTH * FTDF_TX_DATA_BUFFER);

    // Skip PHY header (= MAC length)
    txPtr++;

    FTDF_DataLength msduLength = dataRequest->msduLength;

    txPtr           = FTDF_addFrameHeader(txPtr,
                                          frameHeader,
                                          msduLength);

    txPtr = FTDF_addSecurityHeader(txPtr,
                                   securityHeader);

#if !defined(FTDF_NO_CSL) || !defined(FTDF_NO_TSCH)

    if (dataRequest->frameControlOptions & FTDF_IES_INCLUDED)
    {
        txPtr = FTDF_addIes(txPtr,
                            dataRequest->headerIEList,
                            dataRequest->payloadIEList,
                            dataRequest->msduLength);
    }

#endif /* !FTDF_NO_CSL || !FTDF_NO_TSCH */

    FTDF_Status status = FTDF_sendFrame(FTDF_pib.currentChannel,
                                        frameHeader,
                                        securityHeader,
                                        txPtr,
                                        dataRequest->msduLength,
                                        dataRequest->msdu);

    if (status != FTDF_SUCCESS)
    {
        FTDF_sendDataConfirm(dataRequest, status,
                             0,
                             0,
                             0,
                             NULL);

        FTDF_reqCurrent = NULL;

        return;
    }

    FTDF_nrOfRetries = 0;

    if (frameHeader->SN == FTDF_pib.DSN)
    {
        FTDF_pib.DSN++;
    }
}

void FTDF_sendDataConfirm(FTDF_DataRequest  *dataRequest,
                          FTDF_Status        status,
                          FTDF_Time          timestamp,
                          FTDF_SN            DSN,
                          FTDF_NumOfBackoffs numOfBackoffs,
                          FTDF_IEList       *ackPayload)
{
    FTDF_REL_DATA_BUFFER(dataRequest->msdu);

    FTDF_DataConfirm *dataConfirm = (FTDF_DataConfirm *) FTDF_GET_MSG_BUFFER(sizeof(FTDF_DataConfirm));

    dataConfirm->msgId         = FTDF_DATA_CONFIRM;
    dataConfirm->msduHandle    = dataRequest->msduHandle;
    dataConfirm->status        = status;
    dataConfirm->timestamp     = timestamp;
    dataConfirm->numOfBackoffs = numOfBackoffs;
    dataConfirm->DSN           = DSN;
    dataConfirm->ackPayload    = ackPayload;

    if (FTDF_reqCurrent == (FTDF_MsgBuffer *)dataRequest)
    {
        FTDF_reqCurrent = NULL;
    }

    FTDF_REL_MSG_BUFFER((FTDF_MsgBuffer *) dataRequest);
    FTDF_RCV_MSG((FTDF_MsgBuffer *) dataConfirm);

    FTDF_processNextRequest();
}

void FTDF_sendDataIndication(FTDF_FrameHeader    *frameHeader,
                             FTDF_SecurityHeader *securityHeader,
                             FTDF_IEList         *payloadIEList,
                             FTDF_DataLength      msduLength,
                             FTDF_Octet          *msdu,
                             FTDF_LinkQuality     mpduLinkQuality,
                             FTDF_Time            timestamp)
{
    FTDF_DataIndication *dataIndication = (FTDF_DataIndication *) FTDF_GET_MSG_BUFFER(sizeof(FTDF_DataIndication));
    FTDF_Octet          *msduBuf        = FTDF_GET_DATA_BUFFER(msduLength);
    FTDF_Octet          *bufPtr         = msduBuf;

    int                  n;

    for (n = 0; n < msduLength; n++)
    {
        *msduBuf++ = *msdu++;
    }

    dataIndication->msgId           = FTDF_DATA_INDICATION;
    dataIndication->srcAddrMode     = frameHeader->srcAddrMode;
    dataIndication->srcPANId        = frameHeader->srcPANId;
    dataIndication->srcAddr         = frameHeader->srcAddr;
    dataIndication->dstAddrMode     = frameHeader->dstAddrMode;
    dataIndication->dstPANId        = frameHeader->dstPANId;
    dataIndication->dstAddr         = frameHeader->dstAddr;
    dataIndication->msduLength      = msduLength;
    dataIndication->msdu            = bufPtr;
    dataIndication->mpduLinkQuality = mpduLinkQuality;
    dataIndication->DSN             = frameHeader->SN;
    dataIndication->timestamp       = timestamp;
    dataIndication->securityLevel   = securityHeader->securityLevel;
    dataIndication->keyIdMode       = securityHeader->keyIdMode;
    dataIndication->keyIndex        = securityHeader->keyIndex;
    dataIndication->payloadIEList   = payloadIEList;

    if (dataIndication->keyIdMode == 0x2)
    {
        for (n = 0; n < 4; n++)
        {
            dataIndication->keySource[ n ] = securityHeader->keySource[ n ];
        }
    }
    else if (dataIndication->keyIdMode == 0x3)
    {
        for (n = 0; n < 8; n++)
        {
            dataIndication->keySource[ n ] = securityHeader->keySource[ n ];
        }
    }

    FTDF_RCV_MSG((FTDF_MsgBuffer *) dataIndication);
}

void FTDF_processPollRequest(FTDF_PollRequest *pollRequest)
{
    if (FTDF_reqCurrent == NULL)
    {
        FTDF_reqCurrent = (FTDF_MsgBuffer *) pollRequest;
    }
    else
    {
        if (FTDF_queueReqHead((FTDF_MsgBuffer *) pollRequest, &FTDF_reqQueue) == FTDF_TRANSACTION_OVERFLOW)
        {
            FTDF_sendPollConfirm(pollRequest, FTDF_TRANSACTION_OVERFLOW);
        }

        return;
    }

    FTDF_FrameHeader    *frameHeader    = &FTDF_fh;
    FTDF_SecurityHeader *securityHeader = &FTDF_sh;

    frameHeader->frameType = FTDF_MAC_COMMAND_FRAME;
    frameHeader->options   =
        (pollRequest->securityLevel > 0 ? FTDF_OPT_SECURITY_ENABLED : 0) | FTDF_OPT_ACK_REQUESTED;

    if (FTDF_pib.shortAddress < 0xfffe)
    {
        frameHeader->srcAddrMode          = FTDF_SHORT_ADDRESS;
        frameHeader->srcAddr.shortAddress = FTDF_pib.shortAddress;
    }
    else
    {
        frameHeader->srcAddrMode        = FTDF_EXTENDED_ADDRESS;
        frameHeader->srcAddr.extAddress = FTDF_pib.extAddress;
    }

    frameHeader->srcPANId            = FTDF_pib.PANId;
    frameHeader->dstAddrMode         = pollRequest->coordAddrMode;
    frameHeader->dstPANId            = pollRequest->coordPANId;
    frameHeader->dstAddr             = pollRequest->coordAddr;

    securityHeader->securityLevel    = pollRequest->securityLevel;
    securityHeader->keyIdMode        = pollRequest->keyIdMode;
    securityHeader->keyIndex         = pollRequest->keyIndex;
    securityHeader->keySource        = pollRequest->keySource;
    securityHeader->frameCounter     = FTDF_pib.frameCounter;
    securityHeader->frameCounterMode = FTDF_pib.frameCounterMode;

    FTDF_Octet *txPtr = (FTDF_Octet *) FTDF_GET_REG_ADDR(RETENTION_RAM_TX_FIFO) +
                        (FTDF_BUFFER_LENGTH * FTDF_TX_DATA_BUFFER);

    // Skip PHY header (= MAC length)
    txPtr++;

    frameHeader->SN = FTDF_pib.DSN;

    txPtr           = FTDF_addFrameHeader(txPtr,
                                          frameHeader,
                                          1);

    txPtr = FTDF_addSecurityHeader(txPtr,
                                   securityHeader);

    *txPtr++ = FTDF_COMMAND_DATA_REQUEST;

    FTDF_Status status = FTDF_sendFrame(FTDF_pib.currentChannel,
                                        frameHeader,
                                        securityHeader,
                                        txPtr,
                                        0,
                                        NULL);

    if (status != FTDF_SUCCESS)
    {
        FTDF_sendPollConfirm(pollRequest, status);

        return;
    }

    FTDF_nrOfRetries = 0;
    FTDF_pib.DSN++;
}

void FTDF_sendPollConfirm(FTDF_PollRequest *pollRequest, FTDF_Status status)
{
    FTDF_PollConfirm *pollConfirm = (FTDF_PollConfirm *) FTDF_GET_MSG_BUFFER(sizeof(FTDF_PollConfirm));

    pollConfirm->msgId  = FTDF_POLL_CONFIRM;
    pollConfirm->status = status;

    if (FTDF_reqCurrent == (FTDF_MsgBuffer *)pollRequest)
    {
        FTDF_reqCurrent = NULL;
    }

    FTDF_REL_MSG_BUFFER((FTDF_MsgBuffer *) pollRequest);
    FTDF_RCV_MSG((FTDF_MsgBuffer *) pollConfirm);

    FTDF_processNextRequest();
}

void FTDF_processPurgeRequest(FTDF_PurgeRequest *purgeRequest)
{
    FTDF_Handle msduHandle = purgeRequest->msduHandle;
    FTDF_Status status     = FTDF_INVALID_HANDLE;

    int         n;

    for (n = 0; n < FTDF_NR_OF_REQ_BUFFERS; n++)
    {
        FTDF_MsgBuffer *request = FTDF_dequeueByHandle(msduHandle, &FTDF_txPendingList[ n ].queue);

        if (request)
        {
            FTDF_DataRequest *dataRequest = (FTDF_DataRequest *) request;

            if (dataRequest->indirectTX == FTDF_TRUE)
            {
                FTDF_removeTxPendingTimer(request);

                if (FTDF_isQueueEmpty(&FTDF_txPendingList[ n ].queue))
                {
                    FTDF_txPendingList[ n ].addrMode = FTDF_NO_ADDRESS;
                }
            }

            FTDF_REL_DATA_BUFFER(dataRequest->msdu);
            FTDF_REL_MSG_BUFFER((FTDF_MsgBuffer *) dataRequest);

            status = FTDF_SUCCESS;

            break;
        }
    }

    FTDF_PurgeConfirm *purgeConfirm = (FTDF_PurgeConfirm *) FTDF_GET_MSG_BUFFER(sizeof(FTDF_PurgeConfirm));

    purgeConfirm->msgId      = FTDF_PURGE_CONFIRM;
    purgeConfirm->msduHandle = msduHandle;
    purgeConfirm->status     = status;

    FTDF_REL_MSG_BUFFER((FTDF_MsgBuffer *) purgeRequest);
    FTDF_RCV_MSG((FTDF_MsgBuffer *) purgeConfirm);
}
#endif /* !FTDF_LITE */

#ifdef FTDF_PHY_API
FTDF_Status FTDF_sendFrameSimple(FTDF_DataLength    frameLength,
                                 FTDF_Octet        *frame,
                                 FTDF_ChannelNumber channel,
                                 FTDF_PTI           pti,
                                 FTDF_Boolean       csmaSuppress)
{

    FTDF_Octet *fp = frame;

    if (frameLength > 127 ||
        FTDF_transparentMode != FTDF_TRUE)
    {
        return FTDF_INVALID_PARAMETER;
    }

    FTDF_Octet *txPtr = (FTDF_Octet *) FTDF_GET_REG_ADDR(RETENTION_RAM_TX_FIFO) +
                        (FTDF_BUFFER_LENGTH * FTDF_TX_DATA_BUFFER);

    /* This data copy could/should be optimized using DMA */
    *txPtr++ = frameLength;

    int         n;

    for (n = 0; n < frameLength; n++)
    {
        *txPtr++ = *fp++;
    }

    FTDF_criticalVar();
    FTDF_enterCritical();
    FTDF_nrOfRetries = 0;
    FTDF_exitCritical();
    FTDF_sendTransparentFrame(frameLength,
                              frame,
                              channel,
                              pti,
                              csmaSuppress);

    return FTDF_SUCCESS;
}



#else /* !FTDF_PHY_API */
void FTDF_processTransparentRequest(FTDF_TransparentRequest *transparentRequest)
{
    FTDF_DataLength frameLength = transparentRequest->frameLength;

    if (frameLength > 127 ||
        FTDF_transparentMode != FTDF_TRUE)
    {
        FTDF_SEND_FRAME_TRANSPARENT_CONFIRM(transparentRequest->handle,
                                            FTDF_INVALID_PARAMETER);

        FTDF_REL_MSG_BUFFER((FTDF_MsgBuffer *) transparentRequest);

        return;
    }

    if (FTDF_reqCurrent == NULL)
    {
        FTDF_reqCurrent = (FTDF_MsgBuffer *) transparentRequest;
    }
    else
    {
#ifndef FTDF_LITE

        if (FTDF_queueReqHead((FTDF_MsgBuffer *) transparentRequest, &FTDF_reqQueue) == FTDF_TRANSACTION_OVERFLOW)
        {
#endif /* !FTDF_LITE */
            FTDF_SEND_FRAME_TRANSPARENT_CONFIRM(transparentRequest->handle,
                                                FTDF_TRANSPARENT_OVERFLOW);

            FTDF_REL_MSG_BUFFER((FTDF_MsgBuffer *) transparentRequest);
#ifndef FTDF_LITE
        }

#endif /* !FTDF_LITE */
        return;
    }

    FTDF_Octet *txPtr = (FTDF_Octet *) FTDF_GET_REG_ADDR(RETENTION_RAM_TX_FIFO) +
                        (FTDF_BUFFER_LENGTH * FTDF_TX_DATA_BUFFER);

    *txPtr++ = frameLength;

    FTDF_Octet *frame = transparentRequest->frame;

    int         n;

    for (n = 0; n < frameLength; n++)
    {
        *txPtr++ = *frame++;
    }

    FTDF_sendTransparentFrame(frameLength,
                              transparentRequest->frame,
                              transparentRequest->channel,
                              transparentRequest->pti,
                              transparentRequest->cmsaSuppress);
    FTDF_nrOfRetries = 0;
}


void FTDF_sendFrameTransparent(FTDF_DataLength    frameLength,
                               FTDF_Octet        *frame,
                               FTDF_ChannelNumber channel,
                               FTDF_PTI           pti,
                               FTDF_Boolean       cmsaSuppress,
                               void              *handle)
{
    FTDF_TransparentRequest *transparentRequest =
        (FTDF_TransparentRequest *) FTDF_GET_MSG_BUFFER(sizeof(FTDF_TransparentRequest));

    transparentRequest->msgId        = FTDF_TRANSPARENT_REQUEST;
    transparentRequest->frameLength  = frameLength;
    transparentRequest->frame        = frame;
    transparentRequest->channel      = channel;
    transparentRequest->pti          = pti;
    transparentRequest->cmsaSuppress = cmsaSuppress;
    transparentRequest->handle       = handle;

    FTDF_processTransparentRequest(transparentRequest);
}

#endif /* FTDF_PHY_API */
