#ifndef TILE_IO_DETAIL_EINTR_SAFE_H
#define TILE_IO_DETAIL_EINTR_SAFE_H

#pragma once

#include "tile/base/internal/meta.h"
#include "tile/base/likely.h"
#include <utility>

#include <errno.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <unistd.h>

namespace tile {
namespace io {
namespace detail {

template<typename F>
auto
EIntrSafeCallSlow(F &&f) -> invoke_result_t<F>
{
    while (true) {
        auto rc = std::forward<F>(f)();
        if (TILE_LIKELY(rc >= 0 || errno != EINTR)) { return rc; }
    }
}

template<class F>
inline auto
EIntrSafeCall(F &&f) -> invoke_result_t<F>
{
    auto rc = std::forward<F>(f)();
    if (TILE_LIKELY(rc >= 0 || errno != EINTR)) { return rc; }

    return EIntrSafeCallSlow(std::forward<F>(f));
}

inline ssize_t
EIntrSafeRead(int fd, void *buf, size_t count)
{
    return EIntrSafeCall([&] { return read(fd, buf, count); });
}

inline ssize_t
EIntrSafeWrite(int fd, const void *buf, size_t count)
{
    return EIntrSafeCall([&] { return write(fd, buf, count); });
}

inline ssize_t
EIntrSafeReadV(int fd, const iovec *iov, int iovcnt)
{
    // FIXME: Does `readv` really return `EINTR`?
    return EIntrSafeCall([&] { return readv(fd, iov, iovcnt); });
}

inline ssize_t
EIntrSafeWriteV(int fd, const iovec *iov, int iovcnt)
{
    // FIXME: Does `writev` really return `EINTR`?
    return EIntrSafeCall([&] { return writev(fd, iov, iovcnt); });
}

int EIntrSafeAccept(int sockfd, sockaddr *addr, socklen_t *addrlen);
// int EIntrSafeEpollWait(int epfd, epoll_event *events, int maxevents,
//                        int timeout);
ssize_t EIntrSafeRecvFrom(int sockfd, void *buf, size_t len, int flags, sockaddr *src_addr, socklen_t *addrlen);
ssize_t
EIntrSafeSendTo(int sockfd, const void *buf, size_t len, int flags, const sockaddr *dest_addr, socklen_t addrlen);
ssize_t EIntrSafeSendMsg(int sockfd, const struct msghdr *msg, int flags);

}// namespace detail
}// namespace io
}// namespace tile

#endif// TILE_IO_DETAIL_EINTR_SAFE_H
