module std::net::os @if(env::POSIX && SUPPORTS_INET);
import std::io, libc;

const int F_GETFL = 3;
const int F_SETFL = 4;

typedef NativeSocket = inline Fd;

struct Posix_pollfd
{
	CInt fd;
	CUShort events;
	CUShort revents;
}

alias Posix_nfds_t = CUInt;

extern fn CInt connect(NativeSocket socket, SockAddrPtr address, Socklen_t address_len);
extern fn NativeSocket socket(AIFamily af, AISockType type, AIProtocol ip_protocol);
extern fn int fcntl(NativeSocket socket, int cmd, ...);
extern fn CInt bind(NativeSocket socket, SockAddrPtr address, Socklen_t address_len);
extern fn CInt listen(NativeSocket socket, CInt backlog);
extern fn NativeSocket accept(NativeSocket socket, SockAddrPtr address, Socklen_t* address_len);
extern fn CInt poll(Posix_pollfd* fds, Posix_nfds_t nfds, CInt timeout);
extern fn CInt socketpair(AIFamily domain, AISockType type, CInt protocol, NativeSocket[2]* sv);

const CUShort POLLIN      = 0x0001;
const CUShort POLLPRI     = 0x0002;
const CUShort POLLOUT     = 0x0004;
const CUShort POLLERR     = 0x0008;
const CUShort POLLHUP     = 0x0010;
const CUShort POLLNVAL    = 0x0020;

fn fault convert_error(Errno error)
{
	switch (error)
	{
		case errno::EACCES: return io::NO_PERMISSION;
		case errno::EADDRINUSE: return net::ADDRESS_IN_USE;
		case errno::EALREADY: return net::CONNECTION_ALREADY_IN_PROGRESS;
		case errno::EBADF: return net::BAD_SOCKET_DESCRIPTOR;
		case errno::ECONNREFUSED: return net::CONNECTION_REFUSED;
		case errno::ECONNRESET: return net::CONNECTION_RESET;
		case errno::EISCONN: return net::ALREADY_CONNECTED;
		case errno::ENETUNREACH: return net::NETWORK_UNREACHABLE;
		case errno::ENOTSOCK: return net::NOT_A_SOCKET;
		case errno::EINTR: return io::INTERRUPTED;
		case errno::EWOULDBLOCK: return io::WOULD_BLOCK;
		case errno::EOPNOTSUPP: return net::OPERATION_NOT_SUPPORTED_ON_SOCKET;
		case errno::ETIMEDOUT: return net::CONNECTION_TIMED_OUT;
		default: return io::GENERAL_ERROR;
	}
}

fn fault socket_error()
{
	return convert_error(libc::errno());
}

macro bool NativeSocket.is_valid(self)
{
	return (iptr)self >= 0;
}

macro void? NativeSocket.close(self)
{
	if (libc::close(self))
	{
		if (libc::errno() == errno::EBADF) return net::INVALID_SOCKET~;
		return net::GENERAL_ERROR~;
	}
}

macro void? NativeSocket.set_non_blocking(self, bool non_blocking)
{
	int flags = fcntl(self, F_GETFL, 0);
	if (non_blocking)
	{
		if (flags & O_NONBLOCK) return;
		flags |= O_NONBLOCK;
	}
	else
	{
		if (!(flags & O_NONBLOCK)) return;
		flags &= ~(int)O_NONBLOCK;
	}
	if (fcntl(self, F_SETFL, flags) == -1)
	{
		if (libc::errno() == errno::EBADF) return net::INVALID_SOCKET~;
		return net::GENERAL_ERROR~;
	}
}

macro bool NativeSocket.is_non_blocking(self)
{
	return fcntl(self, F_GETFL, 0) & O_NONBLOCK != 0;
}
