#include <packet.h>
#include <net.h>
#include <util.h>
#include <ctype.h>
#include <inttypes.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

typedef uint8_t packet_len, packet_ready;

int packet_send(const struct packet_ctx *const h, const net_peer peer,
    const union packet *const pkt)
{
    const uint8_t header = pkt->common.type;

    if (net_write(h->host, peer, &header, sizeof header))
    {
        fprintf(stderr, "%s: failed to write header\n", __func__);
        return -1;
    }

    const void *buf = NULL;
    size_t n = 0;
    bool send_len = false;
    packet_ready r;

    switch (pkt->common.type)
    {
        case PACKET_TYPE_NAME:
            buf = pkt->name.name;
            n = strlen(buf);
            send_len = true;
            break;

        case PACKET_TYPE_CHAT:
            buf = pkt->chat.msg;
            n = strlen(buf);
            send_len = true;
            break;

        case PACKET_TYPE_READY:
            r = pkt->ready.ready;
            buf = &r;
            n = sizeof r;
            break;

        default:
            fprintf(stderr, "%s: unknown packet type %d\n", __func__,
                pkt->common.type);
            return -1;
    }

    if (send_len)
    {
        const packet_len len = n;

        if (net_write(h->host, peer, &len, sizeof len))
        {
            fprintf(stderr, "%s: failed to write body length\n", __func__);
            return -1;
        }
    }

    if (net_write(h->host, peer, buf, n))
    {
        fprintf(stderr, "%s: failed to write body\n", __func__);
        return -1;
    }

    return 0;
}

static int read_header(struct packet_input *const in,
    const void **const buf, size_t *const n)
{
    const uint8_t header = *(const uint8_t *)*buf;

    if (header >= MAX_PACKET_TYPES)
    {
        fprintf(stderr, "%s: invalid packet type %#" PRIx8 "\n",
            __func__, header);
        return -1;
    }

    in->p.common.type = header;
    *buf = (const uint8_t *)*buf + sizeof header;
    *n -= sizeof header;
    return 0;
}

static int read_string_len(size_t *const len, const size_t maxlen,
    const void **const buf, size_t *const n)
{
    *len = *(const packet_len *)*buf;

    if (*len >= maxlen)
    {
        fprintf(stderr, "%s: maximum length exceeded (%zu/%zu)\n",
            __func__, *len, maxlen);
        return -1;
    }
    else if (!*len)
    {
        fprintf(stderr, "%s: invalid zero length\n", __func__);
        return -1;
    }

    *buf = (const uint8_t *)*buf + sizeof (packet_len);
    *n -= sizeof (packet_len);
    return 0;
}

static int read_string_body(struct packet_input *const in, char *const dst,
    const size_t len, const void **const buf, size_t *const n)
{
    const size_t rem = len > *n ? *n : len;

    for (const char *ch = *buf;
        ch - (const char *)*buf < rem;
        ch++)
    {
        if (!isprint((unsigned char)*ch))
        {
            fprintf(stderr, "%s: unexpected character %#hhx\n", __func__, *ch);
            return -1;
        }

        dst[in->body_i++] = *ch;
    }

    *buf = (const uint8_t *)*buf + rem;
    *n -= rem;

    if (in->body_i == len)
    {
        dst[in->body_i] = '\0';
        return 0;
    }

    return 1;
}

static int read_string(struct packet_input *const in, char *const dst,
    size_t *const len, const size_t maxlen, const void **const buf,
    size_t *const n)
{
    if (!*len && read_string_len(len, maxlen, buf, n))
        return -1;
    else if (!*n)
        return 1;

    return read_string_body(in, dst, *len, buf, n);
}

static int read_chat(struct packet_input *const in, const void **const buf,
    size_t *const n)
{
    struct packet_ctx_chat *const c = &in->data.chat;
    const int ret = read_string(in, c->msg, &c->len, sizeof c->msg - 1, buf, n);

    if (!ret)
        in->p.chat.msg = c->msg;

    return ret;
}

static int read_name(struct packet_input *const in, const void **const buf,
    size_t *const n)
{
    struct packet_ctx_name *const c = &in->data.name;
    const int ret = read_string(in, c->name, &c->len,
        sizeof c->name - 1,  buf, n);

    if (!ret)
        in->p.name.name = c->name;

    return ret;
}

static int read_ready(struct packet_input *const in, const void **const buf,
    size_t *const n)
{
    const packet_ready pr = *(const packet_ready *)*buf;

    if (pr > 1)
    {
        fprintf(stderr, "%s: invalid ready flag %#hhx\n", __func__, pr);
        return -1;
    }

    in->p.ready.ready = pr;
    *buf = (const uint8_t *)*buf + sizeof pr;
    *n -= sizeof pr;
    return 0;
}

static int read_body(struct packet_input *const in, const void **const buf,
    size_t *const n)
{
    static int (*const f[])(struct packet_input *, const void **, size_t *) =
    {
        [PACKET_TYPE_CHAT] = read_chat,
        [PACKET_TYPE_NAME] = read_name,
        [PACKET_TYPE_READY] = read_ready
    };

    return f[in->p.common.type](in, buf, n);
}

static void reset(struct packet_input *const in)
{
    *in = (const struct packet_input){0};
}

int packet_feed(const struct packet_ctx *const h, const net_peer p,
    struct packet_input *const in, const void *buf, size_t n)
{
    int res;

    switch (in->state)
    {
        case PACKET_STATE_HEADER:
            if ((res = read_header(in, &buf, &n)) < 0)
                goto failure;
            else if (!res)
                in->state = PACKET_STATE_BODY;

            if (!n)
                break;

            /* Fall through. */
        case PACKET_STATE_BODY:
            if ((res = read_body(in, &buf, &n)) < 0)
                goto failure;
            else if (!res)
            {
                h->cb(p, &in->p, h->arg);
                reset(in);
            }

            break;
    }

    return 0;

failure:
    reset(in);
    return -1;
}
