/*
 * Public domain
 *
 * BSD socket emulation code for Winsock2
 * File IO compatibility shims
 * Brent Cook <bcook@openbsd.org>
 * Kinichiro Inoguchi <inoguchi@openbsd.org>
 */

#define NO_REDEF_POSIX_FUNCTIONS

#if defined(MIKTEX_WINDOWS)
#include <ws2tcpip.h>
#endif
#include <windows.h>
#include <ws2tcpip.h>

#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

void
posix_perror(const char *s)
{
	fprintf(stderr, "%s: %s\n", s, strerror(errno));
}

FILE *
posix_fopen(const char *path, const char *mode)
{
	if (strchr(mode, 'b') == NULL) {
		char *bin_mode = NULL;
		if (asprintf(&bin_mode, "%sb", mode) == -1)
			return NULL;
		FILE *f = fopen(path, bin_mode);
		free(bin_mode);
		return f;
	}

	return fopen(path, mode);
}

int
posix_open(const char *path, ...)
{
	va_list ap;
	int mode = 0;
	int flags;

	va_start(ap, path);
	flags = va_arg(ap, int);
	if (flags & O_CREAT)
		mode = va_arg(ap, int);
	va_end(ap);

	flags |= O_BINARY;
	if (flags & O_CLOEXEC) {
		flags &= ~O_CLOEXEC;
		flags |= O_NOINHERIT;
	}
	flags &= ~O_NONBLOCK;
	return open(path, flags, mode);
}

char *
posix_fgets(char *s, int size, FILE *stream)
{
	char *ret = fgets(s, size, stream);
	if (ret != NULL) {
		size_t end = strlen(ret);
		if (end >= 2 && ret[end - 2] == '\r' && ret[end - 1] == '\n') {
			ret[end - 2] = '\n';
			ret[end - 1] = '\0';
		}
	}
	return ret;
}

int
posix_rename(const char *oldpath, const char *newpath)
{
	return MoveFileEx(oldpath, newpath, MOVEFILE_REPLACE_EXISTING) ? 0 : -1;
}

static int
wsa_errno(int err)
{
	switch (err) {
	case WSAENOBUFS:
		errno = ENOMEM;
		break;
	case WSAEACCES:
		errno = EACCES;
		break;
	case WSANOTINITIALISED:
		errno = EPERM;
		break;
	case WSAEHOSTUNREACH:
	case WSAENETDOWN:
		errno = EIO;
		break;
	case WSAEFAULT:
		errno = EFAULT;
		break;
	case WSAEINTR:
		errno = EINTR;
		break;
	case WSAEINVAL:
		errno = EINVAL;
		break;
	case WSAEINPROGRESS:
		errno = EINPROGRESS;
		break;
	case WSAEWOULDBLOCK:
		errno = EAGAIN;
		break;
	case WSAEOPNOTSUPP:
		errno = ENOTSUP;
		break;
	case WSAEMSGSIZE:
		errno = EFBIG;
		break;
	case WSAENOTSOCK:
		errno = ENOTSOCK;
		break;
	case WSAENOPROTOOPT:
		errno = ENOPROTOOPT;
		break;
	case WSAECONNREFUSED:
		errno = ECONNREFUSED;
		break;
	case WSAEAFNOSUPPORT:
		errno = EAFNOSUPPORT;
		break;
	case WSAEBADF:
		errno = EBADF;
		break;
	case WSAENETRESET:
	case WSAENOTCONN:
	case WSAECONNABORTED:
	case WSAECONNRESET:
	case WSAESHUTDOWN:
	case WSAETIMEDOUT:
		errno = EPIPE;
		break;
	}
	return -1;
}

/*
 * Employ a similar trick to cpython (pycore_fileutils.h) where the CRT report
 * handler is disabled while checking if a descriptor is a socket or a file
 */
#if defined _MSC_VER && _MSC_VER >= 1900

#include <crtdbg.h>
#include <stdlib.h>

static void noop_handler(const wchar_t *expression,	const wchar_t *function,
    const wchar_t *file, unsigned int line, uintptr_t pReserved)
{
	return;
}

#define BEGIN_SUPPRESS_IPH \
	_invalid_parameter_handler old_handler = _set_thread_local_invalid_parameter_handler(noop_handler)
#define END_SUPPRESS_IPH \
	_set_thread_local_invalid_parameter_handler(old_handler)

#else

#define BEGIN_SUPPRESS_IPH
#define END_SUPPRESS_IPH

#endif

static int
is_socket(int fd)
{
	intptr_t hd;

	BEGIN_SUPPRESS_IPH;
	hd = _get_osfhandle(fd);
	END_SUPPRESS_IPH;

	if (hd == (intptr_t)INVALID_HANDLE_VALUE) {
		return 1; /* fd is not file descriptor */
	}

	return 0;
}

int
posix_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
	int rc = connect(sockfd, addr, addrlen);
	if (rc == SOCKET_ERROR)
		return wsa_errno(WSAGetLastError());
	return rc;
}

int
posix_close(int fd)
{
	int rc;

	if (is_socket(fd)) {
		if ((rc = closesocket(fd)) == SOCKET_ERROR) {
			int err = WSAGetLastError();
			rc = wsa_errno(err);
		}
	} else {
		rc = close(fd);
	}
	return rc;
}

ssize_t
posix_read(int fd, void *buf, size_t count)
{
	ssize_t rc;

	if (is_socket(fd)) {
		if ((rc = recv(fd, buf, count, 0)) == SOCKET_ERROR) {
			int err = WSAGetLastError();
			rc = wsa_errno(err);
		}
	} else {
		rc = read(fd, buf, count);
	}
	return rc;
}

ssize_t
posix_write(int fd, const void *buf, size_t count)
{
	ssize_t rc;
	if (is_socket(fd)) {
		if ((rc = send(fd, buf, count, 0)) == SOCKET_ERROR) {
			rc = wsa_errno(WSAGetLastError());
		}
	} else {
		rc = write(fd, buf, count);
	}
	return rc;
}

int
posix_getsockopt(int sockfd, int level, int optname,
	void *optval, socklen_t *optlen)
{
	int rc;
	if (is_socket(sockfd)) {
		rc = getsockopt(sockfd, level, optname, (char *)optval, optlen);
		if (rc != 0) {
			rc = wsa_errno(WSAGetLastError());
		}
	} else {
		rc = -1;
	}
	return rc;
}

int
posix_setsockopt(int sockfd, int level, int optname,
	const void *optval, socklen_t optlen)
{
	int rc;
	if (is_socket(sockfd)) {
		rc = setsockopt(sockfd, level, optname, (char *)optval, optlen);
		if (rc != 0) {
			rc = wsa_errno(WSAGetLastError());
		}
	} else {
		rc = -1;
	}
	return rc;
}

uid_t getuid(void)
{
	/* Windows fstat sets 0 as st_uid */
	return 0;
}

#ifdef _MSC_VER
struct timezone;
int gettimeofday(struct timeval * tp, struct timezone * tzp)
{
	/*
	 * Note: some broken versions only have 8 trailing zero's, the correct
	 * epoch has 9 trailing zero's
	 */
	static const uint64_t EPOCH = ((uint64_t) 116444736000000000ULL);

	SYSTEMTIME  system_time;
	FILETIME    file_time;
	uint64_t    time;

	GetSystemTime(&system_time);
	SystemTimeToFileTime(&system_time, &file_time);
	time = ((uint64_t)file_time.dwLowDateTime);
	time += ((uint64_t)file_time.dwHighDateTime) << 32;

	tp->tv_sec = (long)((time - EPOCH) / 10000000L);
	tp->tv_usec = (long)(system_time.wMilliseconds * 1000);
	return 0;
}
#endif
