/*
 * Copyright (c) 2014-2019, Texas Instruments Incorporated
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * *  Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * *  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.
 *
 * *  Neither the name of Texas Instruments Incorporated 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 OWNER 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.
 */
/**
 *  \file network_api.c
 *
 *  \brief File containing the Network functions.
 *
 */

 /* ========================================================================== */
/*                             Include Files                                  */
/* ========================================================================== */

#include <stdint.h>

/* BIOS Header files */
#include <ti/sysbios/BIOS.h>

/* OSAL Header files */
#include <ti/osal/osal.h>

/* CSL Header files */
#include <ti/csl/csl_types.h>

/* NDK Dependencies */
#include <ti/ndk/inc/socket.h>
#include <ti/ndk/inc/netmain.h>
#include <ti/ndk/inc/stkmain.h>

/* Module Header files */
#include <network_api.h>
#include <utils_prf.h>

/* ========================================================================== */
/*                           Macros & Typedefs                                */
/* ========================================================================== */

#define NETWORK_INVALID_SOCKET              (0xFFFFFFFFU)
#define NETWORK_TX_SERVER_CLOSED            (0U)
#define NETWORK_TX_SERVER_LISTEN            (1U)
#define NETWORK_TX_SERVER_CONNECTED         (2U)
#define NETWORK_TX_SERVER_POLL_TIMEOUT      (10U)

/* ========================================================================== */
/*                         Structure Declarations                             */
/* ========================================================================== */


/* ========================================================================== */
/*                          Function Declarations                             */
/* ========================================================================== */

static int32_t Network_open(Network_SockObj *pObj, uint32_t port);
static int32_t Network_close(Network_SockObj *pObj, Bool closeServerSock);
static int32_t Network_waitConnect(Network_SockObj *pObj, uint32_t timeout);
static int32_t Network_read(Network_SockObj *pObj, uint8_t *dataBuf, uint32_t *dataSize);
static int32_t Network_write(Network_SockObj *pObj, uint8_t *dataBuf, uint32_t dataSize);
static int32_t Network_sessionOpen(void *handle);
static int32_t Network_sessionClose(void *handle);

static int32_t NetworkWaitConnect(Network_SockObj *pObj);
int32_t NetworkWritePayload(Network_SockObj *pObj, uint8_t  *bufAddr, uint32_t bufSize);

/* ========================================================================== */
/*                            Global Variables                                */
/* ========================================================================== */


/* ========================================================================== */
/*                          Function Definitions                              */
/* ========================================================================== */

int32_t NetworkCreate(Network_SockObj *pObj)
{
    int32_t status;

    Network_sessionOpen(NULL);

    status = Network_open(
                    pObj,
                    pObj->port
                    );
    if (status!=0)
        Utils_consolePrintf("Network_open failed !!!\n");


    pObj->state = NETWORK_TX_SERVER_LISTEN;

    Utils_consolePrintf("Gateway Tx: Server listening (port=%d) !!!\n", pObj->port);

    return status;
}

int32_t NetworkDelete(Network_SockObj *pObj)
{
    Network_close(pObj, (Bool)TRUE);

    Network_sessionClose(NULL);

    pObj->state = NETWORK_TX_SERVER_CLOSED;

    Utils_consolePrintf("Gateway Tx: Server Closed (port=%d) !!!\n",
        pObj->port);

    return 0U;
}

int32_t NetworkWritePayload(Network_SockObj *pObj, uint8_t  *bufAddr, uint32_t bufSize)
{
    int32_t status = 0;

    status = NetworkWaitConnect(pObj);

    if(pObj->state == NETWORK_TX_SERVER_CONNECTED)
    {
        /* write header to client, if write failed, then client
         * disconnected, so go back to listening
         */
        status = Network_write(pObj,
                    bufAddr,
                    bufSize
                    );

        if(status!=0)
        {
            Utils_consolePrintf("Gateway Tx: Disconnected from client "
                       "(port=%d)!!!\n",
                        pObj->port
                       );

            Network_close(pObj, FALSE);

            pObj->state = NETWORK_TX_SERVER_LISTEN;
        }
    }

    return status;
}

int32_t NetworkReadPayload(Network_SockObj *pObj, uint8_t  *bufAddr, uint32_t bufSize)
{
    int32_t status = 0U;

    status = NetworkWaitConnect(pObj);

    if(pObj->state == NETWORK_TX_SERVER_CONNECTED)
    {
        /* write header to client, if write failed, then client
         * disconnected, so go back to listening
         */
        status = Network_read(pObj,
                    bufAddr,
                    &bufSize
                    );

        if(status!=0)
        {
            Utils_consolePrintf("Gateway Tx: Disconnected from client "
                       "(port=%d)!!!\n",
                        pObj->port
                       );

            Network_close(pObj, FALSE);

            pObj->state = NETWORK_TX_SERVER_LISTEN;
        }
    }

#if (defined(DEBUG))
    Utils_consolePrintf("NETWORK_RX: NETWORK_RX: BUF%d: %d bytes recevied (port=%d)!!!\n",
                i,
                bufSize[i],
                pObj->createArgs.networkServerPort
                );
#endif

    return status;
}

/* ========================================================================== */
/*                          Static Function Definitions                       */
/* ========================================================================== */

static int32_t NetworkWaitConnect(Network_SockObj *pObj)
{
    int32_t status = 0;
    int32_t isConnected;

    if(pObj->state == NETWORK_TX_SERVER_LISTEN)
    {
        isConnected = Network_waitConnect(
                    pObj,
                    NETWORK_TX_SERVER_POLL_TIMEOUT);

        if((int32_t)TRUE == isConnected)
        {
            /* connected to client */
            pObj->state = NETWORK_TX_SERVER_CONNECTED;

            Utils_consolePrintf("Gateway Tx: Connected to client (port=%d) !!!\n",
                pObj->port);
        }
        else
        {
            status = -1;
        }
    }

    return status;
}

static int32_t Network_read(Network_SockObj *pObj, uint8_t *dataBuf, uint32_t *dataSize)
{
    int32_t status = NETWORK_SUCCESS;

    uint32_t tmpDataSize;
    int32_t actDataSize = 0;

    tmpDataSize = *dataSize;

    while(tmpDataSize > 0U)
    {

        actDataSize = NDK_recv(pObj->connectedSockFd, (void *)dataBuf, tmpDataSize, 0);
        if(actDataSize <= 0)
        {
            *dataSize = 0U;
            status = NETWORK_ERROR;
            break;
        }
        else
        {
            dataBuf += actDataSize;
            tmpDataSize -= (uint32_t)actDataSize;
        }
    }

    return status;
}


static int32_t Network_write(Network_SockObj *pObj, uint8_t *dataBuf, uint32_t dataSize)
{
    int32_t status = NETWORK_SUCCESS;


    int32_t actDataSize=0;

    while(dataSize > 0 ) {
        actDataSize = (int32_t)NDK_send(pObj->connectedSockFd, dataBuf, (size_t)dataSize, 0);

        if(actDataSize <= 0)
        {
            break;
        }
        else
        {
            dataBuf += actDataSize;
            dataSize -= (uint32_t)actDataSize;
        }
    }

    if( dataSize > 0 ) {
        status = NETWORK_ERROR;
    }


    return status;
}

static int32_t Network_open(Network_SockObj *pObj, uint32_t port)
{
    int32_t status = NETWORK_SUCCESS;

    struct sockaddr_in   sin1;
    int32_t option = 1;

    pObj->connectedSockFd = (SOCKET)NETWORK_INVALID_SOCKET;
    pObj->port = port;
    pObj->sockFd = (SOCKET)NDK_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if( pObj->sockFd == (SOCKET)NETWORK_INVALID_SOCKET)
    {
        Utils_consolePrintf("NETWORK: Unable to open socket (port=%d)!!!\n", port);
        status = NETWORK_ERROR;
    }
    else
    {
        /* Bind to the specified Server port */
        memset( &sin1, 0U, sizeof(struct sockaddr_in) );
        sin1.sin_family     = AF_INET;
        sin1.sin_addr.s_addr = INADDR_ANY;
        sin1.sin_port       = (uint16_t)(htons((uint16_t)(pObj->port)));

        NDK_setsockopt ( pObj->sockFd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof( option ) );

        if( NDK_bind( pObj->sockFd,(struct sockaddr *)&sin1, sizeof(sin1) ) < 0 )
        {
            Utils_consolePrintf("NETWORK: Unable to bind() (port=%d) !!!\n", port);
            fdClose( pObj->sockFd );
            pObj->sockFd = (SOCKET)NETWORK_INVALID_SOCKET;
            status = NETWORK_ERROR;
        }
        else
        {
            if( NDK_listen( pObj->sockFd, 5 ) < 0 )
            {
                fdClose( pObj->sockFd );
                pObj->sockFd = (SOCKET)NETWORK_INVALID_SOCKET;
                status = NETWORK_ERROR;
            }
        }
    }

    return status;
}

static int32_t Network_close(Network_SockObj *pObj, Bool closeServerSock)
{

    if(pObj->connectedSockFd != (SOCKET)NETWORK_INVALID_SOCKET)
    {
        fdClose( pObj->connectedSockFd );
        pObj->connectedSockFd = (SOCKET)NETWORK_INVALID_SOCKET;
    }

    if(closeServerSock)
    {
        if(pObj->sockFd != (SOCKET)NETWORK_INVALID_SOCKET)
        {
            fdClose( pObj->sockFd );
            pObj->sockFd = (SOCKET)NETWORK_INVALID_SOCKET;
        }
    }


    return 0;
}

static int32_t Network_waitConnect(Network_SockObj *pObj, uint32_t timeout)
{
    int32_t status = NETWORK_SUCCESS;

    pObj->pollitem[0].fd = pObj->sockFd;
    pObj->pollitem[0].eventsRequested = 0x1/* POLLIN */;

    if( fdPoll( pObj->pollitem, 1U, timeout ) == SOCKET_ERROR )
    {
        Utils_consolePrintf("NETWORK: fdPoll() failed with SOCKET_ERROR (port=%d) !!!\n", pObj->port);
        status =  NETWORK_ERROR;
    }
    else
    {

        if( pObj->pollitem[0].eventsDetected == FALSE)
        {
            /* NO connection, retry */
            status = NETWORK_SUCCESS;
        }
        else
        {
            if( (uint32_t)(pObj->pollitem[0].eventsDetected) & (uint32_t)0x08/* POLLNVAL */ )
            {
                Utils_consolePrintf("NETWORK: fdPoll() failed with POLLNVAL (port=%d) !!!\n", pObj->port);
                status = NETWORK_ERROR;
            }
            else
            {

                if( (uint32_t)(pObj->pollitem[0].eventsDetected) & (uint32_t)0x1 )
                {
                    pObj->connectedSockFd = (SOCKET)NDK_accept( pObj->sockFd, 0, 0 );

                    if( pObj->connectedSockFd != (SOCKET)NETWORK_INVALID_SOCKET )
                    {
                        Utils_consolePrintf("Connected to PC client !!!\n");
                        status = 1;
                    }
                }
            }
        }
    }

    /* NO connection, retry */
    return status;
}

static int32_t Network_sessionOpen(void *handle)
{

    if(handle==NULL)
    {
        handle = TaskSelf();
    }

    /* Allocate the file environment for this task */
    fdOpenSession( handle );


    return 0;
}

static int32_t Network_sessionClose(void *handle)
{

    if(handle==NULL)
    {
        handle = TaskSelf();
    }

    fdCloseSession( handle );

    return 0;
}

/* end of file */
