#include "utils.h"
#include <pico_ipv4.h>
#include <pico_ipv6.h>
#include <pico_socket.h>

/*** START UDP CLIENT ***/
/*
 * udpclient expects the following format: udpclient:dest_addr:sendto_port[:listen_port:datasize:loops:subloops]
 * dest_addr: IP address to send datagrams to
 * sendto_port: port number to send datagrams to
 * listen_port [OPTIONAL]: port number on which the udpclient listens
 * datasize [OPTIONAL]: size of the data given to the socket in one go
 * loops [OPTIONAL]: number of intervals in which data is send
 * subloops [OPTIONAL]: number of sends in one interval
 *
 * REMARK: once an optional parameter is given, all optional parameters need a value!
 *
 * f.e.: ./build/test/picoapp.elf --vde pic0:/tmp/pic0.ctl:10.40.0.2:255.255.255.0: -a udpclient:10.40.0.3:6667:6667:1400:100:10
 */

struct udpclient_pas *udpclient_pas;

static int exit_retry = 0;

static void request_exit_echo(pico_time now, void *arg)
{
    struct pico_socket *s = (struct pico_socket *)arg;
    char end[4] = "end";
    pico_socket_send(s, end, 4);
    if (exit_retry++ > 3) {
        if (!pico_timer_add(1000, deferred_exit, udpclient_pas)) {
            printf("Failed to start exit timer, exiting now\n");
            exit(1);
        }
    } else {
        if (!pico_timer_add(1000, request_exit_echo, s)) {
            printf("Failed to start request_exit_echo timer, sending request now\n");
            request_exit_echo((pico_time)0, NULL);
            exit(1);
        }
        printf("%s: requested exit of echo\n", __FUNCTION__);
    }
}

void udpclient_send(pico_time __attribute__((unused)) now, void __attribute__((unused))  *arg)
{
    struct pico_socket *s = udpclient_pas->s;
    char *buf = NULL;
    int i = 0, w = 0;
    static uint16_t loop = 0;

    if (++loop > udpclient_pas->loops) {
        if (!pico_timer_add(1000, request_exit_echo, s)) {
            printf("Failed to start request_exit_echo timer, sending request now\n");
            request_exit_echo((pico_time)0, NULL);
            exit(1);
        }
        return;
    } else {
        buf = calloc(1, udpclient_pas->datasize);
        if (!buf) {
            printf("%s: no memory available\n", __FUNCTION__);
            return;
        }

        memset(buf, '1', udpclient_pas->datasize);
        picoapp_dbg("%s: performing loop %u\n", __FUNCTION__, loop);
        for (i = 0; i < udpclient_pas->subloops; i++) {
            w =  pico_socket_send(s, buf, udpclient_pas->datasize);
            if (w <= 0)
                break;
        }
        picoapp_dbg("%s: written %u byte(s) in each of %u subloops\n", __FUNCTION__, udpclient_pas->datasize, i);
        free(buf);
    }

    if (!pico_timer_add(100, udpclient_send, NULL)) {
        printf("Failed to start send timer, sending exit request to echo and exiting\n");
        request_exit_echo((pico_time)0, NULL);
        exit(1);
    }
}

void cb_udpclient(uint16_t ev, struct pico_socket *s)
{
    char *recvbuf = NULL;
    int r = 0;

    if (ev & PICO_SOCK_EV_RD) {
        recvbuf = calloc(1, udpclient_pas->datasize);
        if (!recvbuf) {
            printf("%s: no memory available\n", __FUNCTION__);
            return;
        }

        do {
            r = pico_socket_recv(s, recvbuf, udpclient_pas->datasize);
        } while ( r > 0);
        free(recvbuf);
    }

    if (ev == PICO_SOCK_EV_ERR) {
        printf("Socket Error received. Bailing out.\n");
        free(udpclient_pas);
        exit(7);
    }
}

void app_udpclient(char *arg)
{
    char *daddr = NULL, *lport = NULL, *sport = NULL, *s_datasize = NULL, *s_loops = NULL, *s_subloops = NULL;
    char *nxt = arg;
    char sinaddr_any[40] = {
        0
    };
    uint16_t listen_port = 0;
    int ret = 0;

    udpclient_pas = calloc(1, sizeof(struct udpclient_pas));
    if (!udpclient_pas) {
        printf("%s: no memory available\n", __FUNCTION__);
        exit(255);
    }

    udpclient_pas->s = NULL;
    udpclient_pas->loops = 100;
    udpclient_pas->subloops = 10;
    udpclient_pas->datasize = 1400;

    /* start of argument parsing */
    if (nxt) {
        nxt = cpy_arg(&daddr, arg);
        if (daddr) {
            if (!IPV6_MODE)
                pico_string_to_ipv4(daddr, &udpclient_pas->dst.ip4.addr);

      #ifdef PICO_SUPPORT_IPV6
            else
                pico_string_to_ipv6(daddr, udpclient_pas->dst.ip6.addr);
      #endif
        } else {
            goto out;
        }
    } else {
        /* missing dest_addr */
        goto out;
    }

    if (nxt) {
        nxt = cpy_arg(&sport, nxt);
        if (sport && atoi(sport)) {
            udpclient_pas->sport = short_be(atoi(sport));
        } else {
            goto out;
        }
    } else {
        /* missing send_port */
        goto out;
    }

    if (nxt) {
        nxt = cpy_arg(&lport, nxt);
        if (lport && atoi(lport)) {
            listen_port = short_be(atoi(lport));
        } else {
            goto out;
        }
    } else {
        /* missing listen_port, use default */
        listen_port = 0;
    }

    if (nxt) {
        nxt = cpy_arg(&s_datasize, nxt);
        if (s_datasize && atoi(s_datasize)) {
            udpclient_pas->datasize = atoi(s_datasize);
        } else {
            goto out;
        }
    } else {
        /* missing datasize, incomplete optional parameters? -> exit */
        if (lport)
            goto out;
    }

    if (nxt) {
        nxt = cpy_arg(&s_loops, nxt);
        if (s_loops && atoi(s_loops)) {
            udpclient_pas->loops = atoi(s_loops);
        } else {
            goto out;
        }
    } else {
        /* missing loops, incomplete optional parameters? -> exit */
        if (s_datasize)
            goto out;
    }

    if (nxt) {
        nxt = cpy_arg(&s_subloops, nxt);
        if (s_subloops && atoi(s_subloops)) {
            udpclient_pas->subloops = atoi(s_subloops);
        } else {
            goto out;
        }
    } else {
        /* missing subloops, incomplete optional parameters? -> exit */
        if (s_loops)
            goto out;
    }

    /* end of argument parsing */

    if (!IPV6_MODE)
        pico_ipv4_to_string(sinaddr_any, inaddr_any.addr);

  #ifdef PICO_SUPPORT_IPV6
    else
        pico_ipv6_to_string(sinaddr_any, inaddr6_any.addr);
  #endif

    if (!IPV6_MODE)
        udpclient_pas->s = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_UDP, &cb_udpclient);
    else
        udpclient_pas->s = pico_socket_open(PICO_PROTO_IPV6, PICO_PROTO_UDP, &cb_udpclient);

    if (!udpclient_pas->s) {
        printf("%s: error opening socket: %s\n", __FUNCTION__, strerror(pico_err));
        free(udpclient_pas);
        exit(1);
    }

    if (!IPV6_MODE)
        ret = pico_socket_bind(udpclient_pas->s, &inaddr_any, &listen_port);
    else
        ret = pico_socket_bind(udpclient_pas->s, &inaddr6_any, &listen_port);

    if (ret < 0) {
        free(udpclient_pas);
        printf("%s: error binding socket to %s:%u: %s\n", __FUNCTION__, sinaddr_any, short_be(listen_port), strerror(pico_err));
        exit(1);
    }

    if (!IPV6_MODE)
        ret = pico_socket_connect(udpclient_pas->s, &udpclient_pas->dst.ip4, udpclient_pas->sport);
    else
        ret = pico_socket_connect(udpclient_pas->s, &udpclient_pas->dst.ip6, udpclient_pas->sport);

    if (ret < 0) {
        printf("%s: error connecting to [%s]:%u: %s\n", __FUNCTION__, daddr, short_be(udpclient_pas->sport), strerror(pico_err));
        free(udpclient_pas);
        exit(1);
    }

    printf("\n%s: UDP client launched. Sending packets of %u bytes in %u loops and %u subloops to %s:%u\n\n",
           __FUNCTION__, udpclient_pas->datasize, udpclient_pas->loops, udpclient_pas->subloops, daddr, short_be(udpclient_pas->sport));

    if (!pico_timer_add(100, udpclient_send, NULL)) {
        printf("Failed to start send timer, sending exit request to echo and exiting\n");
        request_exit_echo((pico_time)0, NULL);
        exit(1);
    }

    /* free strdups */
    if (daddr)
        free (daddr);

    if (lport)
        free (lport);

    if (sport)
        free (sport);

    if (s_datasize)
        free (s_datasize);

    if (s_loops)
        free (s_loops);

    if (s_subloops)
        free (s_subloops);

    return;

out:
    fprintf(stderr, "udpclient expects the following format: udpclient:dest_addr:dest_port[:listen_port:datasize:loops:subloops]\n");
    free(udpclient_pas);
    exit(255);
}
/*** END UDP CLIENT ***/
