#include <net.h>
#include <net_private.h>
#include <enet/enet.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

struct net_host_domain
{
    enum net_role role;
    ENetHost *host;
    struct net_event ev;
};

enum
{
    CHANNEL,
    MAX_CHANNELS
};

struct net_host_domain *net_connect_ipv4(const union net_connect *const c)
{
    struct net_host_domain *const h = malloc(sizeof *h);

    if (!h)
        goto failure;

    *h = (const struct net_host_domain)
    {
        .ev = c->common.ev,
        .host = enet_host_create(NULL, 1, MAX_CHANNELS, 0, 0)
    };

    if (!h->host)
    {
        fprintf(stderr, "%s: enet_host_create failed\n", __func__);
        goto failure;
    }

    ENetAddress addr;

    if (enet_address_set_host(&addr, c->ipv4.addr))
    {
        fprintf(stderr, "%s: enet_address_set_host failed\n", __func__);
        goto failure;
    }

    addr.port = c->ipv4.port;

    if (!enet_host_connect(h->host, &addr, MAX_CHANNELS, 0))
    {
        fprintf(stderr, "%s: enet_host_connect failed\n", __func__);
        goto failure;
    }

    h->role = NET_ROLE_CLIENT;
    return h;

failure:

    if (h && h->host)
        enet_host_destroy(h->host);

    free(h);
    return NULL;
}

int net_write_ipv4(struct net_host_domain *const h, const net_peer p,
    const void *const buf, const size_t n)
{
    ENetPacket *const pk = enet_packet_create(buf, n, ENET_PACKET_FLAG_RELIABLE);

    if (!p)
    {
        fprintf(stderr, "%s: enet_packet_create failed\n", __func__);
        return -1;
    }
    else if (enet_peer_send(p, CHANNEL, pk))
    {
        fprintf(stderr, "%s: enet_peer_send failed\n", __func__);
        return -1;
    }

    return 0;
}

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

int net_close_ipv4(struct net_host_domain *const h)
{
    if (h && h->host)
        enet_host_destroy(h->host);

    free(h);
    return 0;
}

int net_update_ipv4(struct net_host_domain *const h)
{
    int res;
    ENetEvent ev;

    while ((res = enet_host_service(h->host, &ev, 0)) > 0)
    {
        switch (ev.type)
        {
            case ENET_EVENT_TYPE_CONNECT:
                if (h->ev.connected)
                    h->ev.connected(ev.peer, h->ev.arg);

                break;

            case ENET_EVENT_TYPE_DISCONNECT:
                if (h->ev.disconnected)
                    h->ev.disconnected(ev.peer, h->ev.arg);

                break;

            case ENET_EVENT_TYPE_RECEIVE:
                if (h->ev.received)
                {
                    const enet_uint8 *const buf = ev.packet->data;
                    const size_t n = ev.packet->dataLength;

                    if (buf && n)
                        h->ev.received(ev.peer, buf, n, h->ev.arg);
                }
                break;

            case ENET_EVENT_TYPE_NONE:
                break;
        }
    }

    if (res < 0)
    {
        fprintf(stderr, "%s: enet_host_service failed\n", __func__);
        return res;
    }

    return 0;
}

struct net_host_domain *net_server_ipv4(const union net_server *const srv)
{
    struct net_host_domain *const h = malloc(sizeof *h);

    if (!h)
        goto failure;

    const ENetAddress addr =
    {
        .port = srv->ipv4.port
    };

    *h = (const struct net_host_domain)
    {
        .host = enet_host_create(&addr, srv->common.max_players,
            MAX_CHANNELS, 0, 0)
    };

    if (!h->host)
    {
        fprintf(stderr, "%s: enet_host_create failed\n", __func__);
        goto failure;
    }

    h->role = NET_ROLE_SERVER;
    return h;

failure:
    free(h);
    return NULL;
}

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

int net_init_ipv4(void)
{
    return enet_initialize();
}

void net_deinit_ipv4(void)
{
    enet_deinitialize();
}
