/****************************************************************/
/* hw7.c                                                        */
/* Justin Hartman                                               */
/* EECS 338 - Spring 2003                                       */
/* Assignment 7                                                 */
/****************************************************************/

/****************************************************************/
/* Written in Microsoft Visual C++ .NET                         */
/* Compiled and executed on CWRU's EECS department              */
/* lab's (Olin 404.5) SUN boxes using SSH and                   */
/* secure FTP                                                   */
/****************************************************************/

#include "hw7.h"

struct timespec sleep_time = {0, GUI_TIMEOUT_NS};

WORLDSTATE		state[ROW][COL];	/* The state matrix */
unsigned short	car_col;			/* Shortcut to keep track of the car */
BOOL			done;				/* Whether or not we're finished */

pthread_mutex_t	mutex1 = PTHREAD_MUTEX_INITIALIZER;		/* Mutex for the state matrix, car_col, and conditional */
pthread_mutex_t	mutex_done = PTHREAD_MUTEX_INITIALIZER; /* Mutex for the done variable */

pthread_cond_t	cond1 = PTHREAD_COND_INITIALIZER;		/* The GUI update conditional */

int main(int argc, char *argv[]) {
	unsigned short i, j;

	pthread_t	thread_state,	/* World state updater thread */
				thread_gui,		/* GUI updater thread */
				thread_car;		/* Car input updater thread */

	/* Enforce the correct usage */
	if (argc != 1) {
		printf("\nUsage: \"%s\"\n", argv[0]);
		exit(1);
	}
	
	/* Initialize the world */
	done = FALSE;
	car_col = 9;
	for (i=0; i<ROW; i++) {
		state[i][0] = OBSTACLE;
		for (j=1; j<COL-1; j++) {
			state[i][j] = FREE;
		}
		state[i][COL-1] = OBSTACLE;
	}
	
	CLS; /* Clear the screen */
	
	/* Create the worker threads */
	pthread_create(&thread_gui, NULL, gui, (void *) NULL);
	pthread_create(&thread_state, NULL, world, (void *) NULL);
	pthread_create(&thread_car, NULL, car, (void *) NULL);
	
	/* Join up with them when they finish */
	pthread_join(thread_state, (void **)NULL);
	pthread_join(thread_gui, (void **)NULL);
	pthread_kill(thread_car, SIGTERM);	/* If thread_state and thread_gui join up, that means they're done, and thread_car is
										   spinning on getchar(). We pthread_kill() it to get it to stop looking for input */
	pthread_join(thread_car, (void **)NULL);
	
	return 0;
}

void * gui(void * junk){
	unsigned short i, j;
	static char buffer[COL + 1];

	pthread_mutex_lock(&mutex_done);
	while (done == FALSE) {
		pthread_mutex_unlock(&mutex_done);

		pthread_mutex_lock(&mutex1);
		
		/* Wait to be signalled on a world state update */
		pthread_cond_wait(&cond1, &mutex1);
		
		for (i=0; i<ROW; i++) {
			/* Build a string with the appropriate characters for the states in the matrix */
			for (j=0; j<COL; j++) {
				if ((i==CARROW) && (j==car_col) && (state[i][j] != CRASH)) buffer[j] = CAR_CHAR;
				else {
					if (state[i][j] == FREE) buffer[j] = FREE_CHAR;
					if (state[i][j] == OBSTACLE) buffer[j] = OBSTACLE_CHAR;
					if (state[i][j] == CRASH) buffer[j] = CRASH_CHAR;
				}
			}
			/* LOCATE and print this string */
			LOCATE(i, 0);
			printf("%*s", COL, buffer);
		}
		/* Move the cursor off the game display */
		LOCATE(ROW+1, 0);

		/* Flush stdout, or it won't update properly */
		fflush(stdout);

		pthread_mutex_unlock(&mutex1);
		pthread_mutex_lock(&mutex_done);
	}
	
	pthread_mutex_unlock(&mutex_done);

	return (void *) NULL;
}

void * world(void * junk){
	unsigned short i, j, n;
	BOOL crash = FALSE;

	pthread_mutex_lock(&mutex_done);
	while (done == FALSE) {
		pthread_mutex_unlock(&mutex_done);
		pthread_mutex_lock(&mutex1);
		
		/* Check for a collision */
		if (state[CARROW - 1][car_col] == OBSTACLE) {
			pthread_mutex_lock(&mutex_done);
			done = TRUE;
			crash = TRUE;
			pthread_mutex_unlock(&mutex_done);
			LOCATE(ROW, 0);
			printf("Crash!\n");
		}

		/* Shift the existing matrix down a row */
		for (i=ROW-1; i>0; i--) {
			for (j=0; j<COL; j++) {
				state[i][j] = state[i-1][j];
			}
		}

		/* Randomly generate a new row */
		state[0][0] = OBSTACLE;
		state[0][COL-1] = OBSTACLE;
		for (i=1; i<COL-1; i++) {
			TRAND(10, n)
			if (n>3) state[0][i] = FREE;
			else state[0][i] = OBSTACLE;
		}
		
		/* If crash == TRUE, then update the world state in the car's position */
		if (crash == TRUE) state[CARROW][car_col] = CRASH;

		/* Signal the GUI for an update */
		pthread_cond_signal(&cond1);
		pthread_mutex_unlock(&mutex1);
		
		/* If we're not done, then sleep for the specified interval */
		pthread_mutex_lock(&mutex_done);
		if (done == FALSE) {
			pthread_mutex_unlock(&mutex_done);
			nanosleep(&sleep_time, NULL);
			pthread_mutex_lock(&mutex_done);
		}
	}
	
	pthread_mutex_unlock(&mutex_done);

	return (void *) NULL;
}

void * car(void * junk){
	char c;

	pthread_mutex_lock(&mutex_done);
	while (done == FALSE) {
		pthread_mutex_unlock(&mutex_done);
		c = getchar();
		if ((c=='q') || (c=='j') || (c=='k')) {
			/* Lock the mutex */
			pthread_mutex_lock(&mutex1);
			
			if (c=='q') {
				/* Game is over */
				pthread_mutex_lock(&mutex_done);
				done = TRUE;
				pthread_mutex_unlock(&mutex_done);
				LOCATE(ROW, 0);
				printf("See you later!\n");
			} else if (c=='j') {
				/* Move left */
				car_col -= 1;
				if (state[CARROW][car_col] != FREE) {
					pthread_mutex_lock(&mutex_done);
					done = TRUE;
					pthread_mutex_unlock(&mutex_done);
					state[CARROW][car_col] = CRASH;
					LOCATE(ROW, 0);
					printf("Crash!\n");
				}
			} else if (c=='k') {
				/* Move right */
				car_col += 1;
				if (state[CARROW][car_col] != FREE) {
					pthread_mutex_lock(&mutex_done);
					done = TRUE;
					pthread_mutex_unlock(&mutex_done);
					state[CARROW][car_col] = CRASH;
					LOCATE(ROW, 0);
					printf("Crash!\n");
				}
			}
			
			/* Release the mutex and signal the gui */
			pthread_cond_signal(&cond1);
			pthread_mutex_unlock(&mutex1);
		}
		
		pthread_mutex_lock(&mutex_done);
	}
	
	pthread_mutex_unlock(&mutex_done);

	return (void *) NULL;
}