#if defined(_WIN32) || defined(__APPLE__) || defined (__linux__)\
	|| defined (__unix__)

#include "link_interface.h"

#ifdef _WIN32
#ifndef WINVER
#define WINVER 0x0501
#endif /* !WINVER */
#endif /* _WIN32 */

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

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#else /* _WIN32 */
#include <net/if.h>
#include <ifaddrs.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <netdb.h>
#endif /* _WIN32 */

#ifdef _WIN32
SOCKET sock_fd = INVALID_SOCKET;
SOCKET fd = INVALID_SOCKET;
#else /* _WIN32 */
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
static int sock_fd = INVALID_SOCKET;
static int fd = INVALID_SOCKET;
#endif /* _WIN32 */

static uint8_t bt;

static struct LinkSettings link_settings =
	{ .port=COMM_PORT, .serverMode=true,
#ifdef COMM_HOST
	.host=COMM_HOST
#else /* COMM_HOST */
	.host=NULL
#endif /* COMM_HOST */
};
struct LinkSettings get_link_settings ()
{
	return link_settings;
}

uint8_t link_read_byte(void) {
#ifdef _WIN32
	int res = recv(fd, (char *)&bt, 1, 0);
	if (res == 0) {
		return 255;
	} else if (res < 0) {
		printf("recv failed with error: %d\n", WSAGetLastError());
		closesocket(fd);
		WSACleanup();
		return 255;
	}
#else /* _WIN32 */
	ssize_t res = read(fd, &bt, 1);
	//Other end closed the socket
	if (res == 0) {
		return 255;
		//Error occured
	} else if (res == -1) {
		perror("read");
		return 255;
	}
	msg_debug("received byte: %02x\n", bt);
#endif /* _WIN32 */
	return bt;
}

void link_write_byte(uint8_t b) {
#ifdef _WIN32
	send(fd, (char *)&b, 1, 0);
#else /* _WIN32 */
	write(fd, &b, 1);
#endif /* _WIN32 */
}

bool link_input_available(void) {
#ifdef _WIN32
	u_long bytes_available;
	if (ioctlsocket(fd, FIONREAD, &bytes_available) != 0) {
		printf("recv failed with error: %d\n", WSAGetLastError());
		WSACleanup();
		die("");
	}
	return bytes_available>0;
#else /* _WIN32 */
	struct timeval tv = { .tv_sec=0, .tv_usec=0 };
	fd_set fds;
	FD_ZERO(&fds);
	FD_SET(fd, &fds);
	if (select(fd + 1, &fds, NULL, NULL, &tv) == SOCKET_ERROR)
		pdie("select");
	return FD_ISSET(fd, &fds);
#endif /* _WIN32 */
}

static void open_client(uint16_t port, const char* host)
{
	struct addrinfo hints;
	struct addrinfo *result = NULL, *ca;

	msg_log("Connect to server %s:%u\n", host, port);

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;

	char p[] = "65536";
	sprintf(p, "%hu", port);
	int rc = getaddrinfo(host, p, &hints, &result);
	if (rc != 0) {
#ifdef _WIN32
		pdie("getaddrinfo");
#else /* _WIN32 */
		if (rc == EAI_SYSTEM) {
			pdie("getaddrinfo");
		} else {
			die("getaddrinfo: %s\n", gai_strerror(rc));
		}
#endif /* _WIN32 */
	}


	for (ca = result; ca != NULL; ca = ca->ai_next) {
		if ((fd = socket(ca->ai_family, ca->ai_socktype,
				ca->ai_protocol)) == INVALID_SOCKET)
			pdie("socket");

		if (connect(fd, ca->ai_addr, ca->ai_addrlen) ==
					SOCKET_ERROR) {
#ifdef _WIN32
			closesocket(fd);
#else /* _WIN32 */
			close(fd);
#endif /* _WIN32 */
			fd = INVALID_SOCKET;
			perror("connect");
			continue;
		}
		break;
	}
	freeaddrinfo(result);

	if (fd == INVALID_SOCKET)
		die("unable to connect to any resolved host");

	msg_log("Connected!\n");
}

static void open_server(uint16_t port)
{
#ifdef _WIN32
	struct addrinfo *result = NULL;
	struct addrinfo hints;
	ZeroMemory(&hints, sizeof(hints));
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;
	hints.ai_flags = AI_PASSIVE;

	char portstr[] = "65536";
	sprintf(portstr, "%hd", port);
	if (getaddrinfo(NULL, portstr, &hints, &result) != 0 ) {
		perror("getaddrinfo");
		WSACleanup();
		die("");
	}

	if ((sock_fd = socket(result->ai_family, result->ai_socktype,
			result->ai_protocol)) == INVALID_SOCKET) {
	        fprintf(stderr, "socket failed with error: %d\n",
			WSAGetLastError());
		freeaddrinfo(result);
		WSACleanup();
		die("");
	}

	// Setup the TCP listening socket
	if (bind(sock_fd, result->ai_addr, (int)result->ai_addrlen)
			== SOCKET_ERROR) {
	        fprintf(stderr, "bind failed with error: %d\n",
			WSAGetLastError());
		freeaddrinfo(result);
		closesocket(sock_fd);
		WSACleanup();
		die("");
	}

	freeaddrinfo(result);

	if (listen(sock_fd, SOMAXCONN) == SOCKET_ERROR) {
		fprintf(stderr, "listen failed with error: %d\n",
			WSAGetLastError());
		closesocket(sock_fd);
		WSACleanup();
		die("");
	}

	msg_log("Listening on %hd\n", port);

	fd_set fds;

	struct timeval tv = { .tv_sec=0, .tv_usec=1 };

	while (fd == INVALID_SOCKET) {
		real_yield();
		FD_ZERO(&fds);
		FD_SET(sock_fd, &fds);
		int r = select(sock_fd + 1, &fds, 0, 0, &tv);
		if (r == SOCKET_ERROR) {
			// Select failed
			fprintf(stderr, "select failed with error: %d\n",
				WSAGetLastError());
			closesocket(sock_fd);
			WSACleanup();
			die("");
		} else if (r > 0) {
			// Accept a client socket
			fd = accept(sock_fd, NULL, NULL);
			if (fd == INVALID_SOCKET) {
				fprintf(stderr,
					"accept failed with error: %d\n",
					WSAGetLastError());
				closesocket(sock_fd);
				WSACleanup();
				die("");
			}
			break;
		}
	}

	// No longer need server socket
	closesocket(sock_fd);
#else /* _WIN32 */
	//Open file descriptors
	struct sockaddr_in sa;

	memset(&sa, 0, sizeof(sa));
	sa.sin_family = AF_INET;
	sa.sin_addr.s_addr = INADDR_ANY;
	sa.sin_port = htons(port);

	if (sock_fd == INVALID_SOCKET) {
		if ((sock_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0))
				== SOCKET_ERROR)
			pdie("socket");
		if (bind(sock_fd, (struct sockaddr*) &sa, sizeof(sa))
				== SOCKET_ERROR)
			pdie("bind");
	}
	if (listen(sock_fd, 10) == SOCKET_ERROR)
		pdie("listen");

	msg_log("Listening on %hd\n", port);
	fd = -1;
	while (fd == -1) {
		fd = accept(sock_fd, NULL, NULL);
		if (fd == INVALID_SOCKET) {
			if (errno == EAGAIN || errno == EWOULDBLOCK) {
				msdelay(50);
				continue;
			} else {
				pdie("accept");
			}
		}
	}
#endif /* _WIN32 */
	msg_log("Accepted incoming connection\n");
}

void open_link(struct LinkSettings ls)
{
	link_settings = ls;
#ifdef _WIN32
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
		pdie("WSAStartup");
#endif /* _WIN32 */
	if (link_settings.serverMode) {
		open_server(link_settings.port);
	} else {
		open_client(link_settings.port, link_settings.host);
	}
}

void close_link(bool temporary) {
	(void) temporary;
#ifdef _WIN32
	if (shutdown(fd, SD_SEND) == SOCKET_ERROR) {
		perror("shutdown");
		WSACleanup();
		die("");
	}
	closesocket(fd);
	WSACleanup();
#else /* _WIN32 */
	close(fd);
#endif /* _WIN32 */
	sock_fd = INVALID_SOCKET;
	fd = INVALID_SOCKET;
}

void cleanup_link()
{
#ifdef _WIN32
	if (sock_fd != INVALID_SOCKET) {
		shutdown(sock_fd, SD_SEND);
		closesocket(sock_fd);
	}
	if (fd != INVALID_SOCKET) {
		shutdown(fd, SD_SEND);
		closesocket(fd);
	}
#else /* _WIN32 */
	if (sock_fd != INVALID_SOCKET) {
//		shutdown(sock_fd, SHUT_RDWR);
		close(sock_fd);
	}
	if (fd != INVALID_SOCKET) {
//		shutdown(fd, SHUT_RDWR);
		close(fd);
	}
#endif /* _WIN32 */
}
#endif /* defined(_WIN32) || defined(__APPLE__) || defined (__linux__)\
	|| defined (__unix__)*/

