/*****************************************************************************
* 	Project 	: Network Based Message Queue System		    **
*	Course		: CIS 650 - Software Engineering		    **
*	Location 	: /research/paraducks3/courses/cis650/proj/rt	    **
*	File		: comm.cc					    **
*	Description 	: Communication Module. The class description	    **
*****************************************************************************/

#include <rtincl.h>

/* The constructor of the Communication module creates a connection (socket)
and binds an address to it */

CommunicationModule::CommunicationModule()
{ /* create the communication module */
	struct 	sockaddr_in  servAddr;
	int on;
	

	/* create the socket and bind it to the port number */

	ret = socket(AF_INET, SOCK_STREAM, 0);
	/* create a socket */
/*
 	DEBUG("sockFd = %d\n", ret);
*/
        err_dump(ret, "Can't open stream socket");

	parentConn = new Connection(ret); /* put the value in it */
	/* the parent connection on which all the requests for new connections
	are received */
	/* Note : the parent connection does not go on the list of connections,
	only the new connections (other programs) are put on the list */

	/* Next set the socket options */
	/* First option - resuse address */
	on = 1; /* enable the option */
        ret = setsockopt(parentConn->sockId, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
	err_dump(ret, "can't set SO_REUSEADDR socket option on parent connection");
	/* second option - keep connection between client and this alive */
 	on = 1; /* enable the option */
        ret = setsockopt(parentConn->sockId, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on));
        err_dump(ret, "can't set SO_KEEPALIVE option");
	/* enable the signal catcher to catch the SIGPIPE signal in case a 
	client dies */
	signal(SIGPIPE, sig_pipe_catcher);

	/* Then bind the local address so that the client can connect */

 	bzero((char *) &servAddr, sizeof(servAddr)); /* clear it */
        servAddr.sin_family     	= AF_INET; /* address family inet */
        servAddr.sin_addr.s_addr        = htonl(INADDR_ANY); /* any client */
        servAddr.sin_port       	= htons(SERV_TCP_PORT); /* port no */
/*
        DEBUG("servAddr.sin_port = %d\n", ntohs(servAddr.sin_port));
*/
	
	/* now bind the address and port number */
	ret = bind(parentConn->sockId, (struct sockaddr *) &servAddr, sizeof(servAddr)) ;
        err_dump(ret,"can't bind local address");

	/* listen for 5 concurrent connections */
	ret = listen(parentConn->sockId,5); 
	err_dump(ret, "Can't listen");

	/* Now create a list of connections */	
	listOfSocketDescriptors = new ListOfConnections();
}

CommunicationModule::~CommunicationModule(void)
{
	delete parentConn;
	/* destroy the first connection - no new connections can be accepted
	now */
	/* then delete the list of all connections */
	delete listOfSocketDescriptors;	
}

int	CommunicationModule::accept_new_connection(void)
{
	struct sockaddr_in	cliAddr;
	Connection *newConn;
	int cliLen; 	
	int newSockFd;
	
	cliLen = sizeof(cliAddr); /* the size of the structure */
	/* Accept a new connection from the requesting process */
	ret = accept(parentConn->sockId, (struct sockaddr *) &cliAddr, &cliLen);
	err_dump(ret, "Error in accept() on parent socket id");

	newConn = new Connection(ret); 
		/* initialise a new connection with this value */
	if(newConn == (Connection *)NULL) return FAILURE;

	/* Next, add this connection to the list of connections */
	listOfSocketDescriptors -> add_item(newConn->sockId);
	DEBUG("received new connection sockId = %d\n", newConn->sockId);

	/* update the statistics module */
	stats->noOfConnections ++;

	return SUCCESS;
}

int	CommunicationModule::wait_for_messages(void)
{
int 	maxConnId; /* the max of all socket ids */
int 	sock;  /* temp. socket id */
int	noOfSockets; /* that are ready to read after select call */
Message	*m;
Connection *c;
int 	empty;

	FD_ZERO(&readyToRead); /* initialise the set to zero */
	FD_SET(parentConn->sockId, &readyToRead); /* put parent in this set */
/*
	DEBUG("ready + parent %d\n", parentConn->sockId);
*/

	empty =  listOfSocketDescriptors->is_empty();
	if (empty != 1)
	{
	/* if the list of socket descriptors is not empty */
/*
		DEBUG("sock list not empty!%d\n", empty);
*/

		do {
	/* get the next descriptor from the list of socket descriptors */
			sock = listOfSocketDescriptors	-> next();

			DEBUG("List --> %d\n", sock);

			if (sock > 0)
	/* if it is valid */
			{
	/* add the socket descriptor to the ready to read set of socket 
	   descriptors. */
				FD_SET(sock, &readyToRead); 	
				DEBUG("ready + %d\n", sock);
			}
		} while (sock > 0);
	/* there are no more descriptors in the socket list */
		maxConnId = listOfSocketDescriptors -> max(); 
		/* calculate the maximum socket descriptor */
	}
	else {
	/* there is only the parent descriptor in the list */
		maxConnId = parentConn->sockId;
	}

	DEBUG("Going for select... max = %d\n", maxConnId);
	/* now the set of all socket descriptors is ready. Go for select call */
	ret = select(maxConnId+1, &readyToRead, (fd_set *)0, (fd_set *)0, (struct timeval *) NULL);
	/* Wait indefinitely till there are a set of sockets ready to read */
	err_dump(ret, "error in select call in wait_for_messages"); 

	/* Now get the socket descriptors  from the ready to read set */
	noOfSockets = ret;
	/* there are ret number of socket descriptors that are ready to read */
/*
	DEBUG("noOfSockets ready to read = %d\n", noOfSockets);
*/

	if (FD_ISSET(parentConn->sockId, &readyToRead))
	{ /* parent connection is ready to read */
		accept_new_connection(); 
		/* get new connection */
		noOfSockets --; 
		/* that are ready to read decremented after a new conn is 
		established */
	}
	/* Now get the other messages */
	/* Get one socket descriptor at a time and check if its ready to read */
	
	c = new Connection(0); /* initialise it with some dummy arg */
	while(noOfSockets != 0)
	{
		/* Check if the socket descriptor is ready to read */
		sock = listOfSocketDescriptors	-> next();
		if(FD_ISSET(sock, &readyToRead))
		{ 	/* if its ready to read then  read from this connection */
			c->sockId = sock; /* put sock in connection */
			m = new Message(); /* create a new message */
			if(get_message(c, m) == SUCCESS) 
			{/* get message m from c */
				r->process_message(c,m); /* check the message */
			}
			noOfSockets --;
			/* that are ready to read */
		}
	}

	/* and reset the list - so it can start from the beginning again */
	listOfSocketDescriptors -> reset();
	return SUCCESS;
}
	

int 	CommunicationModule::get_message(Connection *c, Message *m)
/* get message  from the connection */
{
char buf[MAX_MESSAGE_SIZE];
struct  iovec   iov[2];
int	readRet;

 
	iov[0].iov_len = sizeof(MsgHeader);
	iov[0].iov_base = (char *) m->header;
	iov[1].iov_len = MAX_MESSAGE_SIZE;
	iov[1].iov_base = (char *) m->data;
	readRet = readv(c->sockId, &iov[0], 2);
/*
	printf("Got op(%d) all %d header size = %d, message size = %d, message = %s\n",
		m->header->opcode,readRet, iov[0].iov_len, iov[1].iov_len, m->data);
	m->data[m->header->msgLength] = 0;
*/
/*
	DEBUG("Received from readv length = %d\n", readRet);
	err_dump(readRet, "readv error");
*/

	if((readRet == 0) || (readRet < 0))
	{
		DEBUG("RECEIVED End of FILE EOF on socket \n");
		close(c->sockId);
		listOfSocketDescriptors -> delete_item(c->sockId);
		/* and update the statistics module */
		stats -> noOfConnections --; 
		/* and then see if the descriptor is in the waiting list - in 
		that case delete the socket entry from there. */
		wl -> check_and_delete(c->sockId);
		delete c;
		return FAILURE;
	}
	else 
	{
	DEBUG("Received header  opcode = %d len = %d, type = %d, uniqueId = %d\n",
m->header->opcode, m->header->msgLength, m->header->msgType, m->header->uniqueId
);
        DEBUG("Received  data - %s\n", m->data);
	}
	
	return SUCCESS;
}


