#include "libweb/server.h"
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <poll.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct server
{
    int fd, cfds[2], lastfd;

    struct server_client
    {
        int fd;
        bool write;
        struct server_client *prev, *next;
    } *c;
};

int server_close(struct server *const s)
{
    int ret = 0;

    if (!s)
        return 0;
    else if (s->fd >= 0)
        ret = close(s->fd);

    free(s);
    return ret;
}

int server_client_close(struct server *const s, struct server_client *const c)
{
    int ret = 0;

    for (struct server_client *ref = s->c; ref; ref = ref->next)
    {
        if (c == ref)
        {
            struct server_client *const next = ref->next;

            if ((ret = close(c->fd)))
            {
                fprintf(stderr, "%s: close(2): %s\n",
                    __func__, strerror(errno));
            }
            else if (ref->prev)
                ref->prev->next = next;
            else
                s->c = next;

            if (next)
                next->prev = ref->prev;

            free(ref);
            break;
        }
    }

    return ret;
}

int server_read(void *const buf, const size_t n, struct server_client *const c)
{
    const ssize_t r = read(c->fd, buf, n);

    if (r < 0)
        fprintf(stderr, "%s: read(2): %s\n", __func__, strerror(errno));

    return r;
}

int server_write(const void *const buf, const size_t n,
    struct server_client *const c)
{
    const ssize_t w = write(c->fd, buf, n);

    if (w < 0)
        fprintf(stderr, "%s: write(2): %s\n", __func__, strerror(errno));

    return w;
}

static struct server_client *alloc_client(struct server *const s)
{
    struct sockaddr_in addr;
    socklen_t sz = sizeof addr;
    struct server_client *c = NULL;
    const int fd = accept(s->fd, (struct sockaddr *)&addr, &sz);

    if (fd < 0)
    {
        fprintf(stderr, "%s: accept(2): %s\n",
            __func__, strerror(errno));
        goto failure;
    }

    const int flags = fcntl(fd, F_GETFL);

    if (flags < 0)
    {
        fprintf(stderr, "%s: fcntl(2) F_GETFL: %s\n",
            __func__, strerror(errno));
        goto failure;
    }
    else if (fcntl(fd, F_SETFL, flags | O_NONBLOCK))
    {
        fprintf(stderr, "%s: fcntl(2) F_SETFL: %s\n",
            __func__, strerror(errno));
        goto failure;
    }
    else if (!(c = malloc(sizeof *c)))
    {
        fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
        goto failure;
    }

    *c = (const struct server_client)
    {
        .fd = fd
    };

    if (!s->c)
        s->c = c;
    else
        for (struct server_client *ref = s->c; ref; ref = ref->next)
            if (!ref->next)
            {
                ref->next = c;
                c->prev = ref;
                break;
            }

    return c;

failure:

    if (fd >= 0 && close(fd))
        fprintf(stderr, "%s: close(2): %s\n", __func__, strerror(errno));

    free(c);
    return NULL;
}

void server_client_write_pending(struct server_client *const c,
    const bool write)
{
    c->write = write;
}

static size_t get_clients(const struct server *const s)
{
    size_t ret = 0;

    for (const struct server_client *c = s->c; c; c = c->next)
        ret++;

    return ret;
}

static int check_exit(const int fd)
{
    char exit;
    const ssize_t n = read(fd, &exit, sizeof exit);

    if (n <= 0)
    {
        fprintf(stderr, "%s: read(2): %s\n", __func__, strerror(errno));
        return -1;
    }

    return 0;
}

struct server_client *server_poll(struct server *const s, bool *const io,
    bool *const exit)
{
    enum {SERVER, PIPE, CLIENTS};
    struct server_client *ret = NULL;
    const size_t n_clients = get_clients(s);
    const nfds_t n = n_clients + CLIENTS;
    struct pollfd *const fds = malloc(n * sizeof *fds);

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

    struct pollfd *const sfd = &fds[SERVER], *const pfd = &fds[PIPE];

    *io = *exit = false;
    *sfd = (const struct pollfd)
    {
        .fd = s->fd,
        .events = POLLIN
    };

    *pfd = (const struct pollfd)
    {
        .fd = s->cfds[0],
        .events = POLLIN
    };

    for (struct {const struct server_client *c; size_t j;}
        _ = {.c = s->c, .j = CLIENTS}; _.c; _.c = _.c->next, _.j++)
    {
        struct pollfd *const p = &fds[_.j];
        const int fd = _.c->fd;

        *p = (const struct pollfd)
        {
            .fd = fd,
            .events = POLLIN
        };

        if (_.c->write)
            p->events |= POLLOUT;
    }

    int res;

again:

    res = poll(fds, n, -1);

    if (res < 0)
    {
        switch (errno)
        {
            case EAGAIN:
                /* Fall through. */
            case EINTR:
                goto again;

            default:
                fprintf(stderr, "%s: poll(2): %s\n", __func__, strerror(errno));
                break;
        }

        goto end;
    }
    else if (!res)
    {
        fprintf(stderr, "%s: poll(2) returned zero\n", __func__);
        goto end;
    }
    else if (pfd->revents)
    {
        if (check_exit(pfd->fd))
            fprintf(stderr, "%s: check_exit failed\n", __func__);
        else
            *exit = true;

        goto end;
    }
    else if (sfd->revents)
    {
        ret = alloc_client(s);
        goto end;
    }

    struct server_client *sel = NULL;

    for (struct {struct server_client *c; size_t j;}
        _ = {.c = s->c, .j = CLIENTS}; _.c; _.c = _.c->next, _.j++)
    {
        const struct pollfd *const p = &fds[_.j];

        if (p->revents)
        {
            sel = _.c;

            if (n_clients == 1 || p->fd != s->lastfd)
                break;
        }
    }

    if (sel)
    {
        *io = true;
        ret = sel;
        s->lastfd = sel->fd;
        goto end;
    }

    fprintf(stderr, "%s: unlisted fd\n", __func__);

end:
    free(fds);
    return ret;
}

struct server *server_init(const unsigned short port,
    unsigned short *const outport, const unsigned ubacklog)
{
    struct server *const s = malloc(sizeof *s);
    int fds[2] = {-1, -1}, fd = -1;
    /* Ensure default value if not set. */
    enum {QUEUE_LEN = 10};
    const unsigned backlog = ubacklog ? ubacklog : QUEUE_LEN;

    if (!s)
    {
        fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
        goto failure;
    }
    else if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        fprintf(stderr, "%s: socket(2): %s\n", __func__, strerror(errno));
        goto failure;
    }
    else if (pipe(fds))
    {
        fprintf(stderr, "%s: socketpair(2): %s\n", __func__, strerror(errno));
        goto failure;
    }

    const struct sockaddr_in addr =
    {
        .sin_family = AF_INET,
        .sin_port = htons(port)
    };

    if (bind(fd, (const struct sockaddr *)&addr, sizeof addr))
    {
        fprintf(stderr, "%s: bind(2): %s\n", __func__, strerror(errno));
        goto failure;
    }
    else if (backlog > INT_MAX)
    {
        fprintf(stderr, "%s: backlog (%u) exceeds maximum (%d)\n",
            __func__, backlog, INT_MAX);
        goto failure;
    }
    else if (listen(fd, backlog))
    {
        fprintf(stderr, "%s: listen(2): %s\n", __func__, strerror(errno));
        goto failure;
    }

    struct sockaddr_in in;
    socklen_t sz = sizeof in;

    if (getsockname(fd, (struct sockaddr *)&in, &sz))
    {
        fprintf(stderr, "%s: getsockname(2): %s\n", __func__, strerror(errno));
        goto failure;
    }

    *s = (const struct server)
    {
        .fd = fd,
        .cfds = {
            [0] = fds[0],
            [1] = fds[1]
        }
    };

    if (outport)
        *outport = ntohs(in.sin_port);

    return s;

failure:;

    const int a[] = {fd, fds[0], fds[1]};

    for (size_t i = 0; i < sizeof a / sizeof *a; i++)
    {
        const int fd = a[i];

        if (fd >= 0 && close(fd))
            fprintf(stderr, "%s: close(2)[%zu]: %s\n",
                __func__, i, strerror(errno));
    }

    return NULL;
}

int server_notify_close(struct server *const s)
{
    char exit = 0;

    return write(s->cfds[1], &exit, sizeof exit) <= 0 ? -1 : 0;
}
