#include "sock.hpp"
#include <arpa/inet.h>
#include <fcntl.h>


int sock_listen(in_port_t port) {
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1) {
        LOG_ERRNO("socket(SOCK_STREAM)");
        return -1;
    }

    int tmp = 1;
    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &tmp, sizeof(tmp)) == -1) {
        LOG_ERRNO("setsockopt(SO_REUSEADDR)");
        close(fd);
        return -1;
    }

    struct sockaddr_in addr = {};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    addr.sin_port = htons(port);

    if (bind(fd, (sockaddr*)&addr, sizeof(addr)) == -1) {
        LOG_ERRNO("bind(%d)", port);
        close(fd);
        return -1;
    }

    if (listen(fd, 10) == -1) {
        LOG_ERRNO("listen(%d)", port);
        close(fd);
        return -1;
    }

    LOG("listening...");
    return fd;
}


int sock_accept(int fd, bool nonblock) {
    sockaddr_in addr;
    socklen_t alen = sizeof(addr);
    int rv = accept4(fd, (sockaddr*)&addr, &alen, nonblock? SOCK_NONBLOCK: 0);
    if (rv == -1) {
        LOG_ERRNO("accept()");
    } else {
        LOG("accepted from %s:%d", inet_ntoa(addr.sin_addr), htons(addr.sin_port));
    }
    return rv;
}


int sock_connect(const sockaddr_in* addr, bool nonblock) {
    LOG("connecting to %s:%d ...", inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));

    int fd = socket(AF_INET, SOCK_STREAM|(nonblock? SOCK_NONBLOCK: 0), 0);
    if (fd == -1) {
        LOG_ERRNO("socket()");
        return -1;
    }

    if (connect(fd, (struct sockaddr*)addr, sizeof(*addr)) == -1) {
        if (errno != EINPROGRESS) {
            LOG_ERRNO("connect()");
            close(fd);
            return -1;
        }
    }

    return fd;
}


bool sock_nonblock(int fd, bool nonblock) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) {
        LOG_ERRNO("fcntl(GETFL)");
        return false;
    }
    flags = nonblock? (flags|O_NONBLOCK): (flags&~O_NONBLOCK);
    if (fcntl(fd, F_SETFL, flags) == -1) {
        LOG_ERRNO("fcntl(SETFL)");
        return false;
    }
    return true;
}