#include "network.h"

/* This function opens a socket on port <port> to accept connections.
   It returns the newly formed socket. */
int make_conn_socket(unsigned short port)
{
	int sock, rc;
	struct sockaddr_in srvaddr;

	sock = socket(AF_INET, SOCK_STREAM, 0);	/* create endpoint */
	if (sock < 0)
	{
		ttyprintf("Error opening connection endpoint.\nsocket: %s\n",
				  strerror(errno));
		return -1;
	}

	memset(&srvaddr, 0, sizeof(srvaddr));	/* set up our address */
	srvaddr.sin_family = AF_INET;
	srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	srvaddr.sin_port = htons(port);
	rc = bind(sock, (struct sockaddr *) &srvaddr, sizeof(srvaddr));
	/* bind address to us */
	if (rc < 0)
	{
		ttyprintf("Error initializing connection endpoint.\nbind: %s\n",
				  strerror(errno));
		return -1;
	}
	rc = listen(sock, 5);
	if (rc < 0)
	{
		ttyprintf("Error initializing connection endpoint.\nlisten: %s\n",
				  strerror(errno));
		return -1;
	}
	return sock;
}

/* This is a read() call that is supposed to be smart about
   things like EINTR killed my system call, etc.

   It returns 0 if the read was unsuccessful, the connection closed, etc., 
   or >0 if there is that many bytes to be read. */
int socket_read(int socket, char **bufaddr)
{
	/* Read returns  -1  on  error (errno == EINTR -> try again)
	                  0  on  end of connection
	                 >0  on  got that many bytes */
	static char buffer[READBUFSIZE];
	int rc;

	*bufaddr = buffer;
	do
	{
		rc = read(socket, buffer, READBUFSIZE);
		if (rc < 0 && errno == EINTR)
			continue;
		break;
	}
	while (errno == EINTR);
	if (rc < 0)
	{
		ttyprintf("Communications error.\nread(fd=%d): %s\n",
				  socket, strerror(errno));
		return 0;
	}
	else if (rc == 0)
	{
		disconnect(socket);
		return 0;
	}
	else
		return rc;
}

/* This disconnects the <socket> by doing close(socket) and then
   removes it from the watching list with FD_CLR, so further calls to
   select() don't see it in the list(s). */
void disconnect(int socket)
{
	close(socket);
	FD_CLR(socket, &watching);
}

/* This is the interesting part. this procedure sets up the 
   connection socket, and waits for connections and/or I/O. 
   It then hands off to greet() for the connections and
   process() for the I/O. */
void main_loop(void)
{
	int csock, rc, i;
	fd_set read;

	csock = make_conn_socket(SERV_PORT);	/* Set up our connection */
	if (csock < 0)				/* Did it fail? */
		exit(3);				/* then die, die, die */
	FD_ZERO(&watching);			/* zero out our fdsets */
	FD_ZERO(&read);
	FD_SET(csock, &watching);	/* add the conn-sock to the watching set */
	while (1)					/* Here's where we start looping */
	{
		/* read was overwritten by select, so we must copy it every time. */
		fdset_copy(&watching, &read);

		/* Call select, which should block until something interesting
		   happens on one of the sockets. */
		rc = select(fdset_max(watching) + 1, &read, (fd_set *) NULL,
					(fd_set *) NULL, (struct timeval *) NULL);

		/* Ferret out the obvious problems. */
		if (rc < 0)				/* ah, geez, i/o-err */
			ttyprintf("Communications error.\nselect: %s\n", strerror(errno));
		else if (rc == 0)		/* does this happen w/ timeval = NULL?? */
		{
			sleep(1);			/* give up the processor a little while */
			continue;
		}

		/* rc must be > 0, so we have something to do. */
		for (i = 0; i < NFDBITS; i++)
			if (FD_ISSET(i, &read))
			{
				/* If select returns with the bit for the connection socket
				   set, then there is a connection waiting to be accept()ed.
				   We need to accept(it) and add it to the watching set. */
				if (i == csock)
				{
					int addr_len, newsock;
					struct sockaddr_in cli_addr;

					/* accept new connection */
					addr_len = sizeof(cli_addr);
					newsock = accept(csock, (struct sockaddr *) &cli_addr,
									 &addr_len);
					if (newsock < 0)
						ttyprintf("Communications error.\naccept: %s\n",
								  strerror(errno));
					else
						FD_SET(newsock, &watching);	/* add it to watching set */
					greet(newsock, &cli_addr);	/* new socket greeting msg */
				}
				/* If this is a non-connection socket, then there is data
				   waiting on one of the sockets. */
				else
				{
					int len;
					char *buffer;

					/* do one of our nifty_read()'s :) */
					len = socket_read(i, &buffer);
					if (len)
						process(i, len, buffer);
				}
			}
	}
}

/* This procedure severs all connections and exits. */
void shut_down(void)
{
	int x;

	for (x=0; x<NFDBITS; x++)
		if (FD_ISSET(x,&watching))
			disconnect(x);
	exit(0);
}
