#include <net.h>
#include <net_private.h>
#include <windows.h>
#include <ctype.h>
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct net_host_domain
{
    HANDLE com;
    enum net_role role;
    struct transport_handle h;
    struct net_event ev;

    struct req
    {
        const void *buf;
        size_t n;
        OVERLAPPED ov;

        enum req_type
        {
            REQ_TYPE_READ,
            REQ_TYPE_WRITE
        } type;
    } *reqs;

    size_t n_reqs;
};

int net_write_serial(struct net_host_domain *const h, const net_peer p,
    const void *const buf, const size_t n)
{
    return transport_send(&h->h, buf, n);
}

int net_close_serial(struct net_host_domain *const h)
{
    if (h)
    {
        if (h->com != INVALID_HANDLE_VALUE)
            CloseHandle(h->com);

        if (h->reqs)
            for (size_t i = 0; i < h->n_reqs; i++)
            {
                struct req *const r = &h->reqs[i];

                if (r->ov.hEvent)
                    CloseHandle(r->ov.hEvent);
            }

        free(h->reqs);
    }

    free(h);
    return 0;
}

int net_update_serial(struct net_host_domain *const h)
{
    return transport_update(&h->h);
}

static void on_received(const struct transport_event *const ev,
    void *const arg)
{
    struct net_host_domain *const h = arg;

    net_serial_on_received(ev, &h->ev);
}

static struct req *find_req(struct net_host_domain *const h,
    const void *const buf, const size_t n, const enum req_type t)
{
    for (size_t i = 0; i < h->n_reqs; i++)
    {
        struct req *const r = &h->reqs[i];

        if (r->buf == buf && r->n == n && r->type == t)
            return r;
    }

    return NULL;
}

static int free_req(struct net_host_domain *const h, struct req *const r)
{
    for (size_t i = 0; i < h->n_reqs; i++)
        if (&h->reqs[i] == r)
        {
            const HANDLE ev = r->ov.hEvent;

            if (ev)
                CloseHandle(ev);

            for (size_t j = i + 1; j < h->n_reqs; j++)
                h->reqs[j - 1] = h->reqs[j];

            const size_t n = h->n_reqs - 1;

            if (n)
            {
                struct req *const r = realloc(h->reqs, n * sizeof *h->reqs);

                if (!r)
                {
                    fprintf(stderr, "%s: realloc(3) failed: %s\n",
                        __func__, strerror(errno));
                    return -1;
                }

                h->reqs = r;
            }
            else
            {
                free(h->reqs);
                h->reqs = NULL;
            }

            h->n_reqs = n;
            return 0;
        }

    return -1;
}

static struct req *alloc_req(struct net_host_domain *const h,
    const void *const buf, const size_t n, const enum req_type t)
{
    struct req *const reqs = realloc(h->reqs,
        (h->n_reqs + 1) * sizeof *h->reqs);

    if (!reqs)
    {
        fprintf(stderr, "%s: realloc(3) failed: %s\n",
            __func__, strerror(errno));
        return NULL;
    }

    h->reqs = reqs;

    struct req *const r = &h->reqs[h->n_reqs++];

    *r = (const struct req)
    {
        .buf = buf,
        .n = n,
        .type = t,
        .ov =
        {
            .hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)
        }
    };

    if (!r->ov.hEvent)
    {
        fprintf(stderr, "%s: CreateEvent failed: %#lx\n", __func__,
            GetLastError());
        free_req(h, r);
        return NULL;
    }

    return r;
}

static int get_result(struct net_host_domain *const h, struct req *const r)
{
    DWORD res;

    if (GetOverlappedResult(h->com, &r->ov, &res, FALSE) == FALSE)
    {
        const DWORD error = GetLastError();

        if (error != ERROR_IO_INCOMPLETE)
        {
            fprintf(stderr, "%s: GetOverlappedResult failed: %#lx\n",
                __func__, error);
            return -1;
        }
        else
            return 0;
    }
    else if (ResetEvent(r->ov.hEvent) == FALSE)
    {
        fprintf(stderr, "%s: ResetEvent failed: %#lx\n", __func__,
            GetLastError());
        return -1;
    }
    else if (free_req(h, r))
    {
        fprintf(stderr, "%s: free_req failed\n", __func__);
        return -1;
    }

    return res;
}

static int on_read(void *const buf, const size_t n, void *const arg)
{
    const enum req_type t = REQ_TYPE_READ;
    struct net_host_domain *const h = arg;
    struct req *r = find_req(h, buf, n, t);

    if (!r)
    {
        DWORD res;

        if (!(r = alloc_req(h, buf, n, t)))
        {
            fprintf(stderr, "%s: alloc_req failed\n", __func__);
            goto failure;
        }
        else if (ReadFile(h->com, buf, n, &res, &r->ov) == FALSE
            && GetLastError() != ERROR_IO_PENDING)
        {
            fprintf(stderr, "%s: ReadFile failed: %#lx\n",
                __func__, GetLastError());
            goto failure;
        }
    }

    return get_result(h, r);

failure:
    if (r)
        free_req(h, r);

    return -1;
}

static int on_write(const void *const buf, const size_t n, void *const arg)
{
    const enum req_type t = REQ_TYPE_WRITE;
    struct net_host_domain *const h = arg;
    struct req *r = find_req(h, buf, n, t);

    if (!r)
    {
        DWORD res;

        if (!(r = alloc_req(h, buf, n, t)))
        {
            fprintf(stderr, "%s: alloc_req failed\n", __func__);
            goto failure;
        }
        else if (WriteFile(h->com, buf, n, &res, &r->ov) == FALSE
            && GetLastError() != ERROR_IO_PENDING)
        {
            fprintf(stderr, "%s: WriteFile failed: %#lx\n",
                __func__, GetLastError());
            goto failure;
        }

        printf("%s: outgoing packet, buf: %p, n: %u: [", __func__, buf, (unsigned)n);

        for (size_t i = 0; i < n; i++)
        {
            const char b = ((const char *)buf)[i];
            printf("%02hhx", b);

            if (isprint((unsigned char)b))
                printf(" (%c)", b);

            if (i + 1 < n)
                printf(", ");
        }

        printf("]\n");
    }

    return get_result(h, r);

failure:
    if (r)
        free_req(h, r);

    return -1;
}

static struct transport_cfg get_transport_cfg(void *const arg)
{
    return (const struct transport_cfg)
    {
        .arg = arg,
        .received = on_received,
        .read = on_read,
        .write = on_write
    };
}

static HANDLE init_com(const char *const dev)
{
    static const char *const prefix = "\\\\.\\";

    /* snprintf(NULL, 0, ...) could have been used to determine the
     * total length of the string. However, this is a C99 feature that
     * must be supported by the underlying libc, which is not the case
     * for Win9x, where only ANSI C is supported. */
    char *const fulldev = calloc(1, strlen(prefix) + strlen(dev) + 1);
    HANDLE ret = INVALID_HANDLE_VALUE;

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

    strcat(fulldev, prefix);
    strcat(fulldev, dev);

    ret = CreateFile(
        fulldev,
        GENERIC_READ | GENERIC_WRITE,
        0, /* No sharing. */
        NULL, /* No security. */
        OPEN_EXISTING, /* Open existing port only. */
        FILE_FLAG_OVERLAPPED, /* Asynchronous I/O. */
        NULL /* NULL for COM devices. */);

    if (ret == INVALID_HANDLE_VALUE)
        fprintf(stderr, "%s: CreateFile failed: %#lx\n", __func__,
            GetLastError());

end:
    free(fulldev);
    return ret;
}

static int config_baud(const HANDLE com, const unsigned long baud)
{
    DCB dcb =
    {
        .DCBlength = sizeof dcb,
        .BaudRate = baud,
        .ByteSize = 8,
        .StopBits = ONESTOPBIT,
        .Parity = NOPARITY,
        .fBinary = TRUE
    };

    if (SetCommState(com, &dcb) == FALSE)
    {
        fprintf(stderr, "%s: SetCommState failed: %#lx\n",
            __func__, GetLastError());
        return -1;
    }

    return 0;
}

struct net_host_domain *net_connect_serial(const union net_connect *const c)
{
    struct net_host_domain *const h = malloc(sizeof *h);
    const struct net_serial_cfg *const cfg = &c->serial.cfg;

    if (!h)
        goto failure;

    *h = (const struct net_host_domain)
    {
        .role = NET_ROLE_CLIENT,
        .com = init_com(cfg->dev),
        .h =
        {
            .cfg = get_transport_cfg(h)
        }
    };

    if (h->com == INVALID_HANDLE_VALUE)
    {
        fprintf(stderr, "%s: init_com failed\n", __func__);
        goto failure;
    }
    else if (config_baud(h->com, cfg->baud))
    {
        fprintf(stderr, "%s: config_com_baud failed\n", __func__);
        goto failure;
    }
    else if (transport_connect(&h->h))
    {
        fprintf(stderr, "%s: transport_connect failed\n", __func__);
        goto failure;
    }

    return h;

failure:
    net_close_serial(h);
    return NULL;
}

struct net_host_domain *net_server_serial(const union net_server *const s)
{
    struct net_host_domain *const h = malloc(sizeof *h);
    const struct net_serial_cfg *const cfg = &s->serial.cfg;

    if (!h)
        goto failure;

    *h = (const struct net_host_domain)
    {
        .role = NET_ROLE_SERVER,
        .com = init_com(cfg->dev),
        .h =
        {
            .cfg = get_transport_cfg(h)
        }
    };

    if (h->com == INVALID_HANDLE_VALUE)
    {
        fprintf(stderr, "%s: init_com failed\n", __func__);
        goto failure;
    }
    else if (config_baud(h->com, cfg->baud))
    {
        fprintf(stderr, "%s: config_com_baud failed\n", __func__);
        goto failure;
    }

    return h;

failure:
    net_close_serial(h);
    return NULL;
}

int net_init_serial(void)
{
    return 0;
}

void net_deinit_serial(void)
{
}

int net_set_event_serial(struct net_host_domain *const d,
    const struct net_event *const ev)
{
    d->ev = *ev;
    return 0;
}

enum net_role net_role_serial(const struct net_host_domain *const h)
{
    return h->role;
}

bool net_serial_unique(void)
{
    return false;
}
