#ifndef LAB_CHANNEL_H
#define LAB_CHANNEL_H
#include <errno.h>
#include <poll.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <time.h>
#include <unistd.h>

static inline int channel_wait(int fd)
{
    struct timespec now;
    if (clock_gettime(CLOCK_MONOTONIC, &now) == -1)
        return -1;
    long long end = (long long)now.tv_sec * 1000 + now.tv_nsec / 1000000 + 15000;
    for (;;) {
        if (clock_gettime(CLOCK_MONOTONIC, &now) == -1)
            return -1;
        long long left = end - ((long long)now.tv_sec * 1000 + now.tv_nsec / 1000000);
        if (left <= 0) {
            errno = ETIMEDOUT;
            return -1;
        }
        struct pollfd event = {.fd = fd, .events = POLLIN};
        int result = poll(&event, 1, (int)left);
        if (result > 0)
            return 0;
        if (result == -1 && errno != EINTR)
            return -1;
    }
}

static inline int channel_open(const char *path, int server)
{
    struct sockaddr_un address = {.sun_family = AF_UNIX};
    if (strlen(path) >= sizeof(address.sun_path)) {
        errno = ENAMETOOLONG;
        return -1;
    }
    memcpy(address.sun_path, path, strlen(path) + 1);
    int fd = socket(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0);
    if (fd == -1)
        return -1;
    int bound = 0;
    if (server) {
        if (bind(fd, (struct sockaddr *)&address, sizeof(address)) == -1)
            goto fail;
        bound = 1;
        if (listen(fd, 1) == -1)
            goto fail;
        printf("READY %s\n", path);
        fflush(stdout);
    } else if (connect(fd, (struct sockaddr *)&address, sizeof(address)) == -1) {
        goto fail;
    }
    return fd;
fail:;
    int saved = errno;
    close(fd);
    if (bound)
        unlink(path);
    errno = saved;
    return -1;
}

static inline int channel_accept(int listener)
{
    if (channel_wait(listener) == -1)
        return -1;
    return accept4(listener, NULL, NULL, SOCK_CLOEXEC);
}

static inline int channel_send(int fd, const void *data, size_t length)
{
    ssize_t count;
    do {
        count = send(fd, data, length, MSG_NOSIGNAL);
    } while (count == -1 && errno == EINTR);
    if (count == (ssize_t)length)
        return 0;
    if (count >= 0)
        errno = EPROTO;
    return -1;
}

static inline int channel_receive(int fd, void *data, size_t length)
{
    if (channel_wait(fd) == -1)
        return -1;
    ssize_t count = recv(fd, data, length, MSG_TRUNC | MSG_DONTWAIT);
    if (count == (ssize_t)length)
        return 0;
    if (count >= 0)
        errno = EPROTO;
    return -1;
}

static inline int channel_finished(int fd)
{
    if (channel_wait(fd) == -1)
        return -1;
    char byte;
    ssize_t count = recv(fd, &byte, 1, MSG_DONTWAIT);
    if (count == 0)
        return 0;
    if (count > 0)
        errno = EPROTO;
    return -1;
}

static inline int channel_remove(int listener, const char *path)
{
    int result = 0;
    if (close(listener) == -1)
        result = -1;
    if (unlink(path) == -1)
        result = -1;
    return result;
}
#endif
