#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <arpa/inet.h>

int fd_srv = -1, fd_epoll = -1;

#define FAILED(msg) perror(msg); \
if (fd_epoll > -1) close(fd_epoll); \
if (fd_srv > -1) close(fd_srv); \
return -1;

#define BACKLOG 5
#define EPOLL_SIZE 65535
#define EPOLL_EVENTS_SIZE 1024
#define READ_BUFFER_SIZE 1024

int main(int argc, char *argv[])
{
	const char *host;
	int port;
	struct sockaddr_in addr_srv = {}, addr_cli = {};
	int fd_cli;
	struct epoll_event evt = {}, events[EPOLL_EVENTS_SIZE] = {};
	int epoll_events_n, i, flags_cli;
	socklen_t n_cli;
	char buffer[READ_BUFFER_SIZE] = {};
	ssize_t n_read, n_write;

	if (argc < 3)
	{
		printf("Usage: %s SERVER_IP SERVER_PORT\n", argv[0]);
		return -1;
	}
	host = argv[1];
	port = atoi(argv[2]);
	addr_srv.sin_family = AF_INET;
	addr_srv.sin_port = htons(port);
	addr_srv.sin_addr.s_addr = inet_addr(host);

	if ((fd_srv = socket(AF_INET, SOCK_STREAM, 0)) == -1)
	{
		FAILED("socket()");
	}

	if ((bind(fd_srv, (struct sockaddr *)&addr_srv, sizeof(struct sockaddr_in))) == -1)
	{
		FAILED("bind()");
	}

	if (listen(fd_srv, BACKLOG) == -1)
	{
		FAILED("bind()");
	}

	if ((fd_epoll = epoll_create(EPOLL_SIZE)) == -1)
	{
		FAILED("epoll_create()");
	}

	evt.events = EPOLLIN;
	evt.data.fd = fd_srv;
	if (epoll_ctl(fd_epoll, EPOLL_CTL_ADD, fd_srv, &evt))
	{
		FAILED("epoll_ctl()");
	}

	while (1)
	{

		epoll_events_n = epoll_wait(fd_epoll, events, EPOLL_EVENTS_SIZE, -1);
		if (epoll_events_n == -1)
		{
			if (errno != EINTR)
			{
				FAILED("epoll_wait()");
			}
			continue;
		}

		for (i = 0; i < epoll_events_n; i++)
		{
			if (events[i].data.fd == fd_srv)
			{
				if ((fd_cli = accept(fd_srv, (struct sockaddr *)&addr_cli, &n_cli)) == -1)
				{
					FAILED("accept()");
				}
				
				flags_cli = fcntl(fd_cli, F_GETFL);
				if (fcntl(fd_cli, F_SETFL, flags_cli | O_NONBLOCK) == -1)
				{
					FAILED("fcntl()");
				}
				
				evt.events = EPOLLIN;
				evt.data.fd = fd_cli;
				if (epoll_ctl(fd_epoll, EPOLL_CTL_ADD, fd_cli, &evt))
				{
					FAILED("epoll_ctl()");
				}
			}
			else
			{
				if (events[i].events & EPOLLIN)
				{
					while (1)
					{
						n_read = read(events[i].data.fd, buffer, READ_BUFFER_SIZE);
						if (n_read < 0)
						{
							if (errno != EAGAIN)
							{
								perror("read()");
								epoll_ctl(fd_epoll, EPOLL_CTL_DEL, events[i].data.fd, &events[i]);
								close(events[i].data.fd);
							}
							break;
						}
						else if (n_read == 0)
						{
							epoll_ctl(fd_epoll, EPOLL_CTL_DEL, events[i].data.fd, &events[i]);
							close(events[i].data.fd);
							break;
						}
						n_write = write(events[i].data.fd, buffer, n_read);
					}
				}
				if (events[i].events & EPOLLOUT)
				{
					
				}
			}
		}
	}

	close(fd_epoll);
	close(fd_srv);

	return 0;
}