#include <transport.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct client
{
    bool connected, received;
    const char *expected;
    struct transport_handle h;

    struct io
    {
        FILE *in, *out;
    } io;
};

struct server
{
    struct transport_handle h;
    struct io io;
    const char *expected;
    bool received;
};

static int io_write(const void *const buf, const size_t n,
    const struct io *const io)
{
    FILE *const f = io->out;
    const size_t w = fwrite(buf, 1, n, f);

    return ferror(f) ? -1 : w;
}

static int io_read(void *const buf, const size_t n,
    const struct io *const io)
{
    FILE *const f = io->in;
    const size_t w = fread(buf, 1, n, f);

    return ferror(f) ? -1 : w;
}

static int client_write(const void *const buf, const size_t n, void *const arg)
{
    struct client *const c = arg;

    return io_write(buf, n, &c->io);
}

static int client_read(void *const buf, const size_t n, void *const arg)
{
    struct client *const c = arg;

    return io_read(buf, n, &c->io);
}

static void client_received(const struct transport_event *const ev,
    void *const arg)
{
    struct client *const c = arg;

    if (ev->common.type == TRANSPORT_EVENT_TYPE_CONNECT)
        c->connected = true;
    else if (ev->common.type == TRANSPORT_EVENT_TYPE_DATA)
    {
        const struct transport_event_data *const d = &ev->u.data;

        if (!strncmp(d->buf, c->expected, d->n))
            c->received = true;
    }
}

static int server_write(const void *const buf, const size_t n, void *const arg)
{
    struct server *const s = arg;

    return io_write(buf, n, &s->io);
}

static int server_read(void *const buf, const size_t n, void *const arg)
{
    struct server *const s = arg;

    return io_read(buf, n, &s->io);
}

static void server_received(const struct transport_event *const ev,
    void *const arg)
{
    struct server *const s = arg;

    if (ev->common.type == TRANSPORT_EVENT_TYPE_DATA)
    {
        const struct transport_event_data *const d = &ev->u.data;

        if (!strncmp(d->buf, s->expected, d->n))
            s->received = true;
    }
}

static int loop(struct server *const s, struct client *const c)
{
    if (transport_update(&s->h)
        || !freopen("client", "w+b", s->io.in))
        return -1;

    rewind(s->io.out);

    if (transport_update(&c->h)
        || !freopen("server", "w+b", c->io.in))
        return -1;

    rewind(c->io.out);
    return 0;
}

int main(void)
{
    int ret = EXIT_FAILURE;
    FILE *const cf = fopen("client", "w+b"),
        *const sf = fopen("server", "w+b");

    if (!cf)
    {
        perror("fopen(3) client");
        goto end;
    }
    else if (!sf)
    {
        perror("fopen(3) server");
        goto end;
    }

    struct client c =
    {
        .h =
        {
            .cfg =
            {
                .arg = &c,
                .read = client_read,
                .write = client_write,
                .received = client_received
            }
        },

        .io =
        {
            .in = sf,
            .out = cf
        },

        .expected = "hi there!"
    };

    struct server s =
    {
        .h =
        {
            .cfg =
            {
                .arg = &s,
                .read = server_read,
                .write = server_write,
                .received = server_received,
            }
        },

        .io =
        {
            .in = cf,
            .out = sf
        },

        .expected = "hello"
    };

    if (transport_connect(&c.h))
        goto end;

    while (!c.connected)
        if (loop(&s, &c))
            goto end;

    if (transport_send(&c.h, s.expected, strlen(s.expected)))
        goto end;

    while (!s.received)
        if (loop(&s, &c))
            goto end;

    if (transport_send(&s.h, c.expected, strlen(c.expected)))
        goto end;

    while (!c.received)
        if (loop(&s, &c))
            goto end;

    ret = EXIT_SUCCESS;

end:
    if (cf)
        fclose(cf);

    if (sf)
        fclose(sf);

    return ret;
}
