/* Tetrinet for Linux, by Andrew Church <achurch@achurch.org>
 * This program is public domain.
 *
 * Socket routines.
 */

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#include <string.h>

#include "client.h"
#include "event.h"
#include "sockets.h"
#include "tetrinet.h"

/*************************************************************************/

static int lastchar = EOF;

int sgetc(int s)
{
    int c;
    char ch;

    if (lastchar != EOF) {
        c = lastchar;
        lastchar = EOF;
        return c;
    }
    if (read(s, &ch, 1) != 1)
        return EOF;
    c = ch & 0xFF;
    return c;
}

int sungetc(int c, int s)
{
    return lastchar = c;
}

/*************************************************************************/

/* Read a string, stopping with (and discarding) 0xFF as line terminator.
 * If connection was broken, return NULL.
 */

char *sgets(char *buf, int len, int s)
{
    int c;
    unsigned char *ptr = (unsigned char *)buf;

    if (len == 0)
        return NULL;
    c = sgetc(s);
    while (--len && (*ptr++ = c) != 0xFF && (c = sgetc(s)) >= 0) ;
    if (c < 0)
        return NULL;
    if (c == 0xFF)
        ptr--;
    *ptr = 0;
    return buf;
}

/*************************************************************************/

/* Adds a 0xFF line terminator. */

int sputs(const char *str, int s)
{
    unsigned char c = 0xFF;
    int n = 0;

    if (*str != 0) {
        n = write(s, str, strlen(str));
        if (n <= 0)
            return n;
    }
    if (write(s, &c, 1) <= 0)
        return n;
    return n + 1;
}

/*************************************************************************/

/* Adds a 0xFF line terminator. */

int sockprintf(int s, const char *fmt, ...)
{
    va_list args;
    char buf[1024];

    va_start(args, fmt);
    vsnprintf(buf, sizeof(buf), fmt, args);
    return sputs(buf, s);
}

/*************************************************************************/
/*************************************************************************/

void conn_task(void *arg)
{
    event_t event;
    fd_set fds;
    struct timeval tv;

    while (sock >= 0) {
        FD_ZERO(&fds);
        FD_SET(sock, &fds);
        tv.tv_sec = 0;
        tv.tv_usec = 10 * 1000;
        while (select(sock + 1, &fds, NULL, NULL, &tv) < 0) {
            if (errno != EINTR) {
                return;
            }
        }
        if (FD_ISSET(sock, &fds)) {
            event.type = EVENT_TYPE_SOCKET;
            event.socket.eof = false;
            if (!sgets(event.socket.buf, sizeof(event.socket.buf), sock)) {
                event.socket.eof = true;
            }
            xQueueSend(event_queue, &event, portMAX_DELAY);
        }
    }
}

/*************************************************************************/

int conn(const char *host, int port, char ipbuf[4])
{
#ifdef HAVE_IPV6
    char hbuf[NI_MAXHOST];
    struct addrinfo hints, *res, *res0;
    char service[11];
#else
    struct hostent *hp;
    struct sockaddr_in sa;
#endif
    int sock = -1;

#ifdef HAVE_IPV6
    snprintf(service, sizeof(service), "%d", port);
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    if (getaddrinfo(host, service, &hints, &res0))
        return -1;
    for (res = res0; res; res = res->ai_next) {
        int errno_save;
        sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
        if (sock < 0)
            continue;
        getnameinfo(res->ai_addr, res->ai_addrlen, hbuf, sizeof(hbuf),
                    NULL, 0, 0);
        if (connect(sock, res->ai_addr, res->ai_addrlen) == 0) {
            if (ipbuf) {
                if (res->ai_family == AF_INET6) {
                    struct sockaddr_in6 *sin6 =
                        (struct sockaddr_in6 *)(res->ai_addr);
                    memcpy(ipbuf, (char *)(&sin6->sin6_addr) + 12, 4);
                } else {
                    struct sockaddr_in *sin =
                        (struct sockaddr_in *)(res->ai_addr);
                    memcpy(ipbuf, &sin->sin_addr, 4);
                }
            }
            break;
        }
        errno_save = errno;
        close(sock);
        sock = -1;
        errno = errno_save;
    }
    freeaddrinfo(res0);
#else                           /* !HAVE_IPV6 */
    memset(&sa, 0, sizeof(sa));
    if (!(hp = gethostbyname(host)))
        return -1;
    memcpy((char *)&sa.sin_addr, hp->h_addr, hp->h_length);
    sa.sin_family = hp->h_addrtype;
    sa.sin_port = htons((unsigned short)port);
    if ((sock = socket(sa.sin_family, SOCK_STREAM, 0)) < 0)
        return -1;
    if (connect(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
        int errno_save = errno;
        close(sock);
        sock = -1;
        errno = errno_save;
        return -1;
    }
#endif

    xTaskCreate(conn_task, "conn", 3072, NULL, 5, NULL);

    return sock;
}

/*************************************************************************/

void disconn(int s)
{
    shutdown(s, 2);
    close(s);
}

/*************************************************************************/
