/* $Id: os_abstraction.h 15993 2009-04-08 23:41:48Z rubidium $ */

/**
 * @file os_abstraction.h Network stuff has many things that needs to be
 *                        included and/or implemented by default.
 *                        All those things are in this file.
 */

#ifndef NETWORK_CORE_OS_ABSTRACTION_H
#define NETWORK_CORE_OS_ABSTRACTION_H

/* Include standard stuff per OS */

#ifdef ENABLE_NETWORK

/* Windows stuff */
#if defined(WIN32) || defined(WIN64)
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>

#define GET_LAST_ERROR() WSAGetLastError()
#define EWOULDBLOCK WSAEWOULDBLOCK
/* Windows has some different names for some types */
typedef unsigned long in_addr_t;

#if !(defined(__MINGW32__) || defined(__CYGWIN__))
	/* Windows has some different names for some types */
	typedef SSIZE_T ssize_t;
	typedef int socklen_t;
#	define IPPROTO_IPV6 41
#else
#include "../../win32.h"
#include "../../core/alloc_func.hpp"

#define AI_ADDRCONFIG   0x00000400  // Resolution only if global address configured
#define IPV6_V6ONLY 27

static inline int OTTDgetnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, DWORD hostlen, char *serv, DWORD servlen, int flags)
{
	static int (WINAPI *getnameinfo)(const struct sockaddr *, socklen_t, char *, DWORD, char *, DWORD, int) = NULL;
	static bool first_time = true;

	if (first_time) {
		LoadLibraryList((Function*)&getnameinfo, "ws2_32.dll\0getnameinfo\0\0");
		first_time = false;
	}

	if (getnameinfo != NULL) return getnameinfo(sa, salen, host, hostlen, serv, servlen, flags);

	strncpy(host, inet_ntoa(((struct sockaddr_in *)sa)->sin_addr), hostlen);
	return 0;
}
#define getnameinfo OTTDgetnameinfo

static inline int OTTDgetaddrinfo(const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res)
{
	static int (WINAPI *getaddrinfo)(const char *, const char *, const struct addrinfo *, struct addrinfo **) = NULL;
	static bool first_time = true;

	if (first_time) {
		LoadLibraryList((Function*)&getaddrinfo, "ws2_32.dll\0getaddrinfo\0\0");
		first_time = false;
	}

	if (getaddrinfo != NULL) return getaddrinfo(nodename, servname, hints, res);

	*res = NULL;

	in_addr_t ip = inet_addr(nodename);
	if (ip == INADDR_NONE) {
		struct hostent *he = gethostbyname(nodename);
		if (he == NULL) return EAI_NONAME;
		ip = (*(struct in_addr *)he->h_addr).s_addr;
	}

	struct sockaddr_in *sin = CallocT<struct sockaddr_in>(1);
	sin->sin_family = AF_INET;
	sin->sin_port = htons(strtoul(servname, NULL, 10));
	sin->sin_addr.s_addr = ip;

	struct addrinfo *ai = CallocT<struct addrinfo>(1);
	ai->ai_family = PF_INET;
	ai->ai_addr = (struct sockaddr*)sin;
	ai->ai_addrlen = sizeof(*sin);
	ai->ai_socktype = hints->ai_socktype;

	*res = ai;
	return 0;
}
#define getaddrinfo OTTDgetaddrinfo

static inline void OTTDfreeaddrinfo(struct addrinfo *ai)
{
	static int (WINAPI *freeaddrinfo)(struct addrinfo *) = NULL;
	static bool first_time = true;

	if (ai == NULL) return;

	if (first_time) {
		LoadLibraryList((Function*)&freeaddrinfo, "ws2_32.dll\freeaddrinfo\0\0");
		first_time = false;
	}

	if (freeaddrinfo != NULL) {
		freeaddrinfo(ai);
		return;
	}

	do {
		struct addrinfo *next = ai->ai_next;
		free(ai->ai_addr);
		free(ai);
		ai = next;
	} while(ai != NULL);
}
#define freeaddrinfo OTTDfreeaddrinfo
#endif /* __MINGW32__ && __CYGWIN__ */
#endif /* WIN32 */

/* UNIX stuff */
#if defined(UNIX) && !defined(__OS2__)
#	define SOCKET int
#	define INVALID_SOCKET -1
#	if !defined(__MORPHOS__) && !defined(__AMIGA__)
#		define ioctlsocket ioctl
#	if !defined(BEOS_NET_SERVER)
#		define closesocket close
#	endif
#		define GET_LAST_ERROR() (errno)
#	endif
/* Need this for FIONREAD on solaris */
#	define BSD_COMP

/* Includes needed for UNIX-like systems */
#	include <unistd.h>
#	include <sys/ioctl.h>
#	if defined(__BEOS__) && defined(BEOS_NET_SERVER)
#		include <be/net/socket.h>
#		include <be/kernel/OS.h> /* snooze() */
#		include <be/net/netdb.h>
		typedef unsigned long in_addr_t;
#		define INADDR_NONE INADDR_BROADCAST
#	else
#		include <sys/socket.h>
#		include <netinet/in.h>
#		include <netinet/tcp.h>
#		include <arpa/inet.h>
#		include <net/if.h>
/* According to glibc/NEWS, <ifaddrs.h> appeared in glibc-2.3. */
#		if !defined(__sgi__) && !defined(SUNOS) && !defined(__MORPHOS__) && !defined(__BEOS__) && !defined(__INNOTEK_LIBC__) \
		   && !(defined(__GLIBC__) && (__GLIBC__ <= 2) && (__GLIBC_MINOR__ <= 2)) && !defined(__dietlibc__) && !defined(HPUX)
/* If for any reason ifaddrs.h does not exist on your system, comment out
 *   the following two lines and an alternative way will be used to fetch
 *   the list of IPs from the system. */
#			include <ifaddrs.h>
#			define HAVE_GETIFADDRS
#		endif
#		if !defined(INADDR_NONE)
#			define INADDR_NONE 0xffffffff
#		endif
#		if defined(__BEOS__) && !defined(BEOS_NET_SERVER)
			/* needed on Zeta */
#			include <sys/sockio.h>
#		endif
#	endif /* BEOS_NET_SERVER */

#	if !defined(__BEOS__) && defined(__GLIBC__) && (__GLIBC__ <= 2) && (__GLIBC_MINOR__ <= 1)
		typedef uint32_t in_addr_t;
#	endif

#	include <errno.h>
#	include <sys/time.h>
#	include <netdb.h>
#endif /* UNIX */

#ifdef __BEOS__
	typedef int socklen_t;
#endif

#if defined(PSP)
#	include <sys/socket.h>
#	include <netinet/in.h>
#	include <arpa/inet.h>
#	include <pspnet.h>
#	include <pspnet_inet.h>
#	include <pspnet_apctl.h>
#	include <pspnet_resolver.h>
#	include <errno.h>
#	include <unistd.h>
#	include <sys/select.h>
#	include <sys/time.h>
#	include <sys/fd_set.h>

#	define TCP_NODELAY 1
#	define SO_NONBLOCK 0x1009
#	define SOCKET int
#	define INVALID_SOCKET -1
#	define INADDR_NONE 0xffffffff
#	define closesocket close
#	define GET_LAST_ERROR() sceNetInetGetErrno()
#endif /* PSP */

/* OS/2 stuff */
#if defined(__OS2__)
#	define SOCKET int
#	define INVALID_SOCKET -1
#	define ioctlsocket ioctl
#	define closesocket close
#	define GET_LAST_ERROR() (sock_errno())

/* Includes needed for OS/2 systems */
#	include <types.h>
#	include <unistd.h>
#	include <sys/ioctl.h>
#	include <sys/socket.h>
#	include <netinet/in.h>
#	include <netinet/tcp.h>
#	include <arpa/inet.h>
#	include <net/if.h>
#	include <errno.h>
#	include <sys/time.h>
#	include <netdb.h>
#	include <nerrno.h>
#	define INADDR_NONE 0xffffffff

typedef int socklen_t;
#if !defined(__INNOTEK_LIBC__)
typedef unsigned long in_addr_t;
#endif /* __INNOTEK_LIBC__ */
#endif /* OS/2 */

/* MorphOS and Amiga stuff */
#if defined(__MORPHOS__) || defined(__AMIGA__)
#	include <exec/types.h>
#	include <proto/exec.h>   /* required for Open/CloseLibrary() */
	/* MorphOS defines his network functions with UBYTE arrays while we
	 *  use char arrays. This gives tons of unneeded warnings */
#	define UBYTE char
#	if defined(__MORPHOS__)
#		include <sys/filio.h>  /* FIO* defines */
#		include <sys/sockio.h> /* SIO* defines */
#		include <netinet/in.h>
#	else /* __AMIGA__ */
#		include	<proto/socket.h>
#	endif

/* Make the names compatible */
#	define closesocket(s) CloseSocket(s)
#	define GET_LAST_ERROR() Errno()
#	define ioctlsocket(s, request, status) IoctlSocket((LONG)s, (ULONG)request, (char*)status)
#	define ioctl ioctlsocket

	typedef unsigned int in_addr_t;
	typedef long         socklen_t;
	extern struct Library *SocketBase;

#	ifdef __AMIGA__
	/* for usleep() implementation */
	extern struct Device      *TimerBase;
	extern struct MsgPort     *TimerPort;
	extern struct timerequest *TimerRequest;
#	endif
#endif /* __MORPHOS__ || __AMIGA__ */

static inline bool SetNonBlocking(SOCKET d)
{
#ifdef WIN32
	u_long nonblocking = 1;
#else
	int nonblocking = 1;
#endif
#if (defined(__BEOS__) && defined(BEOS_NET_SERVER)) || defined(PSP)
	return setsockopt(d, SOL_SOCKET, SO_NONBLOCK, &nonblocking, sizeof(nonblocking)) == 0;
#else
	return ioctlsocket(d, FIONBIO, &nonblocking) == 0;
#endif
}

static inline bool SetNoDelay(SOCKET d)
{
	/* XXX should this be done at all? */
#if !defined(BEOS_NET_SERVER) /* not implemented on BeOS net_server */
	int b = 1;
	/* The (const char*) cast is needed for windows */
	return setsockopt(d, IPPROTO_TCP, TCP_NODELAY, (const char*)&b, sizeof(b)) == 0;
#else
	return true;
#endif
}

/* Make sure these structures have the size we expect them to be */
assert_compile(sizeof(in_addr)  ==  4);
assert_compile(sizeof(in6_addr) == 16);

#endif /* ENABLE_NETWORK */

#endif /* NETWORK_CORE_OS_ABSTRACTION_H */
