/* CSci4061 F2005 Assignment 5
* section: 3
* login: hedg0029
* date: 12/07/05
* name: David R. Hedges
* id: 2836226 */

/*
Name:David R. Hedges
S ID: 2836226
x500: hedg0029
Sec : 003

Ver : 2005-12-05T2315-0600 drh encrypt-server.c
Par : 2005-11-23T1853-0600 drh encrypt.c

Desc:

*/

#define _REENTRANT
#include <errno.h>
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include "messages.h"
#define FILESIZELIMIT 1048576

//FILESIZELIMIT 1048576 is 1 MiB, which specifies the largest size we'll allow a file to be, and still keep it in ram (rather than writing to a tempfile)

//shared resources
static int reqBuf[1024];		//now will hold sockfd's of pending client requests
static int bufHead = 0;
static int bufTail = 0;
static int encrypt_log_fd;
static int done = 0;
static int threadCounter = 0;
int debug = 0;
int sockfd;

static pthread_mutex_t bufAccess = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t logAccess = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t doneAccess = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t bufAvail = PTHREAD_COND_INITIALIZER;

//prototypes
void *readerThread (void *arg);
void *encrypterThread (void *arg);


int main(int argc, char **argv) {
	//variables
	int i;
	int num_threads, port_number;
	pthread_t reader;
	pthread_t encrypter[100];
	struct sockaddr_in serv_addr;
	char *errorMsg;
	char writeMsg[1024];


	if (argc != 3 && argc != 4) {
		printf("(#00): argc = %d.\n", argc);
		printf("(#01): Usage: ./encrypt-server num_threads port_number [-v]\n\n");
		return -1;
	}

	if (argc == 4 && strcmp(argv[3],"-v") == 0) {
		debug = 1;
	}

	//convert second argument from string to int
	num_threads = atoi(argv[1]);
	if (num_threads < 1 || num_threads > 100) {
		printf("(#02): num_threads must be between 1 and 100, inclusive. %d is not in this range.\n\n", num_threads);
		return -1;
	}

	//grab port number
	port_number = atoi(argv[2]);
	if (port_number < 1 || port_number > 65535) {
		printf("(#50): port_number must be between 1 and 65,535, inclusive. %d is not in this range.\n\n", port_number);
		return -1;
	}

	//open logfile, don't need to worry about mutex or perror, since no one else is around yet
	if ((encrypt_log_fd = creat("encrypt_log", 0666)) < 0) {
		perror("(#03): Couldn't create file 'encrypt_log'");
		return -1;
	}

	//set up socket to listen; will be used by reader thread
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd < 0) {
		char *errmsg = strerror(errno);
		printf("(#51): Unable to open socket: '%s'\n", errmsg);
		return -1;
	}

	memset((char *) &serv_addr, '0', sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = INADDR_ANY;
	serv_addr.sin_port = htons(port_number);
	if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
		errorMsg = strerror(errno);
		printf("(#52): Unable to bind(): '%s'\n", errorMsg);
	}
	if (listen(sockfd,10) < 0) {
		errorMsg = strerror(errno);
		printf("(#53): Unable to listen(): '%s'\n", errorMsg);
	}

	//spawn the reader thread
	if (pthread_create(&reader,NULL,readerThread,NULL) != 0) {
		perror("(#04): Unable to create reader thread");
	}

	//loop to create up to 100 encrypterThreads
	for (i=0; i<num_threads; i++) {
		if(pthread_create(&encrypter[i],NULL,encrypterThread,NULL) != 0) {
			errorMsg = strerror_r(errno, writeMsg, 1024);
			snprintf(writeMsg, 1024, "main: %s (encrypter[%d])\n", errorMsg, i);
			if (debug) {
				printf("(#05): Error creating encrypter thread[%d]. Error: '%s'\n", i, errorMsg);
			}
			pthread_mutex_lock(&logAccess);
			write(encrypt_log_fd, writeMsg, strlen(writeMsg));
			pthread_mutex_unlock(&logAccess);
			return -1;
		}
	}

	//la de da, threads do things, now let's join them

	//join the reader thread
	if (pthread_join(reader,NULL) != 0) {
		char writeMsg[1024];
		char *errorMsg = strerror_r(errno, writeMsg, 1024);
		snprintf(writeMsg, 1024, "main: %s (reader)\n", errorMsg);
		if (debug) {
			printf("(#06): Error with reader thread rejoining main. Error: '%s'\n", errorMsg);
		}
		pthread_mutex_lock(&logAccess);
		write(encrypt_log_fd, writeMsg, strlen(writeMsg));
		pthread_mutex_unlock(&logAccess);
		return -1;
	}

	for (i=0; i<num_threads; i++) {
		if(pthread_join(encrypter[i],NULL) != 0) {
			char writeMsg[1024];
			char *errorMsg = strerror_r(errno, writeMsg, 1024);
			snprintf(writeMsg, 1024, "main: %s (encrypter[%d])\n", errorMsg, i);
			if (debug) {
				printf("(#07): Error with decrypter thread[%d] rejoining main. Error: '%s'\n", i, errorMsg);
			}
			pthread_mutex_lock(&logAccess);
			write(encrypt_log_fd, writeMsg, strlen(writeMsg));
			pthread_mutex_unlock(&logAccess);
			return -1;
		}
	}

	//close logfile (don't care about mutex, threads are gone)
	if (close(encrypt_log_fd) != 0) {
		perror("(#08): Error closing encrypt_log.");
		return -1;
	}

	return 1;
}





void *readerThread (void *arg) {
	struct sockaddr_in cli_addr;
	int tempsockfd;
	char *errorMsg;
	char writeMsg[1024];

	unsigned int clilen = sizeof(cli_addr);

	while (1) {
		tempsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
		if (tempsockfd < 0) {
			errorMsg = strerror_r(errno, writeMsg, 1024);
			snprintf(writeMsg, 1024, "[reader]: Error accepting connection '%s'\n", errorMsg);
			if (debug) {
				printf("(#53): Error accepting connection. Error: '%s'\n", errorMsg);
			}
			pthread_mutex_lock(&logAccess);
			write(encrypt_log_fd, writeMsg, strlen(writeMsg));
			pthread_mutex_unlock(&logAccess);
		}

		else {
			if(debug) {
				printf("(#54): [reader] Accepted connection %d from %s:%d\n", tempsockfd, inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
			}
			//put the temp sockfd into the shared buffer for a worker thread to pick up
			pthread_mutex_lock(&bufAccess);
			if (bufTail == 1024) bufTail = 0;			//wrap around to beginning of reqBuf
			reqBuf[bufTail++] = tempsockfd;
			pthread_mutex_unlock(&bufAccess);
			pthread_cond_signal(&bufAvail);
		}

	}
	// equivilent of a signal to the encrypterThreads that we're done
	if (debug) {
		printf("(#15): Exiting readerThread.\n");
	}
	pthread_mutex_lock(&doneAccess);
	done = 1;
	pthread_mutex_unlock(&doneAccess);
	pthread_cond_broadcast(&bufAvail);	//broadcast this, so all the threads wake up, and finish what they ahve to do, then exit

	if (debug) {
		printf("(#16): Returning readerThread.\n");
	}
	return NULL;
}

void *encrypterThread (void *arg) {
	//reads a request from the buffer, then opens in_file, and adds integer_add
	//to each byte, and writes the result to in_file.encrypt. furthermore, must
	//log its actions to encrypt_log, have a thread id, and a request counter
	//REQNO

	pthread_mutex_lock(&logAccess);
	int thread_id = threadCounter++;
	pthread_mutex_unlock(&logAccess);
	int i, x;
	int REQNO = 0;
	int sockfd, encryptionKey, filesize;	//out_fd
	//	char *errorMsg;
	char writeMsg[1024], readMsg[1024];
	char *encBuffer;
	char * writePos;

	if (debug) {
		printf("Spawning encrypter thread %d\n", thread_id);
	}

	while (1) {							//loop until a break
		sched_yield();	//will this give another thread a chance to come and get the reqest, perhaps?
		pthread_mutex_lock(&bufAccess);
		while (bufHead == bufTail && !done) {	// bufEmpty()
			//sleep until there's something in the buffer
			pthread_cond_wait(&bufAvail, &bufAccess);
		}
		if (debug) {
			printf("(#19): Thread %d says done is '%d'.\n", thread_id, done);
		}
		if(bufHead == bufTail) {
			if(done) {
				pthread_mutex_unlock(&bufAccess);
				pthread_exit(NULL);
			}
		}
		//get our sockfd
		if (bufHead == 1024) bufHead = 0;			//wrap around to beginning of reqBuf
		sockfd = reqBuf[bufHead++];
		if (debug) {
			printf("(#23): bufHead is %d\n", bufHead);
		}
		pthread_mutex_unlock(&bufAccess);

		//tell the client that we're READY
		x = htons(READY);
		memcpy(writeMsg,&x,sizeof(int));
		if(debug) {
			writeMsg[4] = '\0';
			printf("(%s:%d#%d): Sent READY command\n", __FILE__, __LINE__, thread_id);
		}
		write(sockfd, writeMsg, sizeof(int));

		//listen for ENCRYPT, encKey, size of data
		read(sockfd, readMsg, sizeof(int));
		memcpy(&x, readMsg, sizeof(int));
		if(ntohs(x) != ENCRYPT) {
			if(debug) {
				printf("(%s:%d#%d): Non-ENCRYPT command received. Message received was %d.\n.", __FILE__, __LINE__, thread_id, ntohs(x));
			}
			x = htons(UNKNOWN_REQUEST);
			memcpy(writeMsg, &x, sizeof(int));
			write(sockfd, writeMsg, strlen(writeMsg));

			//log error to logfile
			snprintf(writeMsg, 1024, "[%d] %d: Bad request from socket %d, closing connection.\n", thread_id, REQNO, sockfd);
			pthread_mutex_lock(&logAccess);
			write(encrypt_log_fd, writeMsg, strlen(writeMsg));
			pthread_mutex_unlock(&logAccess);

			//close socket, then continue
			close(sockfd);
			continue;
		}

		if (debug) {
			printf("(%s:%d#%d): ENCRYPT message received.\n", __FILE__, __LINE__, thread_id);
		}

		read(sockfd, readMsg, sizeof(int));
		memcpy(&x, readMsg, sizeof(int));
		encryptionKey = ntohl(x);

		if (debug) {
			printf("(%s:%d#%d): encryptionKey received (%d).\n", __FILE__, __LINE__, thread_id, encryptionKey);
		}

		read(sockfd, readMsg, sizeof(int));
		memcpy(&x, readMsg, sizeof(int));
		filesize = ntohs(x);

		if (debug) {
			printf("(%s:%d#%d): filesize received (%d).\n", __FILE__, __LINE__, thread_id, filesize);
		}

		if (filesize > FILESIZELIMIT) {
			//store the file in a tmpnam_r(char *result) file
			//ref http://www.gnu.org/software/libc/manual/html_node/Temporary-Files.html

			printf("(%s:%d): filesize (%d) has exceeded the hard-coded FILESIZELIMIT (%d), however, this case has not yet been handled.\n", __FILE__, __LINE__, filesize, FILESIZELIMIT);

			//actually, not going to deal with this case at the moment.
		}

		else {	//the case when we keep the entire file in ram
			//malloc space for filesize
			encBuffer = (char *) malloc(filesize * sizeof(char));
			if (debug) {
				printf("(%s:%d#%d): filesize is %d.\n", __FILE__, __LINE__, thread_id, filesize);
			}

			//read data into the encBuffer
			ssize_t readbytes, writebytes = 0;
			writePos = encBuffer;
			while (writebytes < filesize) {
				//read data into encBuf, 1024 bytes at a time
				int bytesToRead;
				if (filesize - writebytes > 1024) {
					bytesToRead = 1024;
				}
				else {
					bytesToRead = filesize - writebytes;
				}
				readbytes = read(sockfd, writePos, bytesToRead);
				if (debug) {
					printf("(%s:%d#%d): readbytes was %d.\n", __FILE__, __LINE__, thread_id, readbytes);
				}
				writebytes += readbytes;
				writePos += readbytes;
			} //the whole file should be in encBuffer now

			if (debug) {
				printf("(%s:%d#%d): Read all data from sockfd into encBuffer.\n", __FILE__, __LINE__, thread_id);
			}

			//add the encryptionKey to each element in the char[]
			for(i = 0; i<filesize; i++) {
				*(encBuffer+i) += encryptionKey;
			}

			if (debug) {
				printf("(%s:%d#%d): Encrypted encBuf.\n", __FILE__, __LINE__, thread_id);
			}

			//result
			x = htons(RESULT);
			memcpy(writeMsg, &x, sizeof(int));
			write(sockfd, writeMsg, sizeof(int));
			
			if (debug) {
				printf("(%s:%d#%d): Sent RESULT command.\n", __FILE__, __LINE__, thread_id);
			}
			
			//size

			x = htons(filesize);
			memcpy(writeMsg, &x, sizeof(int));
			write(sockfd, writeMsg, sizeof(int));
			
			if (debug) {
				printf("(%s:%d#%d): Sent filesize (%d).\n", __FILE__, __LINE__, thread_id, filesize);
			}

			//write the encrypted encBuffer back to the client
			readbytes = 0;
			writePos = encBuffer;
			int bytesToWrite;
			while (readbytes < filesize) {
				//read data into encBuf, up to 1024 bytes at a time
				if (filesize - readbytes > 1024) {
					bytesToWrite = 1024;
				}
				else {
					bytesToWrite = filesize - readbytes;
				}
				writebytes = write(sockfd, writePos, bytesToWrite);
				readbytes += writebytes;
				writePos += writebytes;
				if (debug) {
					printf("(%s:%d#%d): writebytes was %d.\n", __FILE__, __LINE__, thread_id, writebytes);
				}
			}
			
			if (debug) {
				printf("(%s:%d#%d): Sent encrypted file back to client.\n", __FILE__, __LINE__, thread_id);
			}

			//free the memory that we malloced
			free(encBuffer);

			//log the action that was just completed to the logfile
			snprintf(writeMsg, 1024, "[%d] %d: %d %d\n", thread_id, REQNO, sockfd, encryptionKey);
			pthread_mutex_lock(&logAccess);
			write(encrypt_log_fd, writeMsg, strlen(writeMsg));
			pthread_mutex_unlock(&logAccess);
		}  //close else

		read(sockfd, readMsg, sizeof(int));
		memcpy(&x, readMsg, sizeof(int));
		if (ntohs(x) == DONE) {
			//close socket, then end this iteration
			close(sockfd);
			if (debug) {
				printf("(%s:%d#%d): DONE message received. Closing socket.\n", __FILE__, __LINE__, thread_id);
			}
		}

		else if (ntohs(x) == NOTDONE) {
			//put the sockfd back on the queue
			if (debug) {
				printf("(%s:%d#%d): NOTDONE message received. Inserting sockfd %d back into reqBuf.\n", __FILE__, __LINE__, thread_id, sockfd);
			}
			pthread_mutex_lock(&bufAccess);
			if (bufTail == 1024) bufTail = 0;			//wrap around to beginning of reqBuf
			reqBuf[bufTail++] = sockfd;
			pthread_cond_signal(&bufAvail);
			pthread_mutex_unlock(&bufAccess);
		}

		else {
			//bad request
			if(debug) {
				printf("(#56): Non-DONE/NOTDONE command received. Message received was %d\n.", ntohs(x));
			}
			x = htons(UNKNOWN_REQUEST);
			memcpy(writeMsg, &x, sizeof(int));
			write(sockfd, writeMsg, sizeof(int));

			//log error to logfile
			snprintf(writeMsg, 1024, "[%d] %d: Bad request from socket %d, closing connection.\n", thread_id, REQNO, sockfd);
			pthread_mutex_lock(&logAccess);
			write(encrypt_log_fd, writeMsg, strlen(writeMsg));
			pthread_mutex_unlock(&logAccess);

			//close socket, then continue
			close(sockfd);
		}

		REQNO++;
	} // close while(1)

	return NULL;
}

/*
	to do, time permitting:

		add ctrl-c signal handler to cleanly finish what it's doing, close connections, and then exit

*/
