
#include <stdio.h>
#include <getopt.h>
#include <pthread.h>
#include <unistd.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>

#define TOTAL_PLATES 10
#define ITEMS_PER_PLATE 20
#define TRIPS_PER_GUEST 5

#define MODE_DEADLOCK 0
#define MODE_SINGLE   1
#define MODE_MULTI    2
#define MODE_FAIR     3

/*
mode - The mode of the simulation, either, MODE_DEADLOCK,
MODE_SINGLE, and so forth.  You need this in order to
implement the right behavior in the monitor.
*/
static int mode;

/* 
nthreads - The total number of threads in the simulation.
You might find this value useful for keeping track of threads.
*/
static int nthreads;

/* The plate[] array records the number of items on each plate. */
/* This global variable should be carefully protected by a monitor. */
static int plates[TOTAL_PLATES];

/*
buffet_get() is called by each guest to get items from
one plate on the buffet.  Note that each guest will call
buffet_get several times as he/she walks the buffet line.
The arguments are as follows:
	plate - the number of the plate to modify
	amount - the number of items to get
	thread_id - the ID of the calling thread
*/

/*
Note that this implementation of buffet_get is terrible.
It is broken in three distinct ways:
1 - It doesn't synchronize access to the plate array.
2 - It busy waits when no resources are available. 
3 - It doesn't prevent deadlock.
*/

void buffet_get( int plate, int amount, int thread_id )
{
	if(mode==MODE_DEADLOCK) {

		while(plates[plate]<amount) {
			/* yikes, a busy wait! */
		}
		plates[plate] -= amount;

	} else {
		printf("mode not implemented yet.\n");
		abort();
	}
}

/*
buffet_put() is called by each guest to return items to
the buffet after standing around for a while.  Like buffet_get,
each guest will call this function several times in order
to return items to each plate.  The arguments are the
same as those of buffet_put().
*/

void buffet_put( int plate, int amount, int thread_id )
{
	if(mode==MODE_DEADLOCK) {
		plates[plate] += amount;
	} else {
		printf("mode not implemented yet.\n");
		abort();
	}
}

/*****************************************************/
/* DO NOT CHANGE ANYTHING BELOW THIS LINE            */
/* (Of course, you may read if you are interested.) */
/*****************************************************/

/* Return an integer between low and hi */

int randrange( int low, int hi )
{
	return low+rand()%(hi-low);
}

/* Sleep for a random amount of time. */

void wait_a_while()
{
	usleep(randrange(10,1000));
}

/* 
guest_thread() implements the behavior of each guest.
Each guest either walks from low plates to
high places (direction==1) or vice versa (direction==-1).
After obtaining something from each plate, the guest
stops to talk for a while, and then returns the items.
After taking enough trips through, the guest is done.
*/

void * guest_thread( void * threadidptr )
{
	int i, j;
	int threadid = * (int *) threadidptr;
	char request[TOTAL_PLATES];
	int start, stop, direction;
 	time_t starttime, stoptime, elapsed;
  
	for(i=0;i<TRIPS_PER_GUEST;i++) {

		printf("guest %d: starting trip %d\n",threadid,i);

		/*
		Even numbered guests go from low to high,
		while odd numbered guests go from high to low.
		*/

		/*
		NOTE: There was originally a bug in this
		if-else statement.  It is fixed now.
		*/

		if(threadid%2) {
			start = 0;
			stop = TOTAL_PLATES;
			direction = 1;
		} else {
			start = TOTAL_PLATES-1;
			stop = -1;
			direction = -1;
		}

		for(j=start;j!=stop;j+=direction) {
			request[j] = randrange(1,ITEMS_PER_PLATE);

			printf("guest %d: getting %d items from plate %d\n",threadid,request[j],j);

			starttime = time(0);
			buffet_get(j,request[j],threadid);
			stoptime = time(0);
			elapsed = stoptime-starttime;

			if(mode==MODE_FAIR && elapsed>5) {
				fprintf(stderr,"*** THREAD %d DIED OF STARVATION ***\n",threadid);
				exit(1);
			}
			wait_a_while();
		}

		wait_a_while();

		for(j=start;j!=stop;j+=direction) {
			printf("guest %d: returning %d items to plate %d\n",threadid,request[j],j);
			buffet_put(j,request[j],threadid);
		}

		printf("guest %d: all done with trip %d\n",threadid,i);
	}

	return 0;
}

/*
The main function examines the command line arguments,
selects the running mode, then creates one thread for
every guest in the simulation.  It knows that the
simulation is done when pthread_join succeeds for
every thread in the simulation.  At the very end,
it checks to make sure that all items have been
returned to the buffet.
*/

int main( int argc, char *argv[] )
{
	const char *modename;
	pthread_t *threads;
	int *threadids;
	int result;
	int errors=0;
	int i;

	if(argc<3) {
		fprintf(stderr,"use: %s <nthreads> <deadlock|single|multi|fair>\n",argv[0]);
		return 1;
	}
	
	srand(time(0));

	nthreads = atoi(argv[1]);
	modename = argv[2];

	if(!strcmp(modename,"deadlock")) {
		mode = MODE_DEADLOCK;
	} else if(!strcmp(modename,"single")) {
		mode = MODE_SINGLE;
	} else if(!strcmp(modename,"multi")) {
		mode = MODE_MULTI;
	} else if(!strcmp(modename,"fair")) {
		mode = MODE_FAIR;
	} else {
		fprintf(stderr,"bad mode name: %s\n",modename);
		return 1;
	}

	threadids = malloc(sizeof(int)*nthreads);
	threads = malloc(sizeof(pthread_t)*nthreads);

	for(i=0;i<TOTAL_PLATES;i++) {
		plates[i] = ITEMS_PER_PLATE;
	}

	printf("main: starting %d threads\n",nthreads);

	for(i=0;i<nthreads;i++) {
		threadids[i] = i;
		result = pthread_create(&threads[i],0,guest_thread,&threadids[i]);
		if(result!=0) {
			printf("main: couldn't create thread %d, aborting...\n",i);
			return 1;
		}
	}

	for(i=0;i<nthreads;i++) {
		pthread_join(threads[i],0);
	}

	printf("main: all threads safely complete!\n");
	printf("main: checking plates...\n");

	for(i=0;i<TOTAL_PLATES;i++) {
		if(plates[i]!=ITEMS_PER_PLATE) {
			printf("main: plate %d only has %d items!  (it should have %d)\n",i,plates[i],ITEMS_PER_PLATE);
			errors++;
		}
	}

	if(errors==0) {
		return 0;
	} else {
		return 1;
	}
}
