#include <pico_stack.h>
#include <pico_tree.h>
#include <pico_socket.h>
#include <pico_aodv.h>
#include <pico_device.h>
#include <pico_ipv4.h>
#include "modules/pico_aodv.c"
#include "check.h"


Suite *pico_suite(void);

START_TEST(tc_aodv_node_compare)
{
    struct pico_aodv_node a, b;
    a.dest.ip4.addr = long_be(1);
    b.dest.ip4.addr = long_be(2);

    fail_if(aodv_node_compare(&a, &b) >= 0);
    a.dest.ip4.addr = long_be(3);
    fail_if(aodv_node_compare(&a, &b) <= 0);
    b.dest.ip4.addr = long_be(3);
    fail_if(aodv_node_compare(&a, &b) != 0);
}
END_TEST

START_TEST(tc_aodv_dev_cmp)
{
    struct pico_device a, b;
    a.hash = 1;
    b.hash = 2;
    fail_if(aodv_dev_cmp(&a, &b) >= 0);
    a.hash = 3;
    fail_if(aodv_dev_cmp(&a, &b) <= 0);
    b.hash = 3;
    fail_if(aodv_dev_cmp(&a, &b) != 0);

}
END_TEST

START_TEST(tc_get_node_by_addr)
{
    struct pico_aodv_node a;
    union pico_address test;
    a.dest.ip4.addr = long_be(10);
    test.ip4.addr = long_be(10);

    pico_tree_insert(&aodv_nodes, &a);

    fail_if(get_node_by_addr(&test) != &a);
    pico_tree_delete(&aodv_nodes, &a);
    fail_if(get_node_by_addr(&test) != NULL);

}
END_TEST

static int set_bcast_link_called = 0;
void pico_ipv4_route_set_bcast_link(struct pico_ipv4_link *link)
{
    IGNORE_PARAMETER(link);
    set_bcast_link_called++;
}

START_TEST(tc_pico_aodv_set_dev)
{
    struct pico_device *dev = NULL;
    pico_aodv_set_dev(dev);
    fail_if(set_bcast_link_called != 1);
}
END_TEST

START_TEST(tc_aodv_peer_refresh)
{
    /* TODO: test this: static int aodv_peer_refresh(struct pico_aodv_node *node, uint32_t seq) */
    struct pico_aodv_node node;
    memset(&node, 0, sizeof(node));
    node.dseq = 0xFFFF;
    fail_if(aodv_peer_refresh(&node, 10) != 0); /* should succeed, because SYNC flag is not yet set... */
    fail_if((node.flags & PICO_AODV_NODE_SYNC) == 0); /* Flag should be set after last call... */
    fail_if(aodv_peer_refresh(&node, 5) == 0); /* should FAIL, because seq number is lower...  */
    fail_if(aodv_peer_refresh(&node, 10) == 0); /* should FAIL, because seq number is still the same...  */
    fail_if(aodv_peer_refresh(&node, 15) != 0); /* should succeed, because seq number is now bigger...  */
    fail_if(node.dseq != 15);
}
END_TEST

static int called_route_add = 0;
static uint32_t route_add_gw = 0u;
static int route_add_metric = 0;
int pico_ipv4_route_add(struct pico_ip4 address, struct pico_ip4 netmask, struct pico_ip4 gateway, int metric, struct pico_ipv4_link *link)
{
    IGNORE_PARAMETER(link);
    IGNORE_PARAMETER(netmask);
    IGNORE_PARAMETER(address);
    called_route_add++;
    route_add_gw = gateway.addr;
    route_add_metric = metric;
    return 0;
}

START_TEST(tc_aodv_elect_route)
{
    struct pico_aodv_node node;
    union pico_address gateway;
    memset(&node, 0, sizeof(node));
    gateway.ip4.addr = 0x55555555;

    called_route_add = 0;
    aodv_elect_route(&node, NULL, 150, NULL);
    fail_if(called_route_add != 1); /* Not active, should succeed */
    fail_if(route_add_gw != 0u);
    fail_if(route_add_metric != 1);

    called_route_add = 0;
    route_add_metric = 0;
    route_add_gw = 0u;
    node.flags = PICO_AODV_NODE_ROUTE_DOWN | PICO_AODV_NODE_ROUTE_UP;
    aodv_elect_route(&node, &gateway, 150, NULL);
    fail_if(called_route_add != 0); /* Already active, existing metric is lower */

    called_route_add = 0;
    route_add_metric = 0;
    route_add_gw = 0u;
    node.metric = 22;
    aodv_elect_route(&node, &gateway, 15, NULL);
    fail_if(called_route_add != 1); /* Already active, existing metric is higher */
    fail_if(route_add_metric != 16);
    fail_if(route_add_gw != 0x55555555);

}
END_TEST

START_TEST(tc_aodv_peer_new)
{
    union pico_address addr;
    struct pico_aodv_node *new;
    addr.ip4.addr = 0x44444444;
    new = aodv_peer_new(&addr);
    fail_if(!new);
    fail_if(!get_node_by_addr(&addr));
    pico_set_mm_failure(1);
    new = aodv_peer_new(&addr);
    fail_if(new);
}
END_TEST
START_TEST(tc_aodv_peer_eval)
{
    union pico_address addr;
    struct pico_aodv_node *node = NULL;
    /* Case 0: Creation */
    addr.ip4.addr = 0x11224433;
    node = aodv_peer_eval(&addr, 0, 0);
    fail_if(!node);
    fail_if((node->flags & PICO_AODV_NODE_SYNC) != 0); /* Not synced! */

    /* Case 1: retrieve, unsynced */
    node->metric = 42;
    node = aodv_peer_eval(&addr, 0, 0); /* Should get existing node! */
    fail_if(!node);
    fail_if(node->metric != 42);
    fail_if((node->flags & PICO_AODV_NODE_SYNC) != 0); /* Not synced! */


    /* Case 2: new node, invalid allocation */
    addr.ip4.addr = 0x11224455;
    pico_set_mm_failure(1);
    node = aodv_peer_eval(&addr, long_be(10), 1);
    fail_if(node);

    /* Case 3: existing node, setting the new sequence */
    addr.ip4.addr = 0x11224433;
    node = aodv_peer_eval(&addr, long_be(10), 1); /* Should get existing node! */
    fail_if(node->metric != 42);
    fail_if((node->flags & PICO_AODV_NODE_SYNC) == 0);
    fail_if(node->dseq != 10);
}
END_TEST

START_TEST(tc_aodv_lifetime)
{
    struct pico_aodv_node node;
    pico_time now = PICO_TIME_MS();
    memset(&node, 0, sizeof(node));
    fail_if(aodv_lifetime(&node) == 0);
    fail_if(node.last_seen < now);
    node.last_seen = now - AODV_ACTIVE_ROUTE_TIMEOUT;
    fail_if(aodv_lifetime(&node) != 0);
}
END_TEST

static uint8_t sent_pkt_type = 0xFF;
static uint32_t dest_addr = 0;
static int pico_socket_sendto_called = 0;
static int pico_socket_sendto_extended_called = 0;
uint32_t expected_dseq = 0;
int pico_socket_sendto(struct pico_socket *s, const void *buf, const int len, void *dst, uint16_t remote_port)
{
    uint8_t *pkt = (uint8_t *)(uintptr_t)buf;
    printf("Sendto called!\n");
    pico_socket_sendto_called++;
    fail_if(remote_port != short_be(PICO_AODV_PORT));
    fail_if (s != aodv_socket);
    fail_if(pkt[0] > 4);
    fail_if(pkt[0] < 1);
    sent_pkt_type = pkt[0];
    dest_addr = ((union pico_address *)dst)->ip4.addr;
    if (sent_pkt_type == AODV_TYPE_RREQ) {
        /* struct pico_aodv_rreq *req = (struct pico_aodv_rreq *)(uintptr_t)buf; */
        fail_if(len != sizeof(struct pico_aodv_rreq));
    }
    else if (sent_pkt_type == AODV_TYPE_RREP) {
        struct pico_aodv_rrep *rep = (struct pico_aodv_rrep *)(uintptr_t)buf;
        fail_if(len != sizeof(struct pico_aodv_rrep));
        fail_if(rep->dest != 0x11111111);
        fail_if(rep->orig != 0x22222222);
        printf("rep->dseq= %08x, exp: %08x\n", rep->dseq, expected_dseq);
        fail_if(rep->dseq != expected_dseq);
    }

    return len;
}

int pico_socket_sendto_extended(struct pico_socket *s, const void *buf, const int len,
                                void *dst, uint16_t remote_port, struct pico_msginfo *msginfo)
{
    IGNORE_PARAMETER(msginfo);
    pico_socket_sendto_extended_called++;
    return pico_socket_sendto(s, buf, len, dst, remote_port);
}

START_TEST(tc_aodv_send_reply)
{
    struct pico_aodv_node node;
    struct pico_aodv_rreq req;
    struct pico_msginfo info;
    union pico_address addr;
    addr.ip4.addr = 0x22222222;
    memset(&node, 0, sizeof(node));
    memset(&req, 0, sizeof(req));
    memset(&info, 0, sizeof(info));

    req.dest = 0x11111111;
    req.orig = addr.ip4.addr;
    req.dseq = 99;

    aodv_send_reply(&node, &req, 1, &info);
    fail_if(pico_socket_sendto_called != 0); /* Call should have no effect, due to non-existing origin node */

    /* Creating origin... */
    fail_if(aodv_peer_new(&addr) == NULL);
    aodv_send_reply(&node, &req, 0, &info);
    fail_if(pico_socket_sendto_called != 0); /* Call should have no effect, node non-local, non sync'd */

    expected_dseq = long_be(pico_aodv_local_id + 1);
    aodv_send_reply(&node, &req, 1, &info);
    fail_if(pico_socket_sendto_called != 1);  /* Call should succeed */
    pico_socket_sendto_called = 0;

    node.flags = PICO_AODV_NODE_SYNC;
    node.dseq = 42;
    expected_dseq = long_be(42);
    aodv_send_reply(&node, &req, 0, &info);
    fail_if(pico_socket_sendto_called != 1);  /* Call should succeed */
    pico_socket_sendto_called = 0;
}
END_TEST

static struct pico_ipv4_link global_link;
struct pico_ipv4_link *pico_ipv4_link_by_dev(struct pico_device *dev)
{
    IGNORE_PARAMETER(dev);
    if (!global_link.address.addr)
        return NULL;

    printf("Setting link!\n");
    return &global_link;
}

static struct pico_device global_dev;
static int link_find_success = 0;
struct pico_device *pico_ipv4_link_find(struct pico_ip4 *ip4)
{
    IGNORE_PARAMETER(ip4);
    if (link_find_success)
        return &global_dev;

    return NULL;
}

static int timer_set = 0;
uint32_t pico_timer_add(pico_time expire, void (*timer)(pico_time, void *), void *arg)
{
    IGNORE_PARAMETER(arg);
    IGNORE_PARAMETER(timer);
    IGNORE_PARAMETER(expire);
    printf("Timer set!\n");
    timer_set++;
    return (uint32_t ) 0x99999999;

}

START_TEST(tc_aodv_send_req)
{
    struct pico_aodv_node node;
    struct pico_device d;
    aodv_socket = NULL;

    memset(&node, 0, sizeof(node));
    node.flags = PICO_AODV_NODE_ROUTE_DOWN | PICO_AODV_NODE_ROUTE_UP;
    fail_if(aodv_send_req(&node) != 0); /* Should fail: node already active */
    fail_if(pico_socket_sendto_called != 0);
    fail_if(pico_socket_sendto_extended_called != 0);

    node.flags = 0;
    fail_if(aodv_send_req(&node) != 0); /* Should fail: no devices in tree */
    fail_if(pico_socket_sendto_called != 0);
    fail_if(pico_socket_sendto_extended_called != 0);

    pico_tree_insert(&aodv_devices, &d);
    fail_if(aodv_send_req(&node) != -1); /* Should fail: aodv_socket == NULL */
    fail_if(pico_err != PICO_ERR_EINVAL);
    fail_if(pico_socket_sendto_called != 0);
    fail_if(pico_socket_sendto_extended_called != 0);


    /* No valid link, timer is set, call does not send packets */
    aodv_socket = (struct pico_socket*) 1;
    global_link.address.addr = 0;
    fail_if(aodv_send_req(&node) != 0);
    fail_if(pico_socket_sendto_called != 0);
    fail_if(pico_socket_sendto_extended_called != 0);
    fail_if(timer_set != 1);
    timer_set = 0;


    /* One valid link, timer is set, one packet is sent */
    global_link.address.addr = 0xFEFEFEFE;
    fail_if(aodv_send_req(&node) != 1);
    fail_if(pico_socket_sendto_called != 1);
    fail_if(pico_socket_sendto_extended_called != 1);
    fail_if(timer_set != 1);
    pico_socket_sendto_called = 0;
    pico_socket_sendto_extended_called = 0;
    timer_set = 0;

}
END_TEST

START_TEST(tc_aodv_reverse_path_discover)
{
    struct pico_aodv_node node;
    memset(&node, 0, sizeof(node));
    aodv_reverse_path_discover(0, &node);
}
END_TEST

START_TEST(tc_aodv_recv_valid_rreq)
{
    struct pico_aodv_node node;
    struct pico_aodv_rreq req;
    struct pico_msginfo info;
    union pico_address addr;
    memset(&node, 0, sizeof(node));
    memset(&req, 0, sizeof(req));
    memset(&info, 0, sizeof(info));

    addr.ip4.addr = 0x22222222;

    link_find_success = 0;
    aodv_recv_valid_rreq(&node, &req, &info);
    fail_if(pico_socket_sendto_called > 0);

    /* link not local, but active node, set to send reply, no timer */
    link_find_success = 0;
    fail_if(aodv_peer_new(&addr) == NULL);
    global_link.address.addr = 0x44444444;
    req.orig = addr.ip4.addr;
    req.dest = 0x11111111;
    node.flags = PICO_AODV_NODE_SYNC | PICO_AODV_NODE_ROUTE_UP | PICO_AODV_NODE_ROUTE_DOWN;
    node.dseq = 42;
    expected_dseq = long_be(42);
    aodv_recv_valid_rreq(&node, &req, &info);
    fail_if(pico_socket_sendto_called < 1);
    fail_if(timer_set > 0);
    pico_socket_sendto_called = 0;

    /* link local, active node. Full send + set timer. */
    link_find_success = 1;
    expected_dseq = long_be(pico_aodv_local_id + 1);
    aodv_peer_new(&addr);
    aodv_recv_valid_rreq(&node, &req, &info);
    fail_if(pico_socket_sendto_called < 1);
    fail_if(timer_set < 1);

}
END_TEST

START_TEST(tc_aodv_parse_rreq)
{
    /* TODO: test this: static void aodv_parse_rreq(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) */
}
END_TEST

START_TEST(tc_aodv_parse_rrep)
{
    /* TODO: test this: static void aodv_parse_rrep(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) */
}
END_TEST

START_TEST(tc_aodv_parse_rerr)
{
    /* TODO: test this: static void aodv_parse_rerr(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) */
}
END_TEST

START_TEST(tc_aodv_parse_rack)
{
    aodv_parse_rack(NULL, NULL, 0, NULL);
}
END_TEST

START_TEST(tc_pico_aodv_parse)
{
}
END_TEST

START_TEST(tc_pico_aodv_socket_callback)
{
    /* TODO: test this: static void pico_aodv_socket_callback(uint16_t ev, struct pico_socket *s) */
}
END_TEST

START_TEST(tc_aodv_make_rreq)
{
    /* TODO: test this: static void aodv_make_rreq(struct pico_aodv_node *node, struct pico_aodv_rreq *req) */
}
END_TEST

START_TEST(tc_aodv_retrans_rreq)
{
    /* TODO: test this: static void aodv_retrans_rreq(pico_time now, void *arg) */
}
END_TEST

START_TEST(tc_pico_aodv_expired)
{
    /* TODO: test this: static void pico_aodv_expired(struct pico_aodv_node *node) */
}
END_TEST

START_TEST(tc_pico_aodv_collector)
{
    /* TODO: test this: static void pico_aodv_collector(pico_time now, void *arg) */
}
END_TEST


Suite *pico_suite(void)
{
    Suite *s = suite_create("PicoTCP");

    TCase *TCase_aodv_node_compare = tcase_create("Unit test for aodv_node_compare");
    TCase *TCase_aodv_dev_cmp = tcase_create("Unit test for aodv_dev_cmp");
    TCase *TCase_get_node_by_addr = tcase_create("Unit test for get_node_by_addr");
    TCase *TCase_pico_aodv_set_dev = tcase_create("Unit test for pico_aodv_set_dev");
    TCase *TCase_aodv_peer_refresh = tcase_create("Unit test for aodv_peer_refresh");
    TCase *TCase_aodv_elect_route = tcase_create("Unit test for aodv_elect_route");
    TCase *TCase_aodv_peer_new = tcase_create("Unit test for aodv_peer_new");
    TCase *TCase_aodv_peer_eval = tcase_create("Unit test for aodv_peer_eval");
    TCase *TCase_aodv_lifetime = tcase_create("Unit test for aodv_lifetime");
    TCase *TCase_aodv_send_reply = tcase_create("Unit test for aodv_send_reply");
    TCase *TCase_aodv_send_req = tcase_create("Unit test for aodv_send_req");
    TCase *TCase_aodv_reverse_path_discover = tcase_create("Unit test for aodv_reverse_path_discover");
    TCase *TCase_aodv_recv_valid_rreq = tcase_create("Unit test for aodv_recv_valid_rreq");
    TCase *TCase_aodv_parse_rreq = tcase_create("Unit test for aodv_parse_rreq");
    TCase *TCase_aodv_parse_rrep = tcase_create("Unit test for aodv_parse_rrep");
    TCase *TCase_aodv_parse_rerr = tcase_create("Unit test for aodv_parse_rerr");
    TCase *TCase_aodv_parse_rack = tcase_create("Unit test for aodv_parse_rack");
    TCase *TCase_pico_aodv_parse = tcase_create("Unit test for pico_aodv_parse");
    TCase *TCase_pico_aodv_socket_callback = tcase_create("Unit test for pico_aodv_socket_callback");
    TCase *TCase_aodv_make_rreq = tcase_create("Unit test for aodv_make_rreq");
    TCase *TCase_aodv_retrans_rreq = tcase_create("Unit test for aodv_retrans_rreq");
    TCase *TCase_pico_aodv_expired = tcase_create("Unit test for pico_aodv_expired");
    TCase *TCase_pico_aodv_collector = tcase_create("Unit test for pico_aodv_collector");


    tcase_add_test(TCase_aodv_node_compare, tc_aodv_node_compare);
    suite_add_tcase(s, TCase_aodv_node_compare);
    tcase_add_test(TCase_aodv_dev_cmp, tc_aodv_dev_cmp);
    suite_add_tcase(s, TCase_aodv_dev_cmp);
    tcase_add_test(TCase_get_node_by_addr, tc_get_node_by_addr);
    suite_add_tcase(s, TCase_get_node_by_addr);
    tcase_add_test(TCase_pico_aodv_set_dev, tc_pico_aodv_set_dev);
    suite_add_tcase(s, TCase_pico_aodv_set_dev);
    tcase_add_test(TCase_aodv_peer_refresh, tc_aodv_peer_refresh);
    suite_add_tcase(s, TCase_aodv_peer_refresh);
    tcase_add_test(TCase_aodv_elect_route, tc_aodv_elect_route);
    suite_add_tcase(s, TCase_aodv_elect_route);
    tcase_add_test(TCase_aodv_peer_new, tc_aodv_peer_new);
    suite_add_tcase(s, TCase_aodv_peer_new);
    tcase_add_test(TCase_aodv_peer_eval, tc_aodv_peer_eval);
    suite_add_tcase(s, TCase_aodv_peer_eval);
    tcase_add_test(TCase_aodv_lifetime, tc_aodv_lifetime);
    suite_add_tcase(s, TCase_aodv_lifetime);
    tcase_add_test(TCase_aodv_send_reply, tc_aodv_send_reply);
    suite_add_tcase(s, TCase_aodv_send_reply);
    tcase_add_test(TCase_aodv_send_req, tc_aodv_send_req);
    suite_add_tcase(s, TCase_aodv_send_req);
    tcase_add_test(TCase_aodv_reverse_path_discover, tc_aodv_reverse_path_discover);
    suite_add_tcase(s, TCase_aodv_reverse_path_discover);
    tcase_add_test(TCase_aodv_recv_valid_rreq, tc_aodv_recv_valid_rreq);
    suite_add_tcase(s, TCase_aodv_recv_valid_rreq);
    tcase_add_test(TCase_aodv_parse_rreq, tc_aodv_parse_rreq);
    suite_add_tcase(s, TCase_aodv_parse_rreq);
    tcase_add_test(TCase_aodv_parse_rrep, tc_aodv_parse_rrep);
    suite_add_tcase(s, TCase_aodv_parse_rrep);
    tcase_add_test(TCase_aodv_parse_rerr, tc_aodv_parse_rerr);
    suite_add_tcase(s, TCase_aodv_parse_rerr);
    tcase_add_test(TCase_aodv_parse_rack, tc_aodv_parse_rack);
    suite_add_tcase(s, TCase_aodv_parse_rack);
    tcase_add_test(TCase_pico_aodv_parse, tc_pico_aodv_parse);
    suite_add_tcase(s, TCase_pico_aodv_parse);
    tcase_add_test(TCase_pico_aodv_socket_callback, tc_pico_aodv_socket_callback);
    suite_add_tcase(s, TCase_pico_aodv_socket_callback);
    tcase_add_test(TCase_aodv_make_rreq, tc_aodv_make_rreq);
    suite_add_tcase(s, TCase_aodv_make_rreq);
    tcase_add_test(TCase_aodv_retrans_rreq, tc_aodv_retrans_rreq);
    suite_add_tcase(s, TCase_aodv_retrans_rreq);
    tcase_add_test(TCase_pico_aodv_expired, tc_pico_aodv_expired);
    suite_add_tcase(s, TCase_pico_aodv_expired);
    tcase_add_test(TCase_pico_aodv_collector, tc_pico_aodv_collector);
    suite_add_tcase(s, TCase_pico_aodv_collector);
    return s;
}

int main(void)
{
    int fails;
    Suite *s = pico_suite();
    SRunner *sr = srunner_create(s);
    srunner_run_all(sr, CK_NORMAL);
    fails = srunner_ntests_failed(sr);
    srunner_free(sr);
    return fails;
}
