#include <sys/types.h>
#include <sys/socket.h>
#include <sys/event.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <err.h>

/*
 * simple kqueue chat server by Peter Werner <peterw@ifost.org.au>
 */

#define NUSERS 10

/*
 * we store each connected clients filedescriptor 
 * and ip address in an array, which places an upper 
 * bound of 10 users. This is done for simplicity.
 */

struct uc {
	int uc_fd;
	char *uc_addr;
} users[NUSERS];

/*
 * bind(2) and listen(2) to a tcp port
 */
int
mksock(char *addr, int port)
{
	int i, sock;
	struct sockaddr_in serv;

	sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock == -1)
		err(1, "socket");

	i = 1;
	if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&i,
	    (socklen_t)sizeof(i)) == -1)
		warn("setsockopt");

	memset(&serv, 0, sizeof(struct sockaddr_in));
	serv.sin_family = AF_INET;
	serv.sin_port = htons(port);
	serv.sin_addr.s_addr = inet_addr(addr);

	i = bind(sock, (struct sockaddr *)&serv, (socklen_t)sizeof(serv));
	if (i == -1)
		err(1, "bind");

	i = listen(sock, 5);
	if (i == -1)
		err(1, "listen");

	return(sock);
}

int
main(void)
{
	int servsock, kq, i, uidx;
	struct kevent ke;
	struct sockaddr_in c; /* client */
	socklen_t len;
	char buf[1024];
	char *umsg = "too many users!\n";
	
	/* get a listening socket */
	servsock = mksock("127.0.0.1", 5000);

	/* get our kqueue descriptor */
	kq = kqueue();
	if (kq == -1)
		err(1, "kqueue!");

	memset(&ke, 0, sizeof(struct kevent));
	memset(users, 0, sizeof(struct uc) * NUSERS);
	
	/* fill out the kevent struct */
	EV_SET(&ke, servsock, EVFILT_READ, EV_ADD, 0, 5, NULL);

	/* set the event */
	i = kevent(kq, &ke, 1, NULL, 0, NULL);
	if (i == -1)
		err(1, "set kevent");

	while (1) {

		memset(&ke, 0, sizeof(ke));

		/* receive an event, a blocking call as timeout is NULL */
		i = kevent(kq, NULL, 0, &ke, 1, NULL);
		if (i == -1)
			err(1, "kevent!");

		if (i == 0)
			continue;

		/*
		 * since we only have one kevent in the eventlist, we're only
		 * going to get one event at a time
		 */

		if (ke.ident == servsock) {

			/* server socket, theres a client to accept */

			len = (socklen_t)sizeof(c);
			i = accept(servsock, (struct sockaddr *)&c, &len);
			if (i == -1)
				err(1, "accept!");
						
			for (uidx = 0; uidx < NUSERS; uidx++)
				if (users[uidx].uc_fd == 0)
					break;

			if (uidx == NUSERS) {
				warnx("%s", umsg);
				write(i, umsg, strlen(umsg));
				close(i);
				continue;
			}

			users[uidx].uc_fd = i; /* users file descriptor */
			users[uidx].uc_addr = strdup(inet_ntoa(c.sin_addr));
			if (users[uidx].uc_addr == NULL)
				err(1, "strdup");
			
			EV_SET(&ke, i, EVFILT_READ, EV_ADD, 0, 0, NULL);
			i = kevent(kq, &ke, 1, NULL, 0, NULL);
			if (i == -1)
				err(1, "kevent add user!");

			printf("connection from %s added\n",
			    users[uidx].uc_addr);
		} else {

			/*
			 * got a message to distribute, first find the user
			 * and read their message
			 */

			for (uidx = 0; uidx < NUSERS; uidx++)
				if (users[uidx].uc_fd == ke.ident)
					break;

			if (uidx == NUSERS)
				errx(1, "bogus message!");

			memset(buf, 0, sizeof(buf));

			i = read(users[uidx].uc_fd, buf, sizeof(buf));
			if (i == -1)
				continue;

			if (i == 0) { /* EOF from a client */

				printf("removing %s\n", users[uidx].uc_addr);

				EV_SET(&ke, users[uidx].uc_fd, EVFILT_READ,
				    EV_DELETE, 0, 0, NULL);

				i = kevent(kq, &ke, 1, 0, 0, NULL);
				if (i == -1)
					err(1, "rm user from kq");
	
				close(users[uidx].uc_fd);
				free(users[uidx].uc_addr);

				users[uidx].uc_fd = 0;
				users[uidx].uc_addr = NULL;

				continue;
			}

			printf("got a message from %s\n", users[uidx].uc_addr);

			/* now write it to the other users */

			for (uidx = 0; uidx < NUSERS; uidx++) {

				if (users[uidx].uc_fd == 0 ||
				    users[uidx].uc_fd == ke.ident)
					continue;

				i = write(users[uidx].uc_fd, buf, sizeof(buf));
				if (i == -1)
					warn("write failed!");
			}
		} /* end if */
	} /* end while */

	return(0);
}
