#include <sys/time.h>
#include <sys/resource.h>
#include <sys/types.h>
#include <sys/socket.h>  
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <sys/epoll.h>
#include "net.h"

int g_maxFd = 0;
struct epoll_event* g_events;
int g_epollFd;

void sigpipe_handler(int i)
{
	// do nothing on SIGPIPE
}

int netStartup()
{
	rlimit rlim;
	getrlimit(RLIMIT_NOFILE, &rlim);
	g_maxFd = rlim.rlim_cur;
	g_epollFd = epoll_create(g_maxFd);
	if (g_epollFd < 0) return -NET_STARTUP_FAILED;
	g_events = new epoll_event[g_maxFd];
	if (!g_events) return -NET_STARTUP_FAILED;
	memset(g_events, 0, sizeof(epoll_event) * g_maxFd);
	signal(SIGPIPE, sigpipe_handler);
	
	return 0;
}

int addEpollNotifications(int fd)
{
	if (fd < g_maxFd) {
		fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
		g_events[fd].data.fd = fd;
		g_events[fd].events = EPOLLOUT | EPOLLIN;
		return epoll_ctl(g_epollFd, EPOLL_CTL_ADD, fd, &g_events[fd]);
	}
	return -1;
}

int modifyEpollNotifications(int fd, int flags)
{
	if (fd < g_maxFd) {
		g_events[fd].events = flags;
		return epoll_ctl(g_epollFd, EPOLL_CTL_MOD, fd, &g_events[fd]);
	}
	return -1;
}

int netCleanup()
{
	return 0;
}

int netCreateSocket()
{
	int socket = (int) ::socket(AF_INET, SOCK_STREAM, 0); 
	if (socket == -1) return -NET_CREATE_SOCKET_FAILED;
	if (addEpollNotifications(socket) == -1) return -NET_CREATE_SOCKET_FAILED;
	return socket;
}

int netCreateServerSocket(const char* address, int port)
{
	// open new socket
	int serverSocket = (int) socket(AF_INET, SOCK_STREAM, 0);
	if (serverSocket == -1) return -NET_CREATE_SOCKET_FAILED;

	// avoid EADDRINUSE error on bind()
	int param = 1;
	setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, (char *)&param, sizeof(param));

	// bind the socket
	sockaddr_in local_addr; 
	local_addr.sin_family = AF_INET; 
	local_addr.sin_port = htons(port); 
	memset(&(local_addr.sin_zero), 0, sizeof(local_addr.sin_zero)); 
	if (address) {
		struct hostent *host;
		if ((host = gethostbyname(address)) == NULL) {
			return -NET_GET_HOST_BY_NAME_FAILED;
		}
		local_addr.sin_addr.s_addr = *((unsigned long *) host->h_addr);
	} else {
		local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	}
	if (bind(serverSocket, (sockaddr *) &local_addr, sizeof(sockaddr)) < 0) return -NET_BIND_FAILED;
	if (addEpollNotifications(serverSocket) == -1) return -NET_CREATE_SOCKET_FAILED;
	return serverSocket;
}

int netListen(int serverSocket, int backlog)
{
	// listen for connections
	if (listen(serverSocket, backlog) < 0) return -NET_LISTEN_FAILED;
	return 0;
}

int netConnect(int socket, const char* address, int port)
{
	sockaddr_in serv_addr; 
	serv_addr.sin_family = AF_INET; 
	serv_addr.sin_port = htons(port); 
	memset(&(serv_addr.sin_zero), 0, sizeof(serv_addr.sin_zero)); 
	struct hostent *host;
	if ((host = gethostbyname(address)) == NULL) {
		return -NET_GET_HOST_BY_NAME_FAILED;
	}
	serv_addr.sin_addr.s_addr = *((unsigned long *) host->h_addr);
	int err = ::connect(socket, (sockaddr *) &serv_addr, sizeof(sockaddr));
	if (err < 0 && errno != EINPROGRESS) return -NET_CONNECT_FAILED;
	return 0;
}

int netAccept(int serverSocket, char** ipAddress)
{
	// accept connection
	sockaddr_in client_addr;
	socklen_t addrLen = sizeof(sockaddr_in);
	int clientSocket = (int) accept(serverSocket, ipAddress ? (sockaddr*) &client_addr : NULL, ipAddress ? &addrLen : NULL);
	if (clientSocket == -1) return -NET_ACCEPT_FAILED;

	// get client address, if requested
	if (ipAddress) *ipAddress = strdup(inet_ntoa(client_addr.sin_addr));

	// register notifications
	if (addEpollNotifications(clientSocket) == -1) return -NET_ACCEPT_FAILED;

	return clientSocket;
}

int netCloseSocket(int socket)
{
	if (close(socket) != 0) {
		return -NET_CLOSE_SOCKET_FAILED;
	}
	return 0;
}

int netWait(int millisecondsTimeout)
{
	int num = epoll_wait(g_epollFd, g_events, g_maxFd, millisecondsTimeout);
	if (num < 0) {
		return -NET_LOOP_FAILED;
	}

	if (num == 0) return 0;
	for (int i = 0; i < num; i++) {
		epoll_event* pEvent = &g_events[i];
		if (pEvent->events & (EPOLLIN | EPOLLHUP)) {
			if (g_readCallback) g_readCallback(pEvent->data.fd);
		}
		if (pEvent->events & EPOLLOUT) {
			// disable write EPOLLOUT events after such an event
			modifyEpollNotifications(pEvent->data.fd, EPOLLIN);
			if (g_writeCallback) g_writeCallback(pEvent->data.fd);
		}
		if ((pEvent->events & EPOLLERR) && !(pEvent->events & EPOLLHUP)) {
			if (g_errorCallback) g_errorCallback(pEvent->data.fd);
		}
	}
	return 0;
}

int netWrite(int socket, const void* data, int size, int* sent)
{
	*sent = 0;
	int result = ::send(socket, (char*) data, size, 0);
	if (result >= 0) {
		*sent = result;
	} else {
		if (errno == EAGAIN) {
			// enable write notifications, if not all data was sent
			modifyEpollNotifications(socket, EPOLLIN | EPOLLOUT);
		} else {
			// another error
			return -NET_WRITE_FAILED;
		}
	}
	return 0;
}

int netRead(int socket, void* data, int size, int* received)
{
	*received = 0;
	int result = ::recv(socket, (char*) data, size, 0);
	if (result >= 0) {
		*received = result;
	} else {
		if (errno != EINPROGRESS) return -NET_READ_FAILED;
	}
	return 0;
}

void netSleep(int seconds)
{
	sleep(seconds);
}

