/*********************************************************************
   PicoTCP. Copyright (c) 2012-2017 Altran Intelligent Systems. Some rights reserved.
   See COPYING, LICENSE.GPLv2 and LICENSE.GPLv3 for usage.

   .

   Authors: Daniele Lacamera
 *********************************************************************/

#include "pico_config.h"
#include "pico_device.h"
#include "pico_stack.h"
#include "pico_protocol.h"
#include "pico_tree.h"
#include "pico_ipv6.h"
#include "pico_ipv4.h"
#include "pico_icmp6.h"
#include "pico_eth.h"
#include "pico_802154.h"
#include "pico_6lowpan.h"
#include "pico_6lowpan_ll.h"
#include "pico_addressing.h"
#define PICO_DEVICE_DEFAULT_MTU (1500)

struct pico_devices_rr_info {
    struct pico_tree_node *node_in, *node_out;
};

static struct pico_devices_rr_info Devices_rr_info = {
    NULL, NULL
};

static int pico_dev_cmp(void *ka, void *kb)
{
    struct pico_device *a = ka, *b = kb;
    if (a->hash < b->hash)
        return -1;


    if (a->hash > b->hash)
        return 1;

    return 0;
}

PICO_TREE_DECLARE(Device_tree, pico_dev_cmp);

#ifdef PICO_SUPPORT_6LOWPAN
static struct pico_ipv6_link * pico_6lowpan_link_add(struct pico_device *dev, const struct pico_ip6 *prefix)
{
    struct pico_ip6 netmask64 = {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
    struct pico_6lowpan_info *info = (struct pico_6lowpan_info *)dev->eth;
    struct pico_ipv6_link *link = NULL; /* Make sure to return NULL */
    struct pico_ip6 newaddr;

    memcpy(newaddr.addr, prefix->addr, PICO_SIZE_IP6);
    memcpy(newaddr.addr + 8, info->addr_ext.addr, SIZE_6LOWPAN_EXT);
    newaddr.addr[8] = newaddr.addr[8] ^ 0x02; /* Toggle U/L bit */

    /* RFC6775: No Duplicate Address Detection (DAD) is performed if
        * EUI-64-based IPv6 addresses are used (as these addresses are assumed
        * to be globally unique). */
    if ((link = pico_ipv6_link_add_no_dad(dev, newaddr, netmask64))) {
        if (pico_ipv6_is_linklocal(newaddr.addr))
            pico_6lp_nd_start_soliciting(link, NULL);
        else
            pico_6lp_nd_register(link);
    }

    return link;
}

static int pico_6lowpan_store_info(struct pico_device *dev, const uint8_t *mac)
{
    if ((dev->eth = PICO_ZALLOC(sizeof(struct pico_6lowpan_info)))) {
        memcpy(dev->eth, mac, sizeof(struct pico_6lowpan_info));
        return 0;
    } else {
        pico_err = PICO_ERR_ENOMEM;
        return -1;
    }
}
#endif

#ifdef PICO_SUPPORT_IPV6
static void device_init_ipv6_final(struct pico_device *dev, struct pico_ip6 *linklocal)
{
    dev->hostvars.basetime = PICO_ND_REACHABLE_TIME;
    /* RFC 4861 $6.3.2 value between 0.5 and 1.5 times basetime */
    dev->hostvars.reachabletime = ((5 + (pico_rand() % 10)) * PICO_ND_REACHABLE_TIME) / 10;
    dev->hostvars.retranstime = PICO_ND_RETRANS_TIMER;
    pico_icmp6_router_solicitation(dev, linklocal, NULL);
    dev->hostvars.hoplimit = PICO_IPV6_DEFAULT_HOP;
}

struct pico_ipv6_link *pico_ipv6_link_add_local(struct pico_device *dev, const struct pico_ip6 *prefix)
{
    struct pico_ip6 netmask64 = {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
    struct pico_ipv6_link *link = NULL; /* Make sure to return NULL */
    struct pico_ip6 newaddr;

    if (0) {}
#ifdef PICO_SUPPORT_6LOWPAN
    else if (PICO_DEV_IS_6LOWPAN(dev)) {
        link = pico_6lowpan_link_add(dev, prefix);
    }
#endif
    else {
        memcpy(newaddr.addr, prefix->addr, PICO_SIZE_IP6);
        /* modified EUI-64 + invert universal/local bit */
        newaddr.addr[8] = (dev->eth->mac.addr[0] ^ 0x02);
        newaddr.addr[9] = dev->eth->mac.addr[1];
        newaddr.addr[10] = dev->eth->mac.addr[2];
        newaddr.addr[11] = 0xff;
        newaddr.addr[12] = 0xfe;
        newaddr.addr[13] = dev->eth->mac.addr[3];
        newaddr.addr[14] = dev->eth->mac.addr[4];
        newaddr.addr[15] = dev->eth->mac.addr[5];
        if ((link = pico_ipv6_link_add(dev, newaddr, netmask64))) {
            device_init_ipv6_final(dev, &newaddr);
        }
    }

    return link;
}
#endif
static int device_init_mac(struct pico_device *dev, const uint8_t *mac)
{
#ifdef PICO_SUPPORT_IPV6
    struct pico_ip6 linklocal = {{0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xff, 0xfe, 0xaa, 0xaa, 0xaa}};
#endif

    if (0) {}
#ifdef PICO_SUPPORT_6LOWPAN
    else if (PICO_DEV_IS_6LOWPAN(dev)) {
        if (pico_6lowpan_store_info(dev, mac))
            return -1;
    }
#endif
    else {
        if ((dev->eth = PICO_ZALLOC(sizeof(struct pico_ethdev)))) {
            memcpy(dev->eth->mac.addr, mac, PICO_SIZE_ETH);
        } else {
            pico_err = PICO_ERR_ENOMEM;
            return -1;
        }
    }

#ifdef PICO_SUPPORT_IPV6
    if (pico_ipv6_link_add_local(dev, &linklocal) == NULL) {
        PICO_FREE(dev->q_in);
        PICO_FREE(dev->q_out);
        PICO_FREE(dev->eth);
        return -1;
    }
#endif

    return 0;
}

int pico_device_ipv6_random_ll(struct pico_device *dev)
{
#ifdef PICO_SUPPORT_IPV6
    struct pico_ip6 linklocal = {{0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xff, 0xfe, 0xaa, 0xaa, 0xaa}};
    struct pico_ip6 netmask6 = {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
    uint32_t len = (uint32_t)strlen(dev->name);
    if (strcmp(dev->name, "loop")) {
        do {
            /* privacy extension + unset universal/local and individual/group bit */
            len = pico_rand();
            linklocal.addr[8]  = (uint8_t)((len & 0xffu) & (uint8_t)(~0x03));
            linklocal.addr[9]  = (uint8_t)(len >> 8);
            linklocal.addr[10] = (uint8_t)(len >> 16);
            linklocal.addr[11] = (uint8_t)(len >> 24);
            len = pico_rand();
            linklocal.addr[12] = (uint8_t)len;
            linklocal.addr[13] = (uint8_t)(len >> 8);
            linklocal.addr[14] = (uint8_t)(len >> 16);
            linklocal.addr[15] = (uint8_t)(len >> 24);
            pico_rand_feed(dev->hash);
        } while (pico_ipv6_link_get(&linklocal));

        if (pico_ipv6_link_add(dev, linklocal, netmask6) == NULL) {
            return -1;
        }
    }

#endif
    return 0;
}

static int device_init_nomac(struct pico_device *dev)
{
    if (pico_device_ipv6_random_ll(dev) < 0) {
        PICO_FREE(dev->q_in);
        PICO_FREE(dev->q_out);
        return -1;
    }

    dev->eth = NULL;
    return 0;
}

#define DEBUG_IPV6(ip)  { \
                            char ipstr[40] = { 0 }; \
                            pico_ipv6_to_string(ipstr, (ip).addr); \
                            dbg("IPv6 (%s)\n", ipstr); \
                        }

int pico_device_init(struct pico_device *dev, const char *name, const uint8_t *mac)
{
    uint32_t len = (uint32_t)strlen(name);
    int ret = 0;

    if(len > MAX_DEVICE_NAME)
        len = MAX_DEVICE_NAME;

    memcpy(dev->name, name, len);
    dev->hash = pico_hash(dev->name, len);

    Devices_rr_info.node_in  = NULL;
    Devices_rr_info.node_out = NULL;
    dev->q_in = PICO_ZALLOC(sizeof(struct pico_queue));
    if (!dev->q_in)
        return -1;

    dev->q_out = PICO_ZALLOC(sizeof(struct pico_queue));
    if (!dev->q_out) {
        PICO_FREE(dev->q_in);
        return -1;
    }

    if (pico_tree_insert(&Device_tree, dev)) {
		PICO_FREE(dev->q_in);
		PICO_FREE(dev->q_out);
		return -1;
	}
    if (!dev->mtu)
        dev->mtu = PICO_DEVICE_DEFAULT_MTU;

#ifdef PICO_SUPPORT_6LOWPAN
    if (PICO_DEV_IS_6LOWPAN(dev) && LL_MODE_ETHERNET == dev->mode)
        return -1;
#endif

    if (mac) {
        ret = device_init_mac(dev, mac);
    } else {
        if (!dev->mode) {
            ret = device_init_nomac(dev);
        }
#ifdef PICO_SUPPORT_6LOWPAN
        else {
            /* RFC6775: Link Local to be formed based on EUI-64 as per RFC6775 */
            dbg("Link local address to be formed based on EUI-64\n");
            return -1;
        }
#endif
    }
    return ret;
}

static void pico_queue_destroy(struct pico_queue *q)
{
    if (q) {
        pico_queue_empty(q);
        PICO_FREE(q);
    }
}

void pico_device_destroy(struct pico_device *dev)
{
	pico_queue_deinit(dev->q_in);
	pico_queue_deinit(dev->q_out);
    pico_queue_destroy(dev->q_in);
    pico_queue_destroy(dev->q_out);

    if (!dev->mode && dev->eth)
        PICO_FREE(dev->eth);

#ifdef PICO_SUPPORT_IPV4
    pico_ipv4_cleanup_links(dev);
#endif
#ifdef PICO_SUPPORT_IPV6
    pico_ipv6_cleanup_links(dev);
#endif
    pico_tree_delete(&Device_tree, dev);

    if (dev->destroy)
        dev->destroy(dev);

    Devices_rr_info.node_in  = NULL;
    Devices_rr_info.node_out = NULL;
    PICO_FREE(dev);
}

static int check_dev_serve_interrupt(struct pico_device *dev, int loop_score)
{
    if ((dev->__serving_interrupt) && (dev->dsr)) {
        /* call dsr routine */
        loop_score = dev->dsr(dev, loop_score);
    }

    return loop_score;
}

static int check_dev_serve_polling(struct pico_device *dev, int loop_score)
{
    if (dev->poll) {
        loop_score = dev->poll(dev, loop_score);
    }

    return loop_score;
}

static int devloop_in(struct pico_device *dev, int loop_score)
{
    struct pico_frame *f;
    while(loop_score > 0) {
        if (dev->q_in->frames == 0)
            break;

        /* Receive */
        f = pico_dequeue(dev->q_in);
        if (f) {
            pico_datalink_receive(f);
            loop_score--;
        }
    }
    return loop_score;
}

static int devloop_sendto_dev(struct pico_device *dev, struct pico_frame *f)
{
#ifdef PICO_SUPPORT_6LOWPAN
    if (PICO_DEV_IS_6LOWPAN(dev)) {
        return (pico_6lowpan_ll_sendto_dev(dev, f) <= 0);
    }
#endif
    return (dev->send(dev, f->start, (int)f->len) <= 0);
}

static int devloop_out(struct pico_device *dev, int loop_score)
{
    struct pico_frame *f;
    while(loop_score > 0) {
        if (dev->q_out->frames == 0)
            break;

        /* Device dequeue + send */
        f = pico_queue_peek(dev->q_out);
        if (!f)
            break;

        if (devloop_sendto_dev(dev, f) == 0) { /* success. */
            f = pico_dequeue(dev->q_out);
            pico_frame_discard(f); /* SINGLE POINT OF DISCARD for OUTGOING FRAMES */
            loop_score--;
        } else
            break; /* Don't discard */

    }

    return loop_score;
}

static int devloop(struct pico_device *dev, int loop_score, int direction)
{
    /* If device supports interrupts, read the value of the condition and trigger the dsr */
    loop_score = check_dev_serve_interrupt(dev, loop_score);

    /* If device supports polling, give control. Loop score is managed internally,
     * remaining loop points are returned. */
    loop_score = check_dev_serve_polling(dev, loop_score);

    if (direction == PICO_LOOP_DIR_OUT)
        loop_score = devloop_out(dev, loop_score);
    else
        loop_score = devloop_in(dev, loop_score);

    return loop_score;
}


static struct pico_tree_node *pico_dev_roundrobin_start(int direction)
{
    if (Devices_rr_info.node_in == NULL)
        Devices_rr_info.node_in = pico_tree_firstNode(Device_tree.root);

    if (Devices_rr_info.node_out == NULL)
        Devices_rr_info.node_out = pico_tree_firstNode(Device_tree.root);

    if (direction == PICO_LOOP_DIR_IN)
        return Devices_rr_info.node_in;
    else
        return Devices_rr_info.node_out;
}

static void pico_dev_roundrobin_end(int direction, struct pico_tree_node *last)
{
    if (direction == PICO_LOOP_DIR_IN)
        Devices_rr_info.node_in = last;
    else
        Devices_rr_info.node_out = last;
}

#define DEV_LOOP_MIN  16

int pico_devices_loop(int loop_score, int direction)
{
    struct pico_device *start, *next;
    struct pico_tree_node *next_node  = pico_dev_roundrobin_start(direction);

    if (!next_node)
        return loop_score;

    next = next_node->keyValue;
    start = next;

    /* round-robin all devices, break if traversed all devices */
    while ((loop_score > DEV_LOOP_MIN) && (next != NULL)) {
        loop_score = devloop(next, loop_score, direction);
        next_node = pico_tree_next(next_node);
        next = next_node->keyValue;
        if (next == NULL)
        {
            next_node = pico_tree_firstNode(Device_tree.root);
            next = next_node->keyValue;
        }

        if (next == start)
            break;
    }
    pico_dev_roundrobin_end(direction, next_node);
    return loop_score;
}

struct pico_device *pico_get_device(const char*name)
{
    struct pico_device *dev;
    struct pico_tree_node *index;
    pico_tree_foreach(index, &Device_tree){
        dev = index->keyValue;
        if(strcmp(name, dev->name) == 0)
            return dev;
    }
    return NULL;
}

int32_t pico_device_broadcast(struct pico_frame *f)
{
    struct pico_tree_node *index;
    int32_t ret = -1;

    pico_tree_foreach(index, &Device_tree)
    {
        struct pico_device *dev = index->keyValue;
        if(dev != f->dev)
        {
            struct pico_frame *copy = pico_frame_copy(f);

            if(!copy)
                break;

            copy->dev = dev;
            copy->dev->send(copy->dev, copy->start, (int)copy->len);
            pico_frame_discard(copy);
        }
        else
        {
            ret = f->dev->send(f->dev, f->start, (int)f->len);
        }
    }
    return ret;
}

int pico_device_link_state(struct pico_device *dev)
{
    if (!dev->link_state)
        return 1; /* Not supported, assuming link is always up */

    return dev->link_state(dev);
}
