#include <unistd.h>
#include <fcntl.h>

namespace panda { namespace unievent {

sock_t fd2sock (fd_t fd) {
    return fd;
}

fd_t sock2fd (sock_t sock) {
    return sock;
}

std::error_code sys_error (int syserr) {
    return std::error_code(syserr, std::generic_category());
}

std::error_code last_sys_error () {
    return sys_error(errno);
}

std::error_code last_sys_sock_error () {
    return last_sys_error();
}

fd_t file_dup (fd_t src) {
    auto src_flags = fcntl(src, F_GETFD, 0);
    if (src_flags == -1) throw last_sys_error();
    
    auto ret = ::dup(src);
    if (ret == -1) throw last_sys_error();
    
    if (src_flags & FD_CLOEXEC) {
        auto flags = fcntl(ret, F_GETFD, 0);
        if (flags == -1) {
            auto err = last_sys_error();
            close(ret).nevermind();
            throw err;
        }
        auto res = fcntl(ret, F_SETFD, flags | FD_CLOEXEC);
        if (res == -1) {
            auto err = last_sys_error();
            close(ret).nevermind();
            throw err;
        }
    }
    
    return ret;
}

sock_t sock_dup (sock_t sock) {
    return file_dup(sock);
}

excepted<sock_t, std::error_code> socket (int domain, int type, int protocol) {
    auto sock = ::socket(domain, type, protocol);
    if (sock == -1) return make_unexpected(last_sys_error());
    return sock;
}

excepted<sock_t, std::error_code> accept (sock_t srv, net::SockAddr* sa) {
    net::SockAddr stub;
    if (!sa) sa = &stub;
    socklen_t sz = sizeof(stub);
    auto sock = ::accept(srv, sa->get(), &sz);
    if (sock == -1) return make_unexpected(last_sys_error());
    return sock;
}

excepted<void, std::error_code> setsockopt (sock_t sock, int level, int optname, const void* optval, int optlen) {
    auto status = ::setsockopt(sock, level, optname, optval, (socklen_t)optlen);
    if (status != 0) return make_unexpected(last_sys_error());
    return {};
}

excepted<void, std::error_code> setblocking (sock_t sock, bool val) {
    int flags = fcntl(sock, F_GETFL, 0);
    if (flags == -1) return make_unexpected(last_sys_error());
    flags = val ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK);
    flags = fcntl(sock, F_SETFL, flags);
    if (flags != 0) return make_unexpected(last_sys_error());
    return {};
}

excepted<void, std::error_code> close (sock_t sock) {
    auto status = ::close(sock);
    if (status != 0) return make_unexpected(last_sys_error());
    return {};
}

excepted<net::SockAddr, std::error_code> getsockname (sock_t sock) {
    net::SockAddr ret;
    socklen_t sz = sizeof(ret);
    auto status = ::getsockname(sock, ret.get(), &sz);
    if (status != 0) return make_unexpected(last_sys_error());
    return ret;
}

excepted<net::SockAddr, std::error_code> getpeername (sock_t sock) {
    net::SockAddr ret;
    socklen_t sz = sizeof(ret);
    auto status = ::getpeername(sock, ret.get(), &sz);
    if (status != 0) return make_unexpected(last_sys_error());
    return ret;
}

}}