/*
 * Copyright (c) 2016-2018, Arm Limited and affiliates.
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "nsconfig.h"
#include <string.h>
#include "ethernet_mac_api.h"
#include "eventOS_event.h"
#include "nsdynmemLIB.h"
#include "common_functions.h"
#include "MAC/rf_driver_storage.h"
#include "Core/include/ns_monitor.h"

typedef struct eth_mac_internal_s {
    eth_mac_api_t *mac_api;
    arm_device_driver_list_s *dev_driver;
    uint8_t *mtu_ptr;
    uint16_t mtu_size;
    uint8_t mac48_iid64[8];
    bool active_data_request;
    int8_t tasklet_id;
    //linked list link
} eth_mac_internal_t;

static eth_mac_internal_t mac_store = { //Hack only at this point, later put into linked list
    .tasklet_id = -1
};

#define ETH_INIT_EVENT 0
#define ETH_DATA_IND_EVENT 1
#define ETH_DATA_CNF_EVENT 2


#define ETHERNET_HDROFF_DST_ADDR    0
#define ETHERNET_HDROFF_SRC_ADDR    6
#define ETHERNET_HDROFF_TYPE        12
#define ETHERNET_HDRLEN             14

static int8_t eth_mac_api_init(eth_mac_api_t *api, eth_mac_data_confirm *conf_cb, eth_mac_data_indication *ind_cb, uint8_t parent_id);
static void data_req(const eth_mac_api_t *api, const eth_data_req_t *data);
static int8_t mac48_address_set(const eth_mac_api_t *api, const uint8_t *mac48);
static int8_t mac48_address_get(const eth_mac_api_t *api, uint8_t *mac48_buf);
static int8_t iid64_address_set(const eth_mac_api_t *api, const uint8_t *iid64);
static int8_t iid64_address_get(const eth_mac_api_t *api, uint8_t *iid64_buf);

static int8_t eth_mac_net_phy_rx(const uint8_t *data_ptr, uint16_t data_len, uint8_t link_quality, int8_t dbm, int8_t driver_id);
static int8_t eth_mac_net_phy_tx_done(int8_t driver_id, uint8_t tx_handle, phy_link_tx_status_e status, uint8_t cca_retry, uint8_t tx_retry);
static void ethernet_mac_tasklet(arm_event_s *event);

int8_t ethernet_mac_destroy(eth_mac_api_t *mac_api)
{
    if (!mac_api || mac_store.mac_api != mac_api) {
        return -1;
    }

    mac_store.active_data_request = false;
    ns_dyn_mem_free(mac_store.mac_api);
    mac_store.mac_api = NULL;
    mac_store.dev_driver = NULL;
    ns_dyn_mem_free(mac_store.mtu_ptr);
    mac_store.mtu_ptr = NULL;
    mac_store.mtu_size = 0;
    return 0;
}



eth_mac_api_t *ethernet_mac_create(int8_t driver_id)
{
    //TODO: Refactor this away, Drivers should be stored in MAC layer in future
    arm_device_driver_list_s *driver = arm_net_phy_driver_pointer(driver_id);
    if (!driver || !driver->phy_driver) {
        return NULL;
    }

    //Accept to return same mac if mac is generated and driver is same
    if (mac_store.mac_api) {
        if (mac_store.dev_driver == driver) {
            return mac_store.mac_api;
        }
        return NULL;
    }

    uint8_t *buffer_ptr = NULL;
    uint16_t buffer_length = 0;
    bool address_resolution_needed = true;

    switch (driver->phy_driver->link_type) {

        case PHY_LINK_SLIP:
        case PHY_LINK_PPP:
            //Do not Allocate
            address_resolution_needed = false;
            buffer_length = 0;
            break;

        case PHY_LINK_TUN:
            address_resolution_needed = false;
            buffer_length = 1500 + 4;
            break;
        case PHY_LINK_ETHERNET_TYPE:

            buffer_length = 1500 + ETHERNET_HDRLEN;



            break;

        default:
            return NULL;
    }
    if (buffer_length) {
        buffer_ptr = ns_dyn_mem_alloc(buffer_length);
        if (!buffer_ptr) {
            return NULL;
        }
    }

    eth_mac_api_t *this = ns_dyn_mem_alloc(sizeof(eth_mac_api_t));
    if (!this) {
        ns_dyn_mem_free(buffer_ptr);
        return NULL;
    }
    memset(this, 0, sizeof(eth_mac_api_t));
    this->mac_initialize = &eth_mac_api_init;
    this->data_req = &data_req;

    if (driver->phy_driver->link_type == PHY_LINK_PPP) {
        this->iid64_get = &iid64_address_get;
        this->iid64_set = &iid64_address_set;
    } else {
        this->mac48_get = &mac48_address_get;
        this->mac48_set = &mac48_address_set;
    }

    this->address_resolution_needed = address_resolution_needed;

    memset(&mac_store.mac48_iid64, 0, 8);
    mac_store.active_data_request = false;
    mac_store.mac_api = this;
    mac_store.dev_driver = driver;
    mac_store.mtu_ptr = buffer_ptr;
    mac_store.mtu_size = buffer_length;

    if (driver->phy_driver->link_type == PHY_LINK_PPP) {
        memcpy(&mac_store.mac48_iid64, mac_store.dev_driver->phy_driver->PHY_MAC, 8);
    } else {
        memcpy(&mac_store.mac48_iid64, mac_store.dev_driver->phy_driver->PHY_MAC, 6);
    }
    if (mac_store.tasklet_id == -1) {
        mac_store.tasklet_id = eventOS_event_handler_create(&ethernet_mac_tasklet, ETH_INIT_EVENT);
    }
    arm_net_phy_init(driver->phy_driver, &eth_mac_net_phy_rx, &eth_mac_net_phy_tx_done);

    return this;
}

static void ethernet_mac_tasklet(arm_event_s *event)
{
    uint8_t event_type = event->event_type;

    switch (event_type) {
        case ETH_DATA_IND_EVENT: {
            eth_data_ind_t *data_ind = event->data_ptr;
            mac_store.mac_api->data_ind_cb(mac_store.mac_api, data_ind);
            ns_dyn_mem_free(((eth_data_ind_t *)event->data_ptr)->msdu);
            ns_dyn_mem_free(event->data_ptr);
            break;
        }
        case ETH_DATA_CNF_EVENT: {
            eth_data_conf_t *data_conf = event->data_ptr;
            mac_store.mac_api->data_conf_cb(mac_store.mac_api, data_conf);
            ns_dyn_mem_free(event->data_ptr);
            mac_store.active_data_request = false;
            break;
        }
        case ETH_INIT_EVENT:
        default: {
            break;
        }
    }
}

static int8_t eth_mac_api_init(eth_mac_api_t *api, eth_mac_data_confirm *conf_cb, eth_mac_data_indication *ind_cb, uint8_t parent_id)
{
    if (mac_store.mac_api != api) {
        return -1;
    }
    eth_mac_api_t *cur = mac_store.mac_api;
    cur->data_conf_cb = conf_cb;
    cur->data_ind_cb = ind_cb;
    cur->parent_id = parent_id;

    return 0;
}

static void data_req(const eth_mac_api_t *api, const eth_data_req_t *data)
{
    if (mac_store.mac_api != api || !mac_store.dev_driver->phy_driver || !data  || !data->msduLength) {
        return;
    }


    uint8_t *data_ptr;
    uint16_t data_length;

    //Build header
    switch (mac_store.dev_driver->phy_driver->link_type) {
        case PHY_LINK_ETHERNET_TYPE:

            if (data->msduLength + ETHERNET_HDRLEN > mac_store.mtu_size || !data->dstAddress || !data->srcAddress) {
                return;
            }

            data_ptr = mac_store.mtu_ptr;
            data_length = data->msduLength + ETHERNET_HDRLEN;
            memcpy(data_ptr + ETHERNET_HDROFF_DST_ADDR, data->dstAddress, 6);
            memcpy(data_ptr + ETHERNET_HDROFF_SRC_ADDR, data->srcAddress, 6);
            common_write_16_bit(data->etehernet_type, data_ptr + ETHERNET_HDROFF_TYPE);
            memcpy(data_ptr + ETHERNET_HDRLEN, data->msdu, data->msduLength);
            break;

        case PHY_LINK_TUN:
            if (data->msduLength + 4 > mac_store.mtu_size) {
                return;
            }
            data_ptr = mac_store.mtu_ptr;
            /* TUN header
             * [ TUN FLAGS 2B | PROTOCOL 2B | PAYLOAD ]
             * Tun flags may be 0, and protocol is same as in ether-type field, so
             * replace flags with e.g. IFF_TUN and packet is converted to TUN frame
             */
            common_write_16_bit(0, data_ptr);
            common_write_16_bit(data->etehernet_type, data_ptr + 2);
            memcpy(data_ptr + 4, data->msdu, data->msduLength);
            data_length = data->msduLength + 4;
            break;

        default: //SLIP
            data_ptr = data->msdu;
            data_length = data->msduLength;
            break;
    }

    mac_store.active_data_request = true;
    mac_store.dev_driver->phy_driver->tx(data_ptr, data_length, 0, PHY_LAYER_PAYLOAD);
}

static int8_t eth_mac_net_phy_rx(const uint8_t *data_ptr, uint16_t data_len, uint8_t link_quality, int8_t dbm, int8_t driver_id)
{
    arm_device_driver_list_s *driver = arm_net_phy_driver_pointer(driver_id);
    if (!data_ptr || !driver || driver != mac_store.dev_driver) {
        return -1;
    }

    if (data_len == 0) {
        return -1;
    }

    if (!ns_monitor_packet_allocation_allowed()) {
        // stack can not handle new packets for routing
        return -1;
    }

    eth_data_ind_t *data_ind = ns_dyn_mem_temporary_alloc(sizeof(eth_data_ind_t));
    if (!data_ind) {
        return -1;
    }
    memset(data_ind, 0, sizeof(eth_data_ind_t));

    if (driver->phy_driver->link_type == PHY_LINK_ETHERNET_TYPE) {
        if (data_len < ETHERNET_HDRLEN + 1) {
            ns_dyn_mem_free(data_ind);
            return -1;
        }

        memcpy(data_ind->dstAddress, data_ptr +  ETHERNET_HDROFF_DST_ADDR, 6);
        memcpy(data_ind->srcAddress, data_ptr +  ETHERNET_HDROFF_SRC_ADDR, 6);

        data_ind->etehernet_type = common_read_16_bit(data_ptr + ETHERNET_HDROFF_TYPE);

        data_ptr += ETHERNET_HDRLEN;
        data_len -= ETHERNET_HDRLEN;

    } else if (driver->phy_driver->link_type == PHY_LINK_TUN) {
        if (data_len < 5) {
            ns_dyn_mem_free(data_ind);
            return -1;
        }
        /* TUN header
         * [ TUN FLAGS 2B | PROTOCOL 2B | PAYLOAD ]
         * Protocol is ether-type id.
         */
        data_ind->etehernet_type = common_read_16_bit(data_ptr + 2);

        data_len -= 4;
        data_ptr += 4;
    } else if (driver->phy_driver->link_type == PHY_LINK_SLIP || driver->phy_driver->link_type == PHY_LINK_PPP) {
        data_ind->etehernet_type = ETHERTYPE_IPV6;
    }

    data_ind->msdu = ns_dyn_mem_temporary_alloc(data_len);
    if (!data_ind->msdu) {
        ns_dyn_mem_free(data_ind);
        return -1;
    }
    memcpy(data_ind->msdu, data_ptr, data_len);
    data_ind->msduLength = data_len;
    data_ind->dbm = dbm;
    data_ind->link_quality = link_quality;

    arm_event_s event = {
        .receiver = mac_store.tasklet_id,
        .sender = 0,
        .event_id = 0,
        .data_ptr = data_ind,
        .event_type = ETH_DATA_IND_EVENT,
        .priority = ARM_LIB_HIGH_PRIORITY_EVENT,
    };

    if (eventOS_event_send(&event)) {
        ns_dyn_mem_free(data_ind->msdu);
        ns_dyn_mem_free(data_ind);
        return -1;
    }

    return 0;
}

static int8_t eth_mac_net_phy_tx_done(int8_t driver_id, uint8_t tx_handle, phy_link_tx_status_e status, uint8_t cca_retry, uint8_t tx_retry)
{
    (void)tx_handle;
    (void)cca_retry;
    (void)tx_retry;
    arm_device_driver_list_s *driver = arm_net_phy_driver_pointer(driver_id);
    if (!driver) {
        return -1;
    }

    if (mac_store.active_data_request) {
        mac_store.active_data_request = false;
        eth_data_conf_t *data_conf = ns_dyn_mem_temporary_alloc(sizeof(eth_data_conf_t));
        if (!data_conf) {
            return -1;
        }
        data_conf->status = status;
        arm_event_s event = {
            .receiver = mac_store.tasklet_id,
            .sender = 0,
            .event_id = 0,
            .data_ptr = data_conf,
            .event_type = ETH_DATA_CNF_EVENT,
            .priority = ARM_LIB_HIGH_PRIORITY_EVENT,
        };

        return eventOS_event_send(&event);
    }
    return 0;
}

static int8_t mac48_address_set(const eth_mac_api_t *api, const uint8_t *mac48)
{
    if (!mac48 || !api || mac_store.mac_api != api) {
        return -1;
    }
    memcpy(mac_store.mac48_iid64, mac48, 6);
    phy_device_driver_s *driver = mac_store.dev_driver->phy_driver;
    if (driver->address_write) {
        driver->address_write(PHY_MAC_48BIT, mac_store.mac48_iid64);
    }
    return 0;
}

static int8_t mac48_address_get(const eth_mac_api_t *api, uint8_t *mac48_buf)
{
    if (!mac48_buf || !api || mac_store.mac_api != api) {
        return -1;
    }
    memcpy(&mac_store.mac48_iid64, mac_store.dev_driver->phy_driver->PHY_MAC, 6);
    memcpy(mac48_buf, mac_store.mac48_iid64, 6);
    return 0;
}

static int8_t iid64_address_set(const eth_mac_api_t *api, const uint8_t *iid64)
{
    if (!iid64 || !api || mac_store.mac_api != api) {
        return -1;
    }
    memcpy(mac_store.mac48_iid64, iid64, 8);
    phy_device_driver_s *driver = mac_store.dev_driver->phy_driver;
    if (driver->address_write) {
        driver->address_write(PHY_MAC_48BIT, mac_store.mac48_iid64);
    }
    return 0;
}

static int8_t iid64_address_get(const eth_mac_api_t *api, uint8_t *iid64_buf)
{
    if (!iid64_buf || !api || mac_store.mac_api != api) {
        return -1;
    }
    memcpy(&mac_store.mac48_iid64, mac_store.dev_driver->phy_driver->PHY_MAC, 8);
    memcpy(iid64_buf, mac_store.mac48_iid64, 8);
    return 0;
}
