/*
 * hearts_dist - distributor for hearts
 *
 * Keeps track of games in progress and allows new players to select which
 * game to join or start a new game.
 *
 *
 *	Commands to hearts player:
 *  tn		Following info regards table n (n is unique identifier).
 *  hn		Now playing hand n.
 *  rn		Now playing round n.
 *  pn<name>	Player n is <name>.
 *
 *  xn		Table n has exited (game over).
 *  g		Get table number to join.
 *  cn		Connect to dealer on port n.
 *
 *	Commands from hearts player:
 *  jn		Join table n.
 *  n		Start new game.
 *
 *	Commands to dealer:
 * none yet?!?
 *
 *	Commands from dealer:
 *  hn		Now playing hand n.
 *  rn		Now playing round n.
 *  pn<name>	Player n is <name>.
 *
 */

#include "misc.h"
#include "defs.h"
#include "local.h"
#include <string.h>
#include <malloc.h>

typedef struct table *table_ptr;

struct table {
	table_ptr	next_table;		/* Points to next table entry */
	int		table_id;		/* Unique identifier */
	char		player_name[4][9];	/* Name of players at table */
	int		hand;			/* Current dealer hand (1..4) */
	int		round;			/* Current round (1..13) */
	int		port_num;		/* Port # assigned */
	int		socket;			/* File descriptor for dealer */
	int		pid;			/* Process id for dealer */
};

typedef struct empty_table *empty_ptr;

struct empty_table {
	empty_ptr	next_empty;
	int		port_num;		/* Available port # */
};

typedef struct new_comer *new_ptr;

struct new_comer {
	new_ptr		next_new_comer;
	int		socket;			/* File descriptor */
};

table_ptr	first_table;
empty_ptr	first_empty;
new_ptr		first_new_comer;

int	main_sock,
	next_port,				/* Next port to assign */
	unique_id;

/*
 * clear_table - dealer went away; drop that table.
 */
clear_table(tbl_ptr)
table_ptr	tbl_ptr;
{
	table_ptr	prev_ptr = NULL;
	table_ptr	cur_ptr;
	empty_ptr	temp_empty, cur_empty;
	new_ptr		cur_new_comer;
	char		buf[16];

	while (wait(0) != tbl_ptr->pid)		/* Wait for process to die */
		;
	(void) close(tbl_ptr->socket);
	/*
	 * Inform new-comers
	 */
	(void) sprintf(buf, "x%d", tbl_ptr->table_id);
	for (cur_new_comer = first_new_comer; cur_new_comer;
			cur_new_comer = cur_new_comer->next_new_comer)
		write_socket(cur_new_comer->socket, buf);
	for (cur_ptr = first_table; cur_ptr != tbl_ptr;
					cur_ptr = cur_ptr->next_table)
		prev_ptr = cur_ptr;
	if (prev_ptr)
		prev_ptr->next_table = tbl_ptr->next_table;
	else
		if ((first_table = tbl_ptr->next_table) == NULL &&
		    first_new_comer == NULL)
			exit(0);	/* No more dealers */
			;
     if (first_table) {
	temp_empty = (empty_ptr) malloc(sizeof(struct empty_table));
	temp_empty->next_empty = NULL;
	temp_empty->port_num = tbl_ptr->port_num;
	free((char *) tbl_ptr);
	if (first_empty) {
		for (cur_empty = first_empty; cur_empty->next_empty;
					cur_empty = cur_empty->next_empty) ;
		cur_empty->next_empty = temp_empty;
	}
	else
		first_empty = temp_empty;
      }
}

/*
 * drop_new_comer - New comer exited
 */
drop_new_comer(dead_new_comer)
new_ptr	dead_new_comer;
{
	new_ptr	cur_new_comer,
		prev_new_comer = NULL;

	(void) close(dead_new_comer->socket);
	for (cur_new_comer = first_new_comer; cur_new_comer != dead_new_comer;
			cur_new_comer = cur_new_comer->next_new_comer)
		prev_new_comer = cur_new_comer;
	if (prev_new_comer)
		prev_new_comer->next_new_comer = dead_new_comer->next_new_comer;
	else
		first_new_comer = dead_new_comer->next_new_comer;
	free((char *) dead_new_comer);
	if ((first_table == NULL) && (first_new_comer == NULL))
		exit(0);		/* Nobody connected */
}

/*
 * new_player - New player has connected.  Inform of games in progress and
 *		request game to be joined.
 */
new_player()
{
	int	new_socket;		/* new file descriptor */
	char	buf[64];
	struct	sockaddr_in sockad;
	int	ssize;			/* makes accept happy */
	int	i;
	new_ptr		cur_new_comer, temp_new_comer;
	table_ptr	cur_ptr;

	/*
	 * add whoever's waiting
	 */
	ssize = sizeof (sockad);
	if ((new_socket = accept(main_sock, &sockad, &ssize)) == -1) {
		perror("accept");
		exit(-1);
	}
	/*
	 * add to list of new_comers
	 */
	temp_new_comer = (new_ptr) malloc(sizeof(struct new_comer));
	temp_new_comer->next_new_comer = NULL;
	temp_new_comer->socket = new_socket;
	if (first_new_comer) {
		for (cur_new_comer = first_new_comer;
			cur_new_comer->next_new_comer;
			cur_new_comer = cur_new_comer->next_new_comer) ;
		cur_new_comer->next_new_comer = temp_new_comer;
	}
	else {
		first_new_comer = temp_new_comer;
		first_new_comer->next_new_comer = NULL;
	}
	/*
	 * send info on games in progress
	 */
	for (cur_ptr = first_table; cur_ptr; cur_ptr = cur_ptr->next_table) {
		(void) sprintf(buf, "t%d", cur_ptr->table_id);
		write_socket(new_socket, buf);
		(void) sprintf(buf, "h%d", cur_ptr->hand);
		write_socket(new_socket, buf);
		(void) sprintf(buf, "r%d", cur_ptr->round);
		write_socket(new_socket, buf);
		for (i = 0; i < 4; i++) {
			(void) sprintf(buf, "p%d%s", i, cur_ptr->player_name[i]);
			write_socket(new_socket, buf);
		}
	}
	write_socket(new_socket, "g");
}

tell_new_comers(buf)
char	*buf;
{
	new_ptr	cur_new;

	for (cur_new = first_new_comer; cur_new;
			cur_new = cur_new->next_new_comer)
		write_socket(cur_new->socket, buf);
}

/*
 * join - join an existing table.  table_num is unique identifier for table.
 */
join(table_num, socket)
int	table_num, socket;
{
	table_ptr	cur_ptr;
	char	buf[16];

	for (cur_ptr = first_table; cur_ptr && cur_ptr->table_id != table_num;
					cur_ptr = cur_ptr->next_table) ;
	if (cur_ptr) {
		(void) sprintf(buf, "c%d", cur_ptr->port_num);
		write_socket(socket, buf);
	}
	else
		write_socket(socket, "g");	/* Table doesn't exist?!? */
}

/*
 * new_table - start new hearts game.
 */
new_table(cur_new_comer)
new_ptr	cur_new_comer;
{
	table_ptr	cur_table, new_tbl_ptr;
	empty_ptr	tmp_empty;
	int		i, socks[2];
	int		dealer_port, dealer_socket, retry = 0;
	char	buf[16], filename[1024];

	new_tbl_ptr = (table_ptr) malloc(sizeof(struct table));
	new_tbl_ptr->next_table = NULL;
	new_tbl_ptr->table_id = ++unique_id;
	for (i = 0; i < 4; i++)
		(void) strcpy(new_tbl_ptr->player_name[i], "<empty>");
	/*
	 * Assign a port.  Reassign a used port if available.
	 */
	do {
		if (first_empty) {
			dealer_port = first_empty->port_num;
			tmp_empty = first_empty;
			first_empty = first_empty->next_empty;
			free((char *) tmp_empty);
		}
		else
			dealer_port = next_port++;
		/*
		 * Make sure port is ok before assigning.
		 */
		if ((dealer_socket = open_socket(dealer_port)) == 0)
			if (++ retry == 20) {
				fputs("Can't open a dealer port!\n", stderr);
				exit(1);
			}
	}
	while (dealer_socket == 0);
	new_tbl_ptr->port_num = dealer_port;
	/*
	 * Open a socket pair to talk to dealer on.
	 */
	if (socketpair(AF_UNIX, SOCK_STREAM, 0, socks) == -1) {
		perror("socketpair");
		exit(1);
	}
	switch (new_tbl_ptr->pid = fork()) {
	case 0:
		(void) setpgrp (0, getpid());
		(void) close(socks[0]);
		if (socks[1] != 3)
			(void) dup2(socks[1], 3);	/* Remap to fd 3 */
		if (dealer_socket != 4)
			(void) dup2(dealer_socket, 4);	/* Remap to fd 4 */
		sprintf(filename, "%s/%s", HEARTSLIB, HEARTSD);
		execl (filename, "heartsd", 0);
		exit(1);
	case -1:
		perror("fork");
		exit(1);
	}
	(void) close(socks[1]);
	(void) close(dealer_socket);

	new_tbl_ptr->socket = socks[0];
	if (first_table) {
		for (cur_table = first_table; cur_table->next_table;
			cur_table = cur_table->next_table)
			;
		cur_table->next_table = new_tbl_ptr;
	}
	else
		first_table = new_tbl_ptr;
	/*
	 * Inform hearts player of port # to connect on.
	 */
	(void) sprintf(buf, "c%d", dealer_port);
	write_socket(cur_new_comer->socket, buf);
}

main(argc, argv)
char **argv;
{
	fd_type		read_fd;
	table_ptr	cur_table;
	new_ptr		cur_new_comer;
	char		buf[64], tmp_buf[64];
	int		ret;
	int		table_num;
	char		debug = FALSE;

	while (*++argv) {
		if (**argv == '-') {
			while (*++*argv) {
				switch (**argv) {
				case 'd':
					debug = TRUE;
					break;

				default:
					fprintf (stderr, "usage: hearts_dist [-d]\n");
					exit (1);
					break;
				}
			}
		}
	}

	first_table = NULL;
	first_empty = NULL;
	first_new_comer = NULL;
	next_port = DIST_PORT;
	unique_id = 0;

	if ((main_sock = open_socket(PORT)) == 0) {
		fputs("Distributor port in use!\n", stderr);
		exit(1);
	}
	if (!debug) {
		/*
		 * Fork off and die.  Thus hearts invoker wait() returns.
		 * This signals ready to connect.
		 */
		if (fork())
			exit (0);
	}

	for (;;) {
		fd_init(main_sock, &read_fd);
		/*
		 * Build mask for dealers
		 */
		for (cur_table = first_table; cur_table;
				cur_table = cur_table->next_table)
			fd_set(cur_table->socket, &read_fd);
		/*
		 * Build mask for new_comers
		 */
		for (cur_new_comer = first_new_comer; cur_new_comer;
				cur_new_comer = cur_new_comer->next_new_comer)
			fd_set(cur_new_comer->socket, &read_fd);

		/*
		 * Wait for something to happen
		 */
		if (select(WIDTH, &read_fd, (fd_type *) 0, (fd_type *) 0,
				(struct timeval *) 0)) {
			if (fd_isset(main_sock, read_fd))
				new_player();
			for (cur_table = first_table; cur_table;
					cur_table = cur_table->next_table)
				if (fd_isset(cur_table->socket, read_fd)) {
					/*
					 * Message from dealer
					 */
					ret = read_socket(cur_table->socket, buf);
					if (!ret) {
						clear_table(cur_table);
						break;
					} else {
						switch (buf[0]) {
						case 'h' :
							(void) sscanf(buf + 1, "%d", &cur_table->hand);
							break;
						case 'r' :
							(void) sscanf(buf + 1, "%d", &cur_table->round);
							break;
						case 'p' :
							(void) strcpy(cur_table->player_name[buf[1] - '0'], buf + 2);
						}
						(void) sprintf(tmp_buf, "t%d", cur_table->table_id);
						tell_new_comers(tmp_buf);
						tell_new_comers(buf);
					}
				}
			for (cur_new_comer = first_new_comer; cur_new_comer;
				cur_new_comer = cur_new_comer->next_new_comer)
				if (fd_isset(cur_new_comer->socket, read_fd)) {
					/*
					 * Message from newcomer
					 */
					ret = read_socket(cur_new_comer->socket, buf);
					if (ret)
						switch (buf[0]) {
						case 'j' :
							(void) sscanf(buf + 1, "%d", &table_num);
							join(table_num, cur_new_comer->socket);
							break;

						case 'n' :
							new_table(cur_new_comer);
						}
					else {
						drop_new_comer(cur_new_comer);
						break;
					     }
				}
		}
	}
}

