#include "cs537.h"
#include "request.h"
#define TRUE 1
#define FALSE 0
#define fifo "FIFO"
#define sff "SFF"
#define DEBUG 0
#define STATIC_FILE_SIZE 13370666
/*
** michael bethencourt & adam vail
*/



/* 
 * server.c: A very, very simple web server
 *
 * To run:
 *  server <portnum (above 2000)>
 *
 * Repeatedly handles HTTP requests sent to this port number.
 * Most of the work is done within routines written in request.c
 *
 * CS537 Students: You will be modifying this module
 */
struct request * head_request = NULL; 
struct request * tail_request = NULL; 
struct thread* threads;
static volatile int buffer_count;
static int buffer_max;
static int thread_count;

pthread_mutex_t mutex;
pthread_mutex_t worker_create_mutex;
pthread_cond_t fill, empty;

struct request* consumer();
void* worker(void *threadid);
void getargs(int *port, int *threads, int * buffers, char *is_fifo, int argc, char *argv[]);
int  get_file_size(char * );

/* Debug functions...
void print_one_request_node(struct request* req);
void print_list(struct request* req);
*/

/*
 * CS537: Parse the new arguments too
 */
void getargs(int *port, int *threads, int * buffers, char *is_fifo, int argc, char *argv[])
{
	if (argc != 5) {
		fprintf(stderr, "Usage: %s <port> <threads> <buffers> <schedule>\n", argv[0]);
		exit(1);
	}
	*port = atoi(argv[1]);
	*threads = atoi(argv[2]);
	*buffers = atoi(argv[3]);

	if(strcmp(fifo, argv[4]) == 0){
		*is_fifo = TRUE;
	}
	else if(strcmp(sff, argv[4]) == 0){
		*is_fifo = FALSE;
	}
	else{   
		fprintf(stderr, "Usage: %s <port> <threads> <buffers> <schedule>\n", argv[0]);
		exit(1);
	}
}

int main(int argc, char *argv[])
{

	int listenfd, connfd, port, clientlen;
	char is_fifo;
	struct sockaddr_in clientaddr;
	struct request *new_request; 
	struct request *found_request; 
	int x = 0;
	double server_start_time = get_time();

	getargs(&port,&thread_count, &buffer_max, &is_fifo, argc, argv);
	threads = Malloc(sizeof(struct thread) * thread_count);
	Pthread_mutex_init(&mutex, 0);
	Pthread_mutex_init(&worker_create_mutex, 0);
	/* 
	 * CS537: Create some threads...
	 */
	if(DEBUG) printf("MASTER locking worker_create_mutex...\n");
	Pthread_mutex_lock(&worker_create_mutex);
	for(x=0 ; x <thread_count; x++ ){
		threads[x].int_id = x;
		Pthread_create(&threads[x].thread_id, NULL, &worker, NULL);
	}
	if(DEBUG) printf("MASTER unlocking worker_create_mutex...\n");
	Pthread_mutex_unlock(&worker_create_mutex);
	
	listenfd = Open_listenfd(port);
	while (1) {
		clientlen = sizeof(clientaddr);
		connfd = Accept(listenfd, (SA *)&clientaddr, (socklen_t *) &clientlen);
		/* 
		 * CS537: Don't handle the request in the main thread.
		 * Save the relevant info in a buffer and have one of the worker threads 
		 * do the work.
		 */ 
		new_request = Malloc(sizeof(struct request));
		new_request->stat = Malloc(sizeof(struct request_stats));
		
		/* arrival is used to store the absoulte start time */
		new_request->stat->arrival = get_time();
		new_request->stat->dispatch = 0;
		/* arrival_relative is used to store the relative start time */
		new_request->stat->arrival_relative = get_time() - server_start_time;
		new_request->connfd = connfd;
		// get file size
		
		// Read the connection fd's content into the content argument  
		Rio_readinitb(&new_request->rio, new_request->connfd);
		Rio_readlineb(&new_request->rio, new_request->content, MAXLINE);
		// populate size field, if its not fifo 
		if(!is_fifo)
			new_request->size = get_file_size(new_request->content);
		
		Pthread_mutex_lock(&mutex);
		
		// set up the original request age stat
		new_request->stat->age = buffer_count;
		if(DEBUG) printf("PRODUCER CHECKS FOR IT TO NOT  BE FULL\n");
		while(buffer_count == buffer_max)
			Pthread_cond_wait(&empty, &mutex);
		if(DEBUG) printf("END CHECKS FOR IT TO NOT BE FULL\n");

		// find where we should insert this request into the queue
		found_request = tail_request;
		
		// this loop is used if we are not using fifo
		while(!is_fifo && found_request!=NULL){
			// found_request is temporarily now a looping node
			if(found_request->size <=  new_request->size)
				break;
			new_request->stat->age--;
			found_request->stat->age++;
			found_request = found_request->prev;
		}

		// if it is not going to be the FIRST request 
		if(found_request != NULL){
			new_request->next = found_request->next;
			found_request->next = new_request;
		}
		new_request->prev = found_request;
		
		// if it is the last request node, then update tail
		if(found_request == tail_request)
			tail_request = new_request;

		// if it was the first
		if(found_request == NULL){
			new_request->next = head_request;
			head_request = new_request;
		} 

		// if it is not at the end
		if(new_request->next != NULL)
			new_request->next->prev = new_request;
			
		//print_list(head_request);

		buffer_count++;
		Pthread_cond_signal(&fill);
		Pthread_mutex_unlock(&mutex);
	}

}
int  get_file_size(char * content)
{
	// note: could improve by cleaning up unused variables
	struct stat sbuf;
	char method[MAXLINE], uri[MAXLINE], version[MAXLINE];
	char filename[MAXLINE], cgiargs[MAXLINE];

	sscanf(content, "%s %s %s", method, uri, version);

	// Not static, just return a big number
	if(!requestParseURI(uri, filename, cgiargs)) return STATIC_FILE_SIZE;
	// File not found, just return 0
	if(stat(filename, &sbuf) < 0) return 0;
	return (long) sbuf.st_size;
}

 
void* worker(void *t) {
	// my_thread points to the struct representing the current thread running this worker
	struct thread *my_thread;
	struct request *single_request;
	int x=0;
	pthread_t my_thread_id = pthread_self(); // no errors returned, no need for wrapping
	
	// find out who I am by looping through them
	Pthread_mutex_lock(&worker_create_mutex);
	while((my_thread = &threads[x++])->thread_id != my_thread_id){
		if(x>=thread_count)
			unix_error("Thread initialization error -- could not find self.");
	}

	Pthread_mutex_unlock(&worker_create_mutex);

	while(1){
		single_request = consumer();
		single_request->handled_by = my_thread;

		requestHandle(single_request);
		
		Close(single_request->connfd);
		free(single_request->stat);
		free(single_request);
	}
	return 0;
}

struct request* consumer(){
	if(DEBUG) printf("Getting locked...\n");
	Pthread_mutex_lock(&mutex);
	if(DEBUG) printf("Past locked...\n");

	// wait until there is an item in the buffer
	if(DEBUG) printf("Waiting...\n");
	//if(DEBUG) print_list(head_request);
	while(buffer_count == 0)
		Pthread_cond_wait(&fill, &mutex);
	if(DEBUG) printf("Past waiting...\n");

	// get an item out of the buffer
	struct request *single_request = head_request;

	//  do statistics stuff
	single_request->stat->dispatch = get_time() - single_request->stat->arrival;
	
	// Remove head request from list...
	head_request = head_request->next;

	// Empty list
	if(head_request==NULL) tail_request = NULL;

	// set prev since it is the new head_request
	else head_request->prev = NULL;
	
	buffer_count--;
	if(DEBUG) printf("Request gotten, signalling empty\n");
	// signal that there is an empty spot in the buffer
	Pthread_cond_signal(&empty);
	if(DEBUG) printf("Done signaling empty, now unlocking\n");
	Pthread_mutex_unlock(&mutex);

	return single_request;
}
/*
void print_list(struct request* req){

	printf("----------------------------------------------------------\n");
	fflush(stdout);
	while(req != NULL){
		print_one_request_node(req);
		req = req->next;
	}
	printf("------------------------------------------------------------\n");
	fflush(stdout);
}

void print_one_request_node(struct request* req){
	if(req == head_request) printf("HEAD ");
	else                    printf("     ");
	if(req == tail_request) printf("TAIL ");
	else                    printf("     ");
	if(req == NULL) printf("     NULL    -----       XXXXXXXXXXXXX\n");
	else printf("%08lx --> size: %08d | fd: %02d | age: %04d | dispatch: %f | rel: %f | %08lx<-X->%08lx\n",
			req, req->size, req->connfd,
			req->stat->age, req->stat->dispatch,
			req->stat->arrival_relative, 
			req->prev, req->next);
	fflush(stdout);
}

*/
