/********************************************************
 * File		: jobmanagement.c.			*
 * Author	: Ala'a A. Ibrahim.			*
 * Project	: None.					*
 * Discription	: This program is a semulation of a	*
 *		  system process management system.	*
 * Notes	: - The System has some bugs, but they	*
 *		    they are harmfull at the point.	*
 *		  - This Program was tested under Linux	*
 *		    Kernel 2.2.x, 2.4.x, 2.6.x, and I	*
 *		    guess you wont have any trouble	*
 *		    running it on Linux or Unix of any	*
 *		    version.				*
 *		  - This program was not tested under	*
 *		    MS DOS/Windows, and I have no 	*
 *		    intention to, and I have no idea 	*
 *		    related that it would work, on it,	*
 *		    So if you are intrested then DIY.	*
 * For Future	: - Fix the bug of the ticker (you must	*
 *		    assure that the ticker runs always	*
 *		    at first of every tick.		*
 *		  - Make the program run depending on 	*
 *		    other way than the sleep.		*
 *		  - Make the program implement other	*
 *		    Algorithms.				*
 *		  - If a better data structure for the	*
 *		    job could be implemented, that can	*
 *		    store more info.			*
 *		  - Make processes write there output	*
 *		    to different files instead of	*
 *		    stderr.				*
 ********************************************************/
/********************************************************
 * 			Copyleft			*
 * All Copylefts (c) 2003 are not reserved, Please feel	*
 * free to copy, distribute, reproduce, and upload this	*
 * file, but if you could, please notify me with the 	*
 * updates you make on this file.			*
 * Also notice that there is no warranty for using this	*
 * file, you would use it on your own responsibility.	*
 * Also please notice that under no condition you have	*
 * the right to sell this file, this file is meant for	*
 * educational perposes only.				*
 *		Knowledge For ALL ......		*
 ********************************************************/
/********************************************************
 *			Compilation			*
 * This program uses the Non-standard C Library 	*
 * libpthread.sa so you shoul consider linking at to	*
 * the program, an example of a successfull compilation	*
 * command would be like:				*
 * $ gcc -o jobmanagement -lpthread jobmanagement.c 	*
 * Now you can run it by:				*
 * $ ./jobmanagement					*
 * ENJOY ...!						*
 ********************************************************/

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <malloc.h>
#include <semaphore.h>
#include <stdlib.h>
#include <time.h>

/* Number of CPUS to use */
#define NO_OF_CPUS 2
/* After this time the program would start exiting */
#define MAX_TIME 120
/* After Creating this much of Jobs the program would start exiting */
#define MAX_JOBS 50

#define MAX_JOBS_PER_CYCLE 3
#define CYCLES_BEFORE_JOB 3
#define ONE_JOB_WITH_IO_FROM 2
#define MAX_IO_TIME 5
#define MAX_PROCESS_TIME 20

struct job {
	/* Link field for linked list */
	struct job* next;
	struct job* prev;
	/* fields to describe the job */
	int jid;
	int start_time;
	int process_time;
	int io_time;
	int io_start_time;
	/* fields for the status of the job */
	int wait_time;
	int processed;
	int in_queue_since;
};

struct queue {
	struct job* first;
	struct job* last;
	int count;
};

/* a variable related to the ticker, indicates what time is it. */
int time_now;

/* system generating JOB ID's */
int job_id=0;

/* Variables to indicate quiting the program. */
int shut_down = 0;
int killing = 0;
/* Jobs Queue & I/O Queue. */
struct queue job_queue, io_queue;

/* A mutex protecting job_queue. */
pthread_mutex_t job_queue_mutex =PTHREAD_MUTEX_INITIALIZER;

/* A mutex protecting io_queue. */
pthread_mutex_t io_queue_mutex =PTHREAD_MUTEX_INITIALIZER;

/* A semaphore counting the number of jobs in the queue. */
sem_t job_queue_count;
sem_t io_queue_count;

void initialize() {
	job_queue.first = NULL;
	job_queue.last = NULL;
	job_queue.count = 0;
	io_queue.first = NULL;
	io_queue.last = NULL;
	io_queue.count = 0;
	sem_init (&job_queue_count,0,0);
	sem_init (&io_queue_count,0,0);
	srand((unsigned int) time);
}

void job_queue_enqueue_new_job(int start_time,int process_time,
			       int io_time,int io_start_time) {
	struct job* new_job;
	new_job =(struct job*)malloc (sizeof (struct job));
	new_job->jid = ++job_id;
	new_job->start_time = start_time;
	new_job->process_time = process_time;
	new_job->io_time = io_time;
	new_job->io_start_time = io_start_time;
	new_job->wait_time = 0;
	new_job->processed = 0;
	new_job->in_queue_since = start_time;
	new_job->prev = NULL;
	fprintf(stderr,"Job created at %d with id = %d\n",start_time,job_id);
	
	/* Lock the mutex on the job queue before accessing it. */
	pthread_mutex_lock (&job_queue_mutex);
	if(job_queue.count == 0) {
		/* The queue is empty */
		new_job->next = NULL;
		job_queue.first = new_job;
		job_queue.last = new_job;
	} else {
		new_job->next = job_queue.last;
		job_queue.last->prev = new_job;
		job_queue.last = new_job;
	}
	job_queue.count++;
	sem_post (&job_queue_count);
	/* Unlock the job queue mutex. */
	pthread_mutex_unlock (&job_queue_mutex);
}

void job_queue_enqueue(struct job* new_job) {
	fprintf(stderr,"Job %d returned to Job Queue at %d\n", new_job->jid, time_now);
	new_job->in_queue_since = time_now;
	new_job->prev = NULL;
	/* Lock the mutex on the job queue before accessing it. */
	pthread_mutex_lock (&job_queue_mutex);
	if(job_queue.count == 0) {
		/* The queue is empty */
		new_job->next = NULL;
		job_queue.first = new_job;
		job_queue.last = new_job;
	} else {
		new_job->next = job_queue.last;
		job_queue.last->prev = new_job;
		job_queue.last = new_job;
	}
	job_queue.count++;
	sem_post (&job_queue_count);
	/* Unlock the job queue mutex. */
	pthread_mutex_unlock (&job_queue_mutex);
}

struct job* job_queue_dequeue() {
	struct job* process_job;
	/* wait for a job if the job_queue is empty */
	sem_wait (&job_queue_count);
	
	pthread_mutex_lock (&job_queue_mutex);
	process_job = job_queue.first;
	job_queue.first = job_queue.first->prev;
	--job_queue.count;
	pthread_mutex_unlock (&job_queue_mutex);
	fprintf(stderr,"Job %d is now to be processed at time %d\n",
		process_job->jid,time_now);
	return process_job;
}

void io_queue_enqueue(struct job* new_job) {
	new_job->prev = NULL;
	fprintf(stderr,"Job %d is waiting for it's time for io at time %d\n",
		new_job->jid,time_now);
	/* Lock the mutex on the io queue before accessing it. */
	pthread_mutex_lock (&io_queue_mutex);
	if(io_queue.count == 0) {
		/* The queue is empty */
		new_job->next = NULL;
		io_queue.first = new_job;
		io_queue.last = new_job;
	} else {
		new_job->next = io_queue.last;
		io_queue.last->prev = new_job;
		io_queue.last = new_job;
	}
	io_queue.count++;
	sem_post (&io_queue_count);
	/* Unlock the io queue mutex. */
	pthread_mutex_unlock (&io_queue_mutex);
}

struct job* io_queue_dequeue() {	
	struct job* process_job;
	/* wait for a job if the io_queue is empty */
	sem_wait (&io_queue_count);
	
	pthread_mutex_lock (&io_queue_mutex);
	process_job = io_queue.first;
	io_queue.first = io_queue.first->prev;
	--io_queue.count;
	pthread_mutex_unlock (&io_queue_mutex);
	fprintf(stderr,"Job %d is now working on io at time %d\n",process_job->jid,time_now);
	return process_job;
}

/* Threads HERE */

/* Thread 1: The main processor
 * - takes a job from the job_queue and processes it.
 * - When the job needs io it sends it to the io_queue.
 * - When the job is finished it destroys it.  */
void* cpu(void* notused) {
	int process_tobedone;
	while(!shut_down) {
		int to_io = 0;
		struct job* process_job = job_queue_dequeue();
		process_job->wait_time += time_now - process_job->in_queue_since;
		/* processing to be done */
		if (process_job->io_start_time < process_job->process_time){
			/* This process has some io in it */
			if (process_job->io_start_time > process_job->processed) {
				/* This process has io to do */
				to_io = 1;
				process_tobedone = process_job->io_start_time -
						process_job->processed;
			} else {
				/* process has finished it's io */
				process_tobedone = process_job->process_time -
						process_job->processed;
			}
		} else {
			/* This process has no io in it */
			process_tobedone = process_job->process_time;
		}
		/* Do the processing */
		sleep(process_tobedone);
		process_job->processed += process_tobedone;
		if (to_io) {
			/* Send the job to the io_queue */
			io_queue_enqueue(process_job);
		} else if(process_job->processed <= process_job->process_time) {
			/* This Condition should always be true if checked */
			/* Delete The job */
			fprintf(stderr,"Job %d has finished processing at time %d\n",
				process_job->jid,time_now);
			fprintf(stderr,"\tThis Job has waited %d cycles\n",
				process_job->wait_time);
			free(process_job);
		} else {
			/* Should never get in here. */
			fprintf(stderr,"\nAn Error happened at time %d with process %d\nAborting ...\n", time_now, process_job->jid);
		}
	}
	fprintf(stderr,"Shuting down the processor ...\n");
	return NULL;
}

/* Thread 2: The io handler
 * - Takes a job from the io_queue and processes it.
 * - When the job is finished with io it sends it back to job_queue.  */
void* basic_io(void* notused) {
	while(!shut_down) {
		struct job* process_job = io_queue_dequeue();
		process_job->wait_time += time_now - process_job->in_queue_since;
		/* Now Process it */
		sleep(process_job->io_time);
		process_job->processed += process_job->io_time;
		/* Now Put it in the job_queue again */
		job_queue_enqueue(process_job);
	}
	fprintf(stderr,"Shutting down the I/O ...\n");
	return NULL;
}

/* Thread 3: The job creator
 * - Creates new jobs.
 * - passes them to the job_queue.
 * - Should be terminated after the 2 other threads so that no processor
 *   is on Deadlock.  */
void* job_creator(void* notused) {
	int start_time, process_time, io_time, io_start_time;
	while(!killing) {
		start_time = time_now;
		if(rand() % CYCLES_BEFORE_JOB) {
			/* Do Not Create a Job this time */
			sleep(1);
			continue;
		}
		if(rand() % ONE_JOB_WITH_IO_FROM) {
			/* Make io for this job */
			io_start_time = (rand() % MAX_IO_TIME) + 1;
			io_time = (rand() % MAX_IO_TIME) + 1;
			process_time = io_start_time + io_time + 
					(rand() % (MAX_PROCESS_TIME - 2 * MAX_IO_TIME)) + 1;
		} else {
			/* No io */
			process_time = (rand() % MAX_PROCESS_TIME) + 1;
			io_start_time = process_time + 50;
			io_time = 0;
		}
		job_queue_enqueue_new_job(start_time, process_time, io_time, io_start_time);
		if(!(rand() % MAX_JOBS_PER_CYCLE))
			sleep(1);
	}
	fprintf(stderr,"Shutting down the Job Creator ...\n");
	return NULL;
}


/* Thread : Ticker
 * - it's only responsible of ticking the clock  */
void* ticker(void* notused) {
	while(!killing) {
		sleep(1);
		++time_now;
	}
	fprintf(stderr,"Shutting down the System Clock ...\n");
	return NULL;
}

int main () {
	int i;
	pthread_t thread_io, thread_ticker, thread_creator;
	pthread_t thread_cpu[NO_OF_CPUS];
	
	initialize();
	
	pthread_create (&thread_ticker,NULL,ticker,NULL);
	pthread_create (&thread_creator,NULL,job_creator,NULL);
	/* Create the CPU's */
	for (i = 0; i < NO_OF_CPUS; i++) {
		pthread_create (&thread_cpu[i],NULL,cpu,NULL);
	}
	pthread_create (&thread_io,NULL,basic_io,NULL);
	
	/* Wait while exit conditions happen */
	while(time_now < MAX_TIME && job_id < MAX_JOBS){
		sleep(1);
	}
	
	/* Start Shutting down the System */
	shut_down = 1;
	/* Waiting for all the CPU's to close */
	for (i = 0; i < NO_OF_CPUS; i++){
		pthread_join(thread_cpu[i],NULL);
	}
	pthread_cancel (thread_io);
	pthread_join (thread_io,NULL);
	killing = 1;
	pthread_join (thread_creator,NULL);
	pthread_join (thread_ticker,NULL);
	fprintf(stderr,"System Shutdown Complete.\n");
	return 0;
}

