/* ====================== Curtin University of Technology ====================== *
 * 				FCS 351 Assignment 		    		 *
 * 		   	    Name: Davina Chin Lee Yien		    		 *
 *			       Student Id: 7D2A1001		    		 *
 * ============================================================================= */

//============================= The library use =================================
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <time.h>
#include <signal.h>
#include <errno.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/sem.h>
#define BUF_SIZE 1000
#define BUF_SIZE1 10
//======================== Global variable declaration =========================
int teller1Time, teller2Time, teller3Time, PeriodicTime,WithdrawTime,DepositTime;
int whichTeller;
int pidTeller1, pidTeller2, pidTeller3, pidProducer;
time_t lt_time;
struct tm *timeptr;
FILE *log;
int NumTellerExist;

int shm_buffer = -1;
int shm_tail = -1;
int Sem = -1;
int *tail = 0;

int *TellerTime = 0;
int *totalTime = 0; //avgTime[0] indicate total waiting time
		   	    //totalTime[1] indicate total turnaround time;
//int customers=0;
//======================== Structure declaration ===============================
struct varioustimer             // structure for storing the initial time and final time for
{                               // for each lift_request and lift operations
       char my_time[30];
       char my_init[30];
       char my_init_init[30];
       int init_sec;
       int init_min;
       int final_sec;
       int final_min;
}timerpointer;

//this structure is store the customer details
struct Customer_Queue
{
	int custno;
	int serviceType;
	char TimeIn[20];
	time_t comingTime;
};

struct Customer_Queue *Queue;
//int custnum; //use to give the customer number.Start from 1,never decreases
int *numTeller;
//============================= Sub programs  ==================================
/************************ int PWait(int semid) ***********************/
int PWait(int semid)
{
        int retval;
        struct sembuf p_buf;
        p_buf.sem_num = 0;
        p_buf.sem_op = -1;
        p_buf.sem_flg = SEM_UNDO;

        while(((retval = semop(semid,&p_buf,1)) == -1)&&(errno==EINTR))
                return retval;
}

/******************** int VSignal(int semid) *************************/
int VSignal(int semid)
{
        int retval;
        struct sembuf  v_buf;

        v_buf.sem_num = 0;
        v_buf.sem_op  = 1;
        v_buf.sem_flg = SEM_UNDO;

        while ( ((retval = semop(semid, &v_buf, 1)) == -1) && (errno==EINTR))
                ;
        return retval;
}

/****************** The void CleanUp_IPC() function *****************/
void IPC_Cleanup()
{

//	semN != -1        ? semctl(semN,    0, IPC_RMID, 0) : 0;
//	SemE != -1        ? semctl(semE,    0, IPC_RMID, 0) : 0;
	Sem != -1        ? semctl(Sem,    0, IPC_RMID, 0) : 0;
//	TellerSem != -1        ? semctl(TellerSem,    0, IPC_RMID, 0) : 0;
	shm_buffer != -1  ? shmctl(shm_buffer, IPC_RMID, 0) : 0;
	shm_tail != -1  ? shmctl(shm_tail, IPC_RMID, 0) : 0;
//	shm_timer != -1 ? shmctl(shm_timer, IPC_RMID,0) : 0;

	kill(pidProducer, SIGKILL);
	kill(pidTeller1, SIGKILL);
	kill(pidTeller2, SIGKILL);
	kill(pidTeller3, SIGKILL);
	fprintf(stderr, "IPC Clean Up - Done.\n");
}
//==============================================================================================
void WriteTellerTerminate()
{       
	int elapsed_min;
	int elapsed_sec;

	elapsed_sec = timerpointer.final_sec - timerpointer.init_sec; 
	if (elapsed_sec < 0)
		elapsed_sec = timerpointer.init_sec - timerpointer.final_sec;
	elapsed_min = timerpointer.final_min - timerpointer.init_min;
			
        log = fopen("simulation_log", "a");
	
	if (whichTeller == 1){
		fprintf(log,"Teller-1 Termination:\n");
		PWait(Sem);
		TellerTime[0] = elapsed_min*60 + elapsed_sec;
		VSignal(Sem);
	}
	else{
		if(whichTeller == 2){
			PWait(Sem);
			TellerTime[1] = elapsed_min*60 + elapsed_sec;
			VSignal(Sem);
			fprintf(log,"Teller-2 Termination\n");
		}
		else{
			PWait(Sem);
			TellerTime[2] = elapsed_min*60 + elapsed_sec;
			VSignal(Sem);
			fprintf(log,"Teller-3 Termination\n");
		}
	}
	
	fprintf(log,"Start time: %s",timerpointer.my_init_init);
	fprintf(log,"Termination time: %s\n\n",timerpointer.my_time);
	
        fclose(log);
}
/******************** void LastTellerWrite() *****************************/
void LastTellerWrite()
{

       int elapsed_sec = timerpointer.final_sec - timerpointer.init_sec;
       int elapsed_min = timerpointer.final_min - timerpointer.init_min;
       if (elapsed_sec < 0)
                elapsed_sec = timerpointer.init_sec - timerpointer.final_sec;

	log = fopen("simulation_log","a");
	fprintf(log,"\nTeller Statistic\n");
	PWait(Sem);
	fprintf(log,"Teller-1 running time:%d seconds \n",TellerTime[0]);
	fprintf(log,"Teller-2 running time:%d seconds \n",TellerTime[1]);
	fprintf(log,"Teller-3 running time:%d seconds \n",TellerTime[2]);
	fprintf(log,"Number of customer:%d \n",totalTime[2]);
	fprintf(log,"Average waiting time:%d\n",totalTime[0]/totalTime[2]);
	fprintf(log,"Average turnaround time:%d\n\n", totalTime[1]/totalTime[2]);
	VSignal(Sem);
	fclose(log);
}
/***************** The void Timer(int signum) function ***************/
void timer (int signum)
{
	//printf("Number of teller is %d\n\n",numTeller[0]);
	//PWait(Sem);
	//if (tail[1]>=tail[0] && numTeller[0] >=2 && numTeller[0] <=3){
	//	numTeller[0]--;
        //	VSignal(Sem);
        	lt_time = time(NULL);
	        timeptr = localtime(&lt_time);
	
        	strcpy(timerpointer.my_time,asctime(timeptr));
	        timerpointer.final_sec = timeptr->tm_sec;
        	timerpointer.final_min = timeptr->tm_min;
		WriteTellerTerminate();
		LastTellerWrite();
	        printf("Timer expire!!!!!!BYE BYE...\n");
        	exit(0);
/*	}
	else{
		VSignal(Sem);
        	lt_time = time(NULL);
	        timeptr = localtime(&lt_time);
	
        	strcpy(timerpointer.my_time,asctime(timeptr));
	        timerpointer.final_sec = timeptr->tm_sec;
        	timerpointer.final_min = timeptr->tm_min;
		printf("Unable to terminate...customer still exist\n");
		WriteTellerTerminate();
		LastTellerWrite();
		exit(0);
	}*/
}
/****************** The void Customer() function ****************/
void Customer()
{
	FILE *fp2;
	char ch;
	struct tm *TimeIn;
	char buffer[20], arg1[10], arg2[10];
  	int i;	

	lt_time = time(NULL);
        timeptr = localtime(&lt_time);
       	strcpy(timerpointer.my_time, asctime(timeptr));
        timerpointer.final_sec = timeptr->tm_sec;
        timerpointer.final_min = timeptr->tm_min;
	
	PWait(Sem);
	totalTime[0] = 0;
	totalTime[1] = 0;
	totalTime[2] = 0;
	VSignal(Sem);
	
	if ((fp2 = fopen("customer_file","r"))==NULL)
	{
		printf("Cannot open file\n");
		exit(1);
	}
	else{
		while(fgets(buffer, 20, fp2) != NULL){
			PWait(Sem);
			if(sscanf(buffer,"%s %s",arg1, arg2) == 2){
					
				Queue[tail[0]].custno = atoi(arg1);
			        if (!strcmp(arg2,"Withdrawal"))
					Queue[tail[0]].serviceType = 1;
   			        else
					Queue[tail[0]].serviceType = 2;
				
				lt_time = time(NULL);
				strcpy(Queue[tail[0]].TimeIn,asctime((localtime(&lt_time))));
				Queue[tail[0]].comingTime = time(NULL);
				VSignal(Sem);
				log = fopen("simulation_log", "a");
				fprintf(log,"--------------------------------------------\n");
				PWait(Sem);
				fprintf(log,"Customer %d: ", Queue[tail[0]].custno);
				if (Queue[tail[0]].serviceType == 1)
					fputs("Cash Withdrawal\n", log);
				else
					fputs("Cash deposit\n", log);
				fprintf(log,"Arrival time: %s\n", Queue[tail[0]].TimeIn);
				VSignal(Sem);
				fprintf(log,"--------------------------------------------\n\n");
				/* close file after finish writing */
				fclose(log);
				PWait(Sem);
				tail[0]++;
				VSignal(Sem);
				sleep(PeriodicTime);
			}
		}
	}
	
	fclose(fp2);
	printf("Customer arrived stored into the queue....\n\n");
	exit(0);
}
/***************  void WriteToLogResponse(int customerNo, char TimeCome[20]) *******************/
void WriteToLogResponse(int customerNo,char TimeCome[20],time_t t1, time_t t2)
{
	PWait(Sem);
	totalTime[0] = totalTime[0] + difftime(t2,t1);
	totalTime[2] = totalTime[2] + 1;
	VSignal(Sem);
				
	log = fopen("simulation_log", "a");
	if (whichTeller == 1)
		fprintf(log,"Customer statistics for Teller-1\n");
	else
		if(whichTeller == 2)
			fprintf(log,"Customer statistics for Teller-2\n");
		else
			fprintf(log,"Customer statistics for Teller-3\n");
			
	lt_time = time(NULL);
	timeptr = localtime(&lt_time);
	
	fprintf(log,"Customer %d \n", customerNo);
	fprintf(log,"Arrival time: %s",TimeCome);
	fprintf(log,"Response time: %s\n",asctime(timeptr));	
	fclose(log);
}
/************ void WriteToLogCompletion(int customerNo,char TimeCome[20])  *****************/
void WriteToLogCompletion(int customerNo,char TimeCome[20],time_t t1,time_t t2)
{
	//dunno why this wont work ....when this one open .... the customer served jumping up and down
	PWait(Sem);
	//totalTime[1] = totalTime[1] + difftime(t2,t1);
	VSignal(Sem);
	
	log = fopen("simulation_log","a");
	if (whichTeller == 1)
		fprintf(log,"Customer statistics for Teller-1\n");
	else
		if(whichTeller == 2)
			fprintf(log,"Customer statistics for Teller-2\n");
		else
			fprintf(log,"Customer statistics for Teller-3\n");

	lt_time = time(NULL);
	timeptr = localtime(&lt_time);
	fprintf(log,"Customer %d \n", customerNo);
	fprintf(log,"Arrival time: %s",TimeCome);
	fprintf(log,"Completion time: %s\n\n",asctime(timeptr));
	fclose(log);
}
/******************* The void Teller() function ******************/
void Teller(struct itimerval timer_expire)
{
	int customerNo;
	int service;	
	char TimeCome[20];
	time_t time1;
	time_t time2,time3;
//	int waiting;
	setitimer (ITIMER_REAL,&timer_expire,NULL);
	
	lt_time = time(NULL);
        timeptr = localtime(&lt_time);
	strcpy(timerpointer.my_init_init,asctime(timeptr));
	timerpointer.init_sec = timeptr->tm_sec;
	timerpointer.init_min = timeptr->tm_min;
	
	PWait(Sem);
	tail[1]=0;
	VSignal(Sem);
	
	while (getitimer (ITIMER_REAL,&timer_expire) == 0)
	  {
		PWait(Sem);
		if (tail[1]!=tail[0]){
			if(Queue[tail[1]].custno!=0){
				customerNo = Queue[tail[1]].custno;
				service = Queue[tail[1]].serviceType;
				strcpy(TimeCome,Queue[tail[1]].TimeIn);
				time1 = Queue[tail[1]].comingTime;
				Queue[tail[1]].custno = 0;
				Queue[tail[1]].serviceType = 0;
				strcpy(Queue[tail[1]].TimeIn,"0");
				printf("Teller %d doing the job\n",whichTeller);
				printf("Processing customer %d\n",customerNo);
				tail[1]++;
				VSignal(Sem);
				time2 = time(NULL);
				WriteToLogResponse(customerNo,TimeCome,time1,time2);
				
				if (service ==1){
					printf("Service type is Cash Withdrawal\n");
					printf("Sleep for %d second\n",WithdrawTime);
					sleep(WithdrawTime);
				}
				else{
					printf("Service type is Cash deposit\n");
					printf("Sleep for %d second\n",DepositTime);
					sleep(DepositTime);
				}
				time3 = time(NULL);
				WriteToLogCompletion(customerNo,TimeCome,time1,time3);
			}
			else{
				tail[1]++;
				VSignal(Sem);
				sleep(2);
			}
		}
		else{
			printf("Sleep for 2 seconds while waiting for customer\n");
			VSignal(Sem);
			sleep(2);
		}
	   }
	exit(0);
}

/************************ int main(int argc, char *argv[]) ********************************/
int main(int argc, char *argv[])
{
	int wait1,wait2,wait3;
	struct itimerval timer1, timer2, timer3;
	struct sigaction sa;
	
	//this is to install the timer handler	
	memset(&sa,0,sizeof(sa));
	sa.sa_handler = &timer;
	sigaction (SIGALRM,&sa, NULL);

	shm_buffer = shmget(IPC_PRIVATE,BUF_SIZE, 0644);
	shm_tail = shmget(IPC_PRIVATE, BUF_SIZE1, 0644);
	
        tail = (int *)malloc(2 * sizeof(int));
        tail = (int *)shmat(shm_tail,NULL,0);
	
        numTeller = (int *)malloc(1 * sizeof(int));
        numTeller = (int *)shmat(shm_tail,NULL,0);
        
	Queue = (struct Customer_Queue*)malloc(1000 *sizeof(struct Customer_Queue*));
	Queue = (struct Customer_Queue*)shmat(shm_buffer,NULL,0);
	
	TellerTime = (int *)malloc(3*sizeof(int));
	TellerTime = (int *)shmat(shm_tail,NULL,0);
	
	totalTime = (int *)malloc(3*sizeof(int));
	totalTime = (int *)shmat(shm_tail,NULL,0);
	
	numTeller[0] = 3;
	
	//to check whether the argument is 5 or not
	if (argc != 7){
		printf("==================================================================\n");
		printf("Insufficient arguments. Please enter part_1 Tc T1 T2 T3 Tw Td\n");
		printf("Tc - periodic time for customer to arrive\n");
		printf("T1 - lifetime for teller 1\n");
		printf("T2 - lifetime for teller 2\n");
		printf("T3 - lifetime for teller 3\n");
		printf("Tw - sleep time for cash-withdrawal\n");
		printf("Td - sleep time for cash-deposit\n");
		printf("==================================================================\n");
		exit(1);
	}
	else{
		//to get the argument passed in during runtime to measure the expire time
		PeriodicTime = atoi(argv[1]);
		teller1Time = atoi(argv[2]);
		teller2Time = atoi(argv[3]);
		teller3Time = atoi(argv[4]);
		WithdrawTime = atoi(argv[5]);
		DepositTime = atoi(argv[6]);
		
		//this is to configure the time for expire 
		timer1.it_value.tv_sec = teller1Time; //for the first teller
		timer1.it_value.tv_usec = 0;
		timer2.it_value.tv_sec = teller2Time;  //for the second teller
		timer2.it_value.tv_usec = 0;
		timer3.it_value.tv_sec = teller3Time;  //for the third teller
		timer3.it_value.tv_usec = 0;

		//setting up the semaphore, initialization
                if ( (Sem = semget(IPC_PRIVATE, 1, 0600)) == -1){
			printf("Semaphore fail.");
			exit(1);
                }
                else{
                        if ( semctl(Sem, 0, SETVAL, 2) == -1 ) {
				printf("Initialization of semaphore fail.");
                                exit(1);
                        }
                }
		
		//This is the producer...running the Customer routine, acting as producer
		if (( pidProducer = fork()) < 0){
			printf ("Fork fail.Unable to run Customer routine ..\n");
			exit(errno);
		}
		else{
			if (pidProducer == 0){  //child process when fork succeeded
				printf("The mummy process\n");
				Customer();
			}
		}

		//The first teller
		if (( pidTeller1 = fork()) < 0){
			printf ("Fork fail. Unable to run teller 1..\n");
			exit(errno);
		}
		else
		{
			if (pidTeller1 == 0){  //child process when fork succeeded
				printf("The teller 1 process\n");
				whichTeller = 1 ;
	//			TellerTime[0];
				Teller(timer1);
			}
		}
		
		//The second teller 
		if (( pidTeller2 = fork()) < 0){
			printf ("Fork fail. Unable to run teller 1..\n");
			exit(errno);
		}
		else{
			if (pidTeller2 == 0){ //child process when fork succeeded
				printf("The teller 2 process\n");
				whichTeller = 2;
				Teller(timer2);
			}
		}
	
		//The third teller
		if (( pidTeller3 = fork()) < 0){
			printf ("Fork fail. Unable to run teller 1..\n");
			exit(errno);
		}
		else{
			if (pidTeller3 == 0){ //the child process when fork succeeded
				printf("The teller 3 process\n");
				whichTeller = 3;
				Teller(timer3);
			}
		}
	}

	//Producer waiting for child to terminate
	//while the terminating process is not those three teller, then wait .... 
	while (pidTeller1 != (wait1=wait(&wait1)) || pidTeller2 != (wait2=wait(&wait2)) || pidTeller3 !=(wait3=wait(&wait3)) )
	{
		//if either of the teller already die, then break from the loop
		if (wait1 == -1 || wait2 == -1 || wait3 == -1)
			break;
	}
	
	IPC_Cleanup();
	exit (1); //exit the program 
}

/*******************================ THE END ==============**********************/
/* NOT YET DONE...
 * 1. AVERAGE TURN AROUND TIME..i use the same method as waiting time but it wont work
 * 2. LAST TELLER SHOULD TERMINATE ONLY WHEN THERE IS NO LONGER COMING CUSTOMER..not yet try
 * 3. USE QUEUE FEATURES..might want to lose mark on this
 * */
