/* Copyright (c) 2014 Nordic Semiconductor. All Rights Reserved.
 *
 * The information contained herein is property of Nordic Semiconductor ASA.
 * Terms and conditions of usage are described in detail in NORDIC
 * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT.
 *
 * Licensees are granted free, non-transferable use of the information. NO
 * WARRANTY of ANY KIND is provided. This heading must NOT be removed from
 * the file.
 *
 */

#include "lwip/opt.h"
#include "lwip/def.h"
#include "lwip/mem.h"
#include "lwip/pbuf.h"
#include "lwip/stats.h"
#include "lwip/netif.h"
#include "lwip/ip6.h"
#include "sdk_config.h"
#include "app_timer.h"
#include "ble_ipsp.h"
#include "ble_6lowpan.h"
#include "mem_manager.h"
#include "iot_context_manager.h"
#include "nrf_platform_port.h"

/**
 * @defgroup nrf_driver_debug_log Module's Log Macros
 * @details Macros used for creating module logs which can be useful in understanding handling
 *          of events or actions on API requests. These are intended for debugging purposes and
 *          can be enabled by defining the NRF_DRIVER_ENABLE_LOGS.
 * @note If NRF_LWIP_DRIVER_CONFIG_LOG_ENABLED is disabled, having NRF_DRIVER_ENABLE_LOGS has no effect.
 * @{
 */

#if NRF_LWIP_DRIVER_CONFIG_LOG_ENABLED

#define NRF_LOG_MODULE_NAME LwIP

#define NRF_LOG_LEVEL       NRF_LWIP_DRIVER_CONFIG_LOG_LEVEL
#define NRF_LOG_INFO_COLOR  NRF_LWIP_DRIVER_CONFIG_INFO_COLOR
#define NRF_LOG_DEBUG_COLOR NRF_LWIP_DRIVER_CONFIG_DEBUG_COLOR

#include "nrf_log.h"
NRF_LOG_MODULE_REGISTER();

#define NRF_DRIVER_TRC     NRF_LOG_DEBUG                                                              /**< Used for getting trace of execution in the module. */
#define NRF_DRIVER_LOG     NRF_LOG_INFO                                                               /**< Used for logging details. */
#define NRF_DRIVER_ERR     NRF_LOG_ERROR                                                              /**< Used for logging errors in the module. */
#define NRF_DRIVER_DUMP    NRF_LOG_HEXDUMP_DEBUG                                                      /**< Used for dumping octet information to get details of bond information etc. */

#define NRF_DRIVER_ENTRY()                       NRF_DRIVER_TRC(">> %s", __func__)
#define NRF_DRIVER_EXIT()                        NRF_DRIVER_TRC("<< %s", __func__)

#else // NRF_LWIP_DRIVER_CONFIG_LOG_ENABLED

#define NRF_DRIVER_TRC(...)                                                                           /**< Disables traces. */
#define NRF_DRIVER_LOG(...)                                                                           /**< Disables loggs. */
#define NRF_DRIVER_DUMP(...)                                                                          /**< Disables dumping of octet streams. */
#define NRF_DRIVER_ERR(...)                                                                           /**< Disables error logs. */

#define NRF_DRIVER_ENTRY(...)
#define NRF_DRIVER_EXIT(...)

#endif // NRF_LWIP_DRIVER_CONFIG_LOG_ENABLED

/** Driver table to maintain mapping between IP interface and Bluetooth as transport. */
struct blenetif {
  iot_interface_t * p_ble_interface;
  struct netif      netif;
};

struct blenetif m_blenetif_table[BLE_6LOWPAN_MAX_INTERFACE];   /**< Table maintaining network interface of LwIP and also corresponding 6lowpan interface. */

/** @brief Function to add received packet to pbuf queue and notify the stack of received packet. */
static void blenetif_input(struct netif  * p_netif,  uint8_t * p_payload, uint16_t payload_len)
{
    struct pbuf  * p_buffer;

    NRF_DRIVER_ENTRY();
    NRF_DRIVER_DUMP(p_payload, payload_len);

    // Allocate a pbuf chain of pbufs from the pool.
    p_buffer = pbuf_alloc(PBUF_RAW, payload_len, PBUF_REF);

    if (p_buffer != NULL)
    {
        p_buffer->payload = p_payload;
        p_buffer->len     = payload_len;
        p_buffer->tot_len = payload_len;
        if (ip6_input(p_buffer, p_netif) != ERR_OK)
        {
            NRF_DRIVER_LOG("IP Stack returned error.");
        }
        UNUSED_VARIABLE(pbuf_free(p_buffer));
    }

    NRF_DRIVER_EXIT();
}


/** @brief Network interface output function registered with LwIP to send packets on 6lowpan. */
static err_t blenetif_output(struct netif * p_netif, struct pbuf * p_buffer, const ip6_addr_t * p_addr)
{
    struct blenetif * p_blenetif    = (struct blenetif *)p_netif->state;
    uint8_t         * p_payload;
    err_t             error_code    = ERR_MEM;
    const    uint16_t requested_len = p_buffer->len;


    NRF_DRIVER_ENTRY();
    NRF_DRIVER_DUMP(p_buffer->payload, requested_len);

    p_payload = nrf_malloc(p_buffer->len);

    if (NULL != p_payload)
    {
        memcpy(p_payload, p_buffer->payload, p_buffer->len);
        uint32_t retval = ble_6lowpan_interface_send(p_blenetif->p_ble_interface,
                                                     p_payload,
                                                     requested_len);
        if (retval != NRF_SUCCESS)
        {
            NRF_DRIVER_ERR("Failed to send IP packet, reason 0x%08X", retval);
            nrf_free(p_payload);
        }
        else
        {
            error_code = ERR_OK;
        }
    }
    else
    {
         NRF_DRIVER_ERR("Failed to allocate memory for output packet");
    }

    NRF_DRIVER_EXIT();

    return error_code;

}


/** @brief Initialization once network interface has been added. */
static err_t blenetif_init(struct netif * p_netif)
{
    // Set link MTU size.
    p_netif->mtu = BLE_IPSP_MTU;

    // Device capabilities and link status
    p_netif->flags = NETIF_FLAG_LINK_UP;

    // Indicate interface link is up.
    netif_set_up(p_netif);

    // Enable Address Auto configuration.
    p_netif->ip6_autoconfig_enabled = 1;

    return ERR_OK;
}


/**@brief Callback registered with 6lowpan to receive asynchronous events. */
static void blenetif_transport_callback(iot_interface_t     * p_interface,
                                        ble_6lowpan_event_t * p_event)
{
    uint32_t index;
    uint32_t err_code;
    struct blenetif * p_blenetif = NULL;

    // Search network interface added for the 6lowpan interface that has generated the event.
    for (index = 0; index < BLE_6LOWPAN_MAX_INTERFACE; index++)
    {
        if (p_interface == m_blenetif_table[index].p_ble_interface)
        {
            p_blenetif = &m_blenetif_table[index];
            break;
        }
    }

    switch(p_event->event_id)
    {
        case BLE_6LO_EVT_INTERFACE_ADD:
        {
            // If no existing interface was found, allocate a new one.
            if (p_blenetif == NULL)
            {
                for (index = 0; index < BLE_6LOWPAN_MAX_INTERFACE; index++)
                {
                    if (m_blenetif_table[index].p_ble_interface == NULL)
                    {
                        p_blenetif = &m_blenetif_table[index];
                        p_blenetif->p_ble_interface = p_interface;
                        break;
                    }
                }
            }
            else
            {
                // Interface already exists, this is an error, should not happen.
                break;
            }

            err_code = iot_context_manager_table_alloc(p_interface);

            if (err_code == NRF_SUCCESS)
            {
                NRF_DRIVER_LOG("Successfully allocated context table!");
            }
            else
            {
                NRF_DRIVER_ERR("Failed to allocate context table!");
            }

#if (IPV6_LL_ADDR_SIZE == 6)
            memcpy(p_blenetif->netif.hwaddr, p_interface->local_addr.identifier, 3);
            memcpy(p_blenetif->netif.hwaddr + 3, p_interface->local_addr.identifier + 5, 3);
#else
            memcpy(p_blenetif->netif.hwaddr, p_interface->local_addr.identifier, IPV6_LL_ADDR_SIZE);
#endif
            p_blenetif->netif.hwaddr_len = IPV6_LL_ADDR_SIZE;

            UNUSED_VARIABLE(netif_add(&p_blenetif->netif, p_blenetif, blenetif_init, NULL));

            p_blenetif->netif.ip6_addr[0].addr[0]  = HTONL(0xFE800000);
            p_blenetif->netif.ip6_addr[0].addr[1]  = HTONL(0x00000000);
            p_blenetif->netif.ip6_addr[0].addr[2]  = *((u32_t *) (&p_interface->local_addr.identifier[0]));
            p_blenetif->netif.ip6_addr[0].addr[3]  = *((u32_t *) (&p_interface->local_addr.identifier[4]));

            // Flip local/universal bit.
            p_blenetif->netif.ip6_addr[0].addr[2] ^= IPV6_IID_FLIP_VALUE;
            p_blenetif->netif.ip6_addr_state[0]    = IP6_ADDR_TENTATIVE;

            p_blenetif->netif.output_ip6           = blenetif_output;

            nrf_driver_interface_up(p_interface);

            break;
        }
        case BLE_6LO_EVT_INTERFACE_DELETE:
        {
            if (p_blenetif != NULL)
            {
                netif_remove(&p_blenetif->netif);
                p_blenetif->p_ble_interface = NULL;

                err_code = iot_context_manager_table_free(p_interface);

                if (err_code == NRF_SUCCESS)
                {
                    NRF_DRIVER_ERR("Successfully freed context table!");
                }
                nrf_driver_interface_down(p_interface);
            }
            break;
        }
        case BLE_6LO_EVT_INTERFACE_DATA_RX:
        {
            if (p_blenetif != NULL)
            {
                // Handle received IP packet.
                blenetif_input(&p_blenetif->netif,
                               p_event->event_param.rx_event_param.p_packet,
                               p_event->event_param.rx_event_param.packet_len);
                nrf_free(p_event->event_param.rx_event_param.p_packet);
            }
            else
            {
                NRF_DRIVER_ERR("Dropping packet, unknown interface.");
            }
            break;
        }
        default:
            break;
    }
}


/**@brief Driver initialization. */
uint32_t nrf_driver_init(void)
{
    uint32_t           err_code;
    uint32_t           index;
    ble_6lowpan_init_t init_param;

    init_param.event_handler = blenetif_transport_callback;
    init_param.p_eui64       = EUI64_LOCAL_IID;

    err_code = iot_context_manager_init();

    if (err_code == NRF_SUCCESS)
    {
        // 6lowpan initialization and callback registration.
        err_code = ble_6lowpan_init(&init_param);

        for (index = 0; index < BLE_6LOWPAN_MAX_INTERFACE; index++)
        {
            m_blenetif_table[index].p_ble_interface = NULL;
        }
    }

    return err_code;
}


/**@brief  Message function to redirect LwIP debug traces to nRF tracing. */
void nrf_message(const char * m)
{
    NRF_DRIVER_LOG(m);
}


/**@brief Interface implementation expected by the LwIP stack to get system ticks. */
u32_t sys_now(void)
{
    u32_t sys_tick;

    sys_tick = app_timer_cnt_get();

    //Note: In case prescaler value is changed, this calculation to get sys tick applies.
    //Currently, this is not used for saving in code execution as sys_now is called
    //often.

    //sys_tick = ROUNDED_DIV(((sys_tick & 0x00FFFFF) * (NRF_DRIVER_TIMER_PRESCALER + 1) * 1000), APP_TIMER_CLOCK_FREQ);
    //sys_tick *= 8;
    return sys_tick;
}

void  mem_init(void)
{
}

void *mem_trim(void *mem, mem_size_t size)
{
    return nrf_realloc(mem, size);
}

void *mem_malloc(mem_size_t size)
{
    return nrf_malloc(size);
}

void *mem_calloc(mem_size_t count, mem_size_t size)
{
    return nrf_calloc(count, size);
}

void  mem_free(void *mem)
{
    nrf_free(mem);
}
