#include <net.h>
#include <net/serial.h>
#include <net_serial_private.h>
#include <transport.h>
#include <psx.h>
#include <psxsio.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdint.h>

static struct net_host
{
    enum net_role role;
    bool used, sending;
    struct transport_handle h;
    struct net_event ev;

    struct net_host_fifo
    {
        char buf[32];
        size_t pending, read;
    } in, out;
} host;

enum {TX_INT_ENABLE = 1 << 10};

int net_write(struct net_host *const h, const net_peer peer,
    const void *const buf, size_t n)
{
    return transport_send(&h->h, buf, n);
}

int net_close(struct net_host *const h)
{
    SIOStop();
    RemoveSIOHandler();
    h->used = false;
    return 0;
}

static void send_byte(struct net_host *const h)
{
    struct net_host_fifo *const f = &h->out;

    if (f->pending != f->read)
    {
        if (SIOCheckOutBuffer())
        {
            size_t new = f->read + 1;

            if (new >= sizeof f->buf)
                new = 0;

            h->sending = true;
            SIOSendByte(f->buf[f->read = new]);
        }
    }
    else
    {
        h->sending = false;
        SIO_CTRL &= ~TX_INT_ENABLE;
    }
}

int net_update(struct net_host *const h)
{
    return transport_update(&h->h);
}

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

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

static int on_write(const void *const buf, size_t n, void *const arg)
{
    const size_t orig = n;
    struct net_host *const h = arg;

    EnterCriticalSection();

    for (const uint8_t *b = buf; n; n--, b++)
    {
        struct net_host_fifo *const f = &h->out;
        size_t new = f->pending + 1;

        if (new >= sizeof f->buf)
            new = 0;

        if (new == f->read)
        {
            fprintf(stderr, "%s: out FIFO full\n", __func__);
            goto end;
        }

        f->buf[f->pending = new] = *b;
    }

    if (n != orig)
        SIO_CTRL |= TX_INT_ENABLE;

end:
    ExitCriticalSection();
    return orig - n;
}

static int on_read(void *const buf, const size_t n, void *const arg)
{
    size_t rem = n;
    struct net_host *const h = arg;
    struct net_host_fifo *const f = &h->in;

    EnterCriticalSection();

    for (uint8_t *b = buf; rem; rem--, b++)
    {
        if (f->read == f->pending)
            goto end;

        size_t new = f->read + 1;

        if (new >= sizeof f->buf)
            new = 0;

        *b = f->buf[f->read = new];
    }

end:
    ExitCriticalSection();
    return n - rem;
}

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

static void on_byte_received(struct net_host *const h, const char ch)
{
    struct net_host_fifo *const f = &h->in;
    size_t new = f->pending + 1;

    if (new >= sizeof f->buf)
        new = 0;

    f->buf[f->pending = new] = ch;
}

static void sio(void)
{
    struct net_host *const h = &host;

    if (SIOCheckInBuffer())
        on_byte_received(h, SIOReadByte());

    send_byte(h);
}

static void start_sio(const unsigned long baud)
{
    enum
    {
        RX_INT_MODE_1_BYTE = 1 << 8,
        RX_INT_ENABLE = 1 << 11
    };

    SIOStartEx(baud, SIO_DATA_LEN_8, SIO_PARITY_NONE, SIO_STOP_BIT_1);
    SetSIOHandler(sio);
    SIO_CTRL |= RX_INT_ENABLE | RX_INT_MODE_1_BYTE;
}

struct net_host *net_connect(const union net_connect *const c)
{
    struct net_host *const h = &host;

    if (h->used)
        goto failure;

    start_sio(c->serial.cfg.baud);

    *h = (const struct net_host)
    {
        .role = NET_ROLE_CLIENT,
        .used = true,
        .h =
        {
            .cfg = get_transport_cfg()
        }
    };

    if (transport_connect(&h->h))
        goto failure;

    return h;

failure:
    net_close(h);
    return NULL;
}

struct net_host *net_server(const union net_server *const c)
{
    struct net_host *const h = &host;

    if (h->used)
        return NULL;

    *h = (const struct net_host)
    {
        .role = NET_ROLE_SERVER,
        .used = true,
        .h =
        {
            .cfg = get_transport_cfg()
        }
    };

    start_sio(c->serial.cfg.baud);
    return h;
}

int net_init(void)
{
    return 0;
}

void net_deinit(void)
{
}

int net_set_event(struct net_host *const h,
    const struct net_event *const ev)
{
    h->ev = *ev;
    return 0;
}

bool net_available(const enum net_domain d)
{
    return d == NET_DOMAIN_SERIAL;
}

bool net_serial_unique(void)
{
    return true;
}

enum net_role net_role(const struct net_host *s)
{
    return s->role;
}
