#define _POSIX_C_SOURCE 200809L

#include "libweb/form.h"
#include "libweb/http.h"
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct pair
{
    char *key, *value;
};

struct form
{
    struct pair *pairs;
    size_t n;
};

void form_free(struct form *const f)
{
    if (!f)
        return;

    for (size_t i = 0; i < f->n; i++)
    {
        struct pair *const p = &f->pairs[i];

        free(p->key);
        free(p->value);
    }

    free(f->pairs);
    free(f);
}

const char *form_value(const struct form *const f, const char *const key)
{
    for (size_t i = 0; i < f->n; i++)
    {
        const struct pair *const p = &f->pairs[i];

        if (!strcmp(p->key, key))
            return p->value;
    }

    return NULL;
}

static char *alloc_form_data(const char *const s, const char **const end)
{
    const char *const next = strchr(s, '&');
    char *const data = next ? strndup(s, next - s) : strdup(s);

    if (!data)
    {
        fprintf(stderr, "%s: strndup/strdup(3): %s\n", __func__,
            strerror(errno));
        return NULL;
    }

    *end = next ? next + 1 : s + strlen(s);
    return data;
}

static int append(struct form *const forms, const char **const s)
{
    int ret = -1;
    const char *end;
    char *const data = alloc_form_data(*s, &end), *enckey = NULL,
        *encvalue = NULL, *key = NULL, *value = NULL;
    struct pair *p;

    if (!data)
    {
        fprintf(stderr, "%s: alloc_form_data failed\n", __func__);
        goto end;
    }

    const char *const sep = strchr(data, '=');

    if (!sep)
    {
        ret = 1;
        goto end;
    }
    else if (!data || !*(sep + 1))
    {
        ret = 1;
        goto end;
    }

    const size_t keylen = sep - data;

    if (!(enckey = strndup(data, keylen)))
    {
        fprintf(stderr, "%s: strndup(3) enckey: %s\n",
            __func__, strerror(errno));
        goto end;
    }
    else if (!(encvalue = strdup(sep + 1)))
    {
        fprintf(stderr, "%s: strdup(3) encvalue: %s\n",
            __func__, strerror(errno));
        goto end;
    }
    /* HTML input forms use '+' for whitespace, rather than %20. */
    else if ((ret = http_decode_url(enckey, true, &key)))
    {
        fprintf(stderr, "%s: http_decode_url enckey failed\n", __func__);
        goto end;
    }
    else if ((ret = http_decode_url(encvalue, true, &value)))
    {
        fprintf(stderr, "%s: http_decode_url encvalue failed\n", __func__);
        goto end;
    }
    else if (!(p = realloc(forms->pairs, (forms->n + 1) * sizeof *p)))
    {
        fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
        goto end;
    }

    forms->pairs = p;
    p[forms->n++] = (const struct pair)
    {
        .key = key,
        .value = value
    };

    *s = end;
    ret = 0;

end:
    if (ret)
    {
        free(key);
        free(value);
    }

    free(enckey);
    free(encvalue);
    free(data);
    return ret;
}

int form_foreach(const struct form *const f, const form_iter it,
    void *const user)
{
    for (size_t i = 0; i < f->n; i++)
    {
        const struct pair *const p = &f->pairs[i];
        const int ret = it(p->key, p->value, user);

        if (ret)
            return ret;
    }

    return 0;
}

int form_alloc(const char *data, struct form **const out)
{
    int ret = -1;
    struct form *const f = malloc(sizeof *f);

    if (!f)
    {
        fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
        goto failure;
    }

    *f = (const struct form){0};

    while (*data)
        if ((ret = append(f, &data)))
        {
            if (ret < 0)
                fprintf(stderr, "%s: append_form failed\n", __func__);

            goto failure;
        }

    *out = f;
    return 0;

failure:
    form_free(f);
    return ret;
}
