/*
 * FreeRTOS+TCP V4.2.2
 * Copyright (C) 2022 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * http://aws.amazon.com/freertos
 * http://www.FreeRTOS.org
 */

/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "list.h"

/* FreeRTOS+TCP includes. */
#include "FreeRTOS_IP.h"
/* FreeRTOS+TCP includes. */
#include "FreeRTOS_IP.h"
#include "FreeRTOS_Sockets.h"
#include "FreeRTOS_IP_Private.h"
#include "FreeRTOS_DNS.h"
#include "NetworkBufferManagement.h"
#include "NetworkInterface.h"

#include "wifi-decl.h"
#include "wmerrno.h"
#include "wifi.h"

#include <wmlog.h>

#define net_e( ... ) \
    wmlog_e( "freertos_tcp", ## __VA_ARGS__ )
#define net_w( ... ) \
    wmlog_w( "freertos_tcp", ## __VA_ARGS__ )
#define net_d( ... ) \
    wmlog( "freertos_tcp", ## __VA_ARGS__ )

#if 0 /*this is lwip structure. */
    #define MAX_INTERFACES_SUPPORTED    3
    static struct netif * netif_arr[ MAX_INTERFACES_SUPPORTED ];
#endif

/* If ipconfigETHERNET_DRIVER_FILTERS_FRAME_TYPES is set to 1, then the Ethernet
 * driver will filter incoming packets and only pass the stack those packets it
 * considers need processing. */
#if ( ipconfigETHERNET_DRIVER_FILTERS_FRAME_TYPES == 0 )
    #define ipCONSIDER_FRAME_FOR_PROCESSING( pucEthernetBuffer )    eProcessBuffer
#else
    #define ipCONSIDER_FRAME_FOR_PROCESSING( pucEthernetBuffer )    eConsiderFrameForProcessing( ( pucEthernetBuffer ) )
#endif

#define IP_ADDR_ANY          ( ( ip_addr_t * ) &ip_addr_any )
#define IP_ADDR_BROADCAST    ( ( ip_addr_t * ) &ip_addr_broadcast )

/** 255.255.255.255 */
#define IPADDR_NONE          ( ( u32_t ) 0xffffffffUL )
/** 127.0.0.1 */
#define IPADDR_LOOPBACK      ( ( u32_t ) 0x7f000001UL )
/** 0.0.0.0 */
#define IPADDR_ANY           ( ( u32_t ) 0x00000000UL )
/** 255.255.255.255 */
#define IPADDR_BROADCAST     ( ( u32_t ) 0xffffffffUL )

/** 255.255.255.255 */
#define INADDR_NONE          IPADDR_NONE
/** 127.0.0.1 */
#define INADDR_LOOPBACK      IPADDR_LOOPBACK
/** 0.0.0.0 */
#define INADDR_ANY           IPADDR_ANY
/** 255.255.255.255 */
#define INADDR_BROADCAST     IPADDR_BROADCAST

enum if_state_t
{
    INTERFACE_DOWN = 0,
    INTERFACE_UP,
};
struct ip_addr
{
    u32_t addr;
};

#define MLAN_BSS_TYPE_STA    0

extern uint8_t outbuf[ 2048 ];
extern bool mlan_is_amsdu( const t_u8 * rcvdata );
extern t_u8 * mlan_get_payload( const t_u8 * rcvdata,
                                t_u16 * payload_len,
                                int * interface );
extern int wrapper_wlan_handle_amsdu_rx_packet( const t_u8 * rcvdata,
                                                const t_u16 datalen );
extern int wrapper_wlan_handle_rx_packet( const t_u16 datalen,
                                          const t_u8 * rcvdata,
                                          NetworkBufferDescriptor_t * pxNetworkBuffer );
static volatile uint32_t xInterfaceState = INTERFACE_DOWN;

static int process_data_packet( const t_u8 * databuf,
                                const t_u16 datalen )
{
    int interface = BSS_TYPE_STA;
    t_u8 * payload = NULL;
    t_u16 payload_len = 0;
    const TickType_t xDescriptorWaitTime = pdMS_TO_TICKS( 250 );

    NetworkBufferDescriptor_t * pxNetworkBuffer;
    IPStackEvent_t xRxEvent = { eNetworkRxEvent, NULL };

    payload = ( t_u8 * ) mlan_get_payload( databuf, &payload_len, &interface );

    if( eConsiderFrameForProcessing( payload ) != eProcessBuffer )
    {
        net_d( "Dropping packet\r\n" );
        return WM_SUCCESS;
    }

    pxNetworkBuffer = pxGetNetworkBufferWithDescriptor( /*payload_len*/ datalen, xDescriptorWaitTime );

    if( pxNetworkBuffer != NULL )
    {
        /* Set the packet size, in case a larger buffer was returned. */
        pxNetworkBuffer->xDataLength = payload_len;

        /* Copy the packet data. */
        memcpy( pxNetworkBuffer->pucEthernetBuffer, payload, payload_len );

        xRxEvent.pvData = ( void * ) pxNetworkBuffer;

        if( xSendEventStructToIPTask( &xRxEvent, xDescriptorWaitTime ) == pdFAIL )
        {
            wmprintf( "Failed to enqueue packet to network stack %p, len %d", payload, payload_len );
            vReleaseNetworkBufferAndDescriptor( pxNetworkBuffer );
            return WM_FAIL;
        }
    }

    return WM_SUCCESS;
}

/* Callback function called from the wifi module */
void handle_data_packet( const t_u8 interface,
                         const t_u8 * rcvdata,
                         const t_u16 datalen )
{
    if( interface == BSS_TYPE_STA )
    {
        process_data_packet( rcvdata, datalen );
    }
}

BaseType_t xNetworkInterfaceInitialise( void )
{
    uint8_t ret;
    mac_addr_t mac_addr;

    ret = wifi_get_device_mac_addr( &mac_addr );

    if( ret != WM_SUCCESS )
    {
        net_d( "Failed to get mac address" );
    }

    FreeRTOS_UpdateMACAddress( mac_addr.mac );

    return ( xInterfaceState == INTERFACE_UP && ret == WM_SUCCESS ) ? pdTRUE : pdFALSE;
}

void vNetworkInterfaceAllocateRAMToBuffers( NetworkBufferDescriptor_t pxNetworkBuffers[ ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS ] )
{
    /* FIX ME. */
}

BaseType_t xGetPhyLinkStatus( void )
{
    /* FIX ME. */
    return pdFALSE;
}
void vNetworkNotifyIFDown()
{
    IPStackEvent_t xRxEvent = { eNetworkDownEvent, NULL };

    xInterfaceState = INTERFACE_DOWN;

    if( xSendEventStructToIPTask( &xRxEvent, 0 ) != pdPASS )
    {
        /* Could not send the message, so it is still pending. */
        net_e( "Could not send network down event" );
    }
    else
    {
        /* Message was sent so it is not pending. */
        net_d( "Sent network down event" );
    }
}

void vNetworkNotifyIFUp()
{
    xInterfaceState = INTERFACE_UP;
}

BaseType_t xNetworkInterfaceOutput( NetworkBufferDescriptor_t * const pxNetworkBuffer,
                                    BaseType_t xReleaseAfterSend )
{
    uint8_t pkt_len;

    if( ( pxNetworkBuffer == NULL ) ||
        ( pxNetworkBuffer->pucEthernetBuffer == NULL ) ||
        ( pxNetworkBuffer->xDataLength == 0 ) )
    {
        net_d( "Incorrect params" );
        return pdFALSE;
    }

    memset( outbuf, 0x00, sizeof( outbuf ) );
    pkt_len = 22 + 4; /* sizeof(TxPD) + INTF_HEADER_LEN */
    memcpy( ( u8_t * ) outbuf + pkt_len, ( u8_t * ) pxNetworkBuffer->pucEthernetBuffer,
            pxNetworkBuffer->xDataLength );
    int ret = wifi_low_level_output( BSS_TYPE_STA, outbuf + pkt_len, pxNetworkBuffer->xDataLength );

    if( ret != WM_SUCCESS )
    {
        net_e( "Failed output %p, length %d, error %d \r\n", pxNetworkBuffer->pucEthernetBuffer, pxNetworkBuffer->xDataLength, ret );
    }

    if( xReleaseAfterSend != pdFALSE )
    {
        vReleaseNetworkBufferAndDescriptor( pxNetworkBuffer );
    }

    return ret == WM_SUCCESS ? pdTRUE : pdFALSE;
}
