/**
*  Author: Eric Bergstrom
*  Title: stattest.c
*  Description: This program records the order that the NUM processes
*	finishes in an array.  This is repeated SAMPLE times and averaged.
*	The output of the program shows each process along with the a value
*	representing its average order.  The lower the number, the higher 
*	the priority.  Since we are testing a probabilistic scheduling
*	algorithm, the answers will vary each time it is run.
*	Lottery tickets are assigned at the command line.
*	
*	USAGE: stats [ticket1] [ticket2] ...
*	if no ticket is specified for a process, it defaults to 1.
* 
*  Issues:  This program is a little buggy, containing some 
*	deadlock issues requiring a restart when it locks up.  
* 	A watchdog timer fixes some of the deadlock, so for the 
* 	most part it functions correctly. 
*/
#define _POSIX_SOURCE 1
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <time.h>
#include <sys/types.h>
#include "setlottery.h"

#define NUM 4		/* number of processes to test */ 
#define SAMPLE 35	/* sample size */
#define MSGSIZE 16	
#define DEBUG 0		/* 1 for debug messages, 0 for none */
#define SHOW_CYCLE 0	/* 1 to show current cycle number, 0 for no messages */
#define ALARM_SC 75
#define WORK_ITER 100000

int created = 0; /* number of child finshed being created */
int c[NUM]; /* child pid array */
int P[NUM]; /* lottery ticket value for process P[i] */
int alarm_flag = 0;
int deadlocks = 0;

/* parent_code - invoked when parent gets SIGUSR1 signal 
*	Advances number of child processes that have been
*	created and are ready to run
*/ 
void parent_code(int sig) {
	/* update number of children that have contacted me*/
#if DEBUG == 1
	printf("PARENT: updating created: %d\n", created + 1 );
#endif
	created++;
}

/* work - invoked when child gets SIGUSR1 signal
*	Just does some trivial task and returns
*/
void work(int sig) {
	int i = 0;
	/* do some work */
	for ( i = 0; i < WORK_ITER; ++i );
}

/* watchdog - invoked when parent times out, sets an alarm variable.
*/
void watchdog(int sig) {
	alarm_flag = 1;
}

void child(int pd[2], int index );

void parent( int p[2], int ans[NUM] );

/*
*	main() - parent process forks children process to compete
*	for resources
*/
int main (int argc, char **argv) {

	int pid, sid, i = 0, j = 0, repeat = 0;
	int ans[NUM];
	float successful = 0;
	static struct sigaction pwork;
	int p[2];

	/* initialize histogram and default lottery ticket */
	for( i = 0; i < NUM; ++i ) {
		ans[i] = 0;
		P[i] = 1;
	}
	if ( argc == 1 )
		printf("all processes given 1 ticket\n");

	/* set lottery tickets passed from user */
	/* not much error checking here ... */
	if( argc > 1 ) {
		for ( i = 1; i <= NUM; ++i ) {
			sscanf(argv[i], "%d", &P[i-1]);
			printf("process(%d) will be assigned %d tickets\n", i-1, P[i-1]);
		}
	}

	/* set action for parent code */
       	pwork.sa_handler = parent_code;
	sigaction(SIGUSR1, &pwork, NULL );

	/* change session id so that kill doesn't kill the shell */
	sid = setsid();

	/* get parent name */
	pid = getpid();

	while( repeat++ < SAMPLE ) {
#if SHOW_CYCLE == 1
		printf("starting cycle %d\n", repeat );
#endif		
		/* initialize process storage */
		for ( i = 0; i < NUM; ++i ) 
			c[i] = 0;

		/* reset created global */
		created = 0;
		
		/* create pipe to communicate with child */
		if ( pipe(p) == -1 ) {
			printf("ERROR creating pipe\n");
			exit(1);
		}

		/* create NUM processes */
		for( i = 0; i < NUM; i++ ) {

			if ( (c[i] = fork()) == -1 ) {	
				break;
			}
			if ( c[i] == 0 ) {
				child( p, i );
			}
#if DEBUG == 1
			printf("%d: created %d\n", getpid(), c[i] );
#endif
		}

		parent( p, ans );
		successful += 1.0;
		close(p[0]);
		close(p[1]);
	}
	/* report results */
	printf("lower score reflects higher priority\n");
	for( j = 0; j < NUM; j++ ) {
		printf("[%d] = %2.2f\n", j, ans[j] / successful );
	}
	if ( deadlocks > 0 )	
		printf("%d deadlock(s) were detected\n", deadlocks);

	return 0;
}

/*
*	child - sets up function to call when SIGUSR1 is sent.
*	Sets the priorities for lottery tickets.
*	Waits for a signal from the parent letting it know that
*  	all the other children are ready to go.  Notifies parent 
*	when done. 
*/
void child( int pd[2], int index ) {

	static struct sigaction cwork;
	int i, cpid, ppid, status, res, assigned;
	char msg[MSGSIZE];

	cpid = getpid();
	ppid = getppid();
			
	/* set action for cwork */	
       	cwork.sa_handler = work;
	sigaction(SIGUSR1, &cwork, NULL );

	/* close off in port */
	close(pd[0]);	

	/* set up lottery tickets */
	assigned = setLotteryTickets(cpid, P[index]);

#if DEBUG == 1
	printf("%d(%d) has %d tickets ...\n", cpid, index, assigned );
#endif
    	
	/* notify parent that child has finished */
	kill( ppid, SIGUSR1 );

	alarm(ALARM_SC * 2);
	pause();

	/* notify parent of completion */
	sprintf(msg, "%d", cpid );
#if DEBUG == 1
	printf("CHILD: sending message: %s\n", msg);
#endif

	res = write( pd[1], msg, MSGSIZE );

	/* close up pipe */
	close(pd[1]);
	
	/* end child process */
	exit(0); 
}

/*	
*	parent - waits for all the children processes to be created
*	and initialized.  Once the children are set, the parent waits
*	for responses from the children.  The order that the children 
*	finishes is placed within an array, creating a histogram. 
*/
void parent( int p[2], int ans[NUM] ) {
	int cpid, i, j, cnt = 0, status, ex_status, val, wd = 0, cmpt;
	char buf[MSGSIZE], ch;
	static struct sigaction alrm;

	/* set up alarm code */
	alrm.sa_handler = watchdog;
	sigaction(SIGALRM, &alrm, NULL);

	/* close unneeded write fd */
	close(p[1]);
	
	/* wait for all the processes to be created
		and have their lottery tickets set */
	while( created < NUM ) {	
		alarm(ALARM_SC);
#if DEBUG == 1
		printf("Parent pausing for signal %d\n", created);
#endif
		pause();
		if ( alarm_flag ) {		
			alarm_flag = 0;
			wd = 1;
			break;
		}
	}

	if ( wd == 1 ) {
#if DEBUG == 1
		printf("watchdog activated\n");
#endif
		deadlocks++;
		cmpt = created - 1;
	}
	else
		cmpt = NUM;

	/* signal everyone to do their work (parent also gets notified) */
	ex_status = kill(0, SIGUSR1 );	

#if DEBUG == 1
	printf("PARENT: kill signal returned with %d\n", ex_status );
#endif		

	/* wait for all the processes to finish */
	for( i = 0; i < NUM; ++i ) {
#if DEBUG == 1
		printf("PARENT: waiting for message %d\n", i);
#endif 

		/* read in messages from the child processes */
		/* make read non-blocking */
		read(p[0], buf, MSGSIZE );
		sscanf(buf, "%d", &val );
#if DEBUG == 1
		printf("PARENT: received message from %d\n", val );
#endif
		/* determine order and update histogram */
		cnt++;
		for( j = 0; j < NUM; ++j ) {
			if ( c[j] == val ) 
				break;
		}	
		/* if timed out, assume finished last */
		if ( cnt == 0 ) 
			cnt = 4;
		ans[j] += cnt; 
	}

	/* reclaim resources */
	for ( i = 0; i < NUM; ++i ) {
		cpid = waitpid(-1, &status, 0);
		ex_status = WEXITSTATUS(status);
#if DEBUG == 1
		printf("Exit status from %d was %d\n", cpid, ex_status );
#endif
	}
}
