#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <semaphore.h>
#include <pthread.h>     // for semaphore operations - sem_init,sem_wait,sem_post
#include <sys/ipc.h>
#include <sys/shm.h>              
#include <sys/sem.h>              
#include <sys/wait.h>
#include <sys/errno.h>
#include <sys/types.h>

extern int errno;                      

#define SIZE 10        /* size of the shared buffer*/
#define VARSIZE 1     /* size of shared variable=1byte*/
#define INPUTSIZE 20

#define SHMPERM 0666      /* shared memory permissions */
int segid;                     /* id for shared memory bufer */
int pr_countid;                 
int co_countid;
int input_string_id;
int buff_top_id;
int run_id;
int len_id;
int empty_id;
int full_id;
int mutex_id;

char * buff;  	
char * input_string;
char* outp;      
char* pr_countp;  
char* co_countp;
char* buff_top;
char* run;
char* len;

sem_t *empty;
sem_t *full;
sem_t *mutex;

//-------------------------------------------------------------------------------------------------------------
//                                           Producer function
//-------------------------------------------------------------------------------------------------------------

void produce() 
{
    while (1) 
    {
        if(*pr_countp >= *len)
        {
            printf("\n Producer %d exited \n",getpid());
            exit(1);
        }
        if(*buff_top<10 && *run == 1)
        {
            printf("\nProducer %d trying to aquire Semaphore - Empty \n",getpid());
            sem_wait(empty);
       
            printf("\nProducer %d successfully aquired Semaphore - Empty \n",getpid());
            printf("\nProducer %d trying to aquire Semaphore - Mutex \n",getpid());
            sem_wait(mutex);
           
            printf("\nProducer %d successfully aquired Semaphore - Mutex \n",getpid());
            
            if(*buff_top>=9)
            { 
                printf("\nBuffer Full\n");
            } 
            else if(*pr_countp < *len)
            { 
                buff[(*buff_top)]=input_string[(*pr_countp)];

                printf("\nProducer %d Produced Item [ %c ] \n",getpid(),input_string[*pr_countp]);
                (*buff_top)++; 
                (*pr_countp)++;
                printf("\nItems in Buffer %d \n",(*buff_top));
            }
            sem_post(mutex);
         
            printf("\nProducer %d released Semaphore - Mutex \n",getpid());
            sem_post(full);
           
            printf("\nProducer %d released Semaphore - Full \n",getpid());
            sleep(2/random());  
        }
    }
}

//-------------------------------------------------------------------------------------------------------------


//-------------------------------------------------------------------------------------------------------------
//                                           Consumer function
//-------------------------------------------------------------------------------------------------------------

void consume() 
{
    while (1) 
    {
        if(*co_countp >= *len)
        {
            printf("\n Consumer %d exited \n",getpid());
            exit(1);
        }
        if(*buff_top>=0 && *run == 1)
        {
            printf("\nConsumer %d trying to aquire Semaphore - Full \n",getpid());
            sem_wait(full);
           
            printf("\nConsumer %d successfully aquired Semaphore - Full \n",getpid());
            printf("\nConsumer %d trying to aquire Semaphore - Mutex \n",getpid());
            sem_wait(mutex);
           
            printf("\nConsumer %d successfully aquired Semaphore - Mutex \n",getpid());
           
            if(*buff_top<0)
            { 
                printf("\nBuffer Empty\n");
            }   

            else if(*co_countp < *len)
            {          
                (*buff_top)--; 
                printf("\nConsumer %d Consumed Item [ %c ] \n",getpid(),buff[*buff_top]);
                buff[(*buff_top)]=' ';
                printf("\nItems in Buffer %d \n",(*buff_top));
            
                (*co_countp)++;
                sem_post(mutex);
            }
            printf("\nConsumer %d released Semaphore - Mutex \n",getpid());
            sem_post(empty);
           
            printf("\nConsumer %d released Semaphore - Empty \n",getpid());
            sleep(1);  
        }
    }
}


//-------------------------------------------------------------------------------------------------------------


//-------------------------------------------------------------------------------------------------------------
//                                    Main function (Parent Process)
//-------------------------------------------------------------------------------------------------------------

int main()
{
int i=0,n_producers,n_consumers,n_total,child_status;
pid_t temp_pid,parent_pid,current_pid,child_pids[50];


//--------------------------------------------------------------------------------------------------------------------------
//                            Aquiring shared memory for buffer , input string and vriables     
//--------------------------------------------------------------------------------------------------------------------------
    
segid = shmget (IPC_PRIVATE, SIZE, IPC_CREAT | IPC_EXCL | SHMPERM );
pr_countid=shmget(IPC_PRIVATE,VARSIZE,IPC_CREAT|IPC_EXCL|SHMPERM); 
co_countid=shmget(IPC_PRIVATE,VARSIZE,IPC_CREAT|IPC_EXCL|SHMPERM);
input_string_id=shmget(IPC_PRIVATE,INPUTSIZE,IPC_CREAT|IPC_EXCL|SHMPERM);
buff_top_id=shmget(IPC_PRIVATE,VARSIZE,IPC_CREAT|IPC_EXCL|SHMPERM);
run_id=shmget(IPC_PRIVATE,VARSIZE,IPC_CREAT|IPC_EXCL|SHMPERM);
len_id=shmget(IPC_PRIVATE,VARSIZE,IPC_CREAT|IPC_EXCL|SHMPERM);
empty_id=shmget(IPC_PRIVATE,sizeof(sem_t),IPC_CREAT|IPC_EXCL|SHMPERM);
full_id=shmget(IPC_PRIVATE,sizeof(sem_t),IPC_CREAT|IPC_EXCL|SHMPERM);
mutex_id=shmget(IPC_PRIVATE,sizeof(sem_t),IPC_CREAT|IPC_EXCL|SHMPERM);

//---------------------------------------------------------------------------------------------------------------------------



//---------------------------------------------------------------------------------------------------------------------------
//                                     Attaching pointers to shared memory locations  
//---------------------------------------------------------------------------------------------------------------------------

buff = shmat( segid, (char *)0, 0 );        
pr_countp = shmat(pr_countid,(char *)0,0);  
co_countp = shmat(co_countid,(char *)0,0);
input_string = shmat(input_string_id,(char *)0,0);
buff_top = shmat(buff_top_id,(char *)0,0);
run = shmat(run_id,(char *)0,0);
len = shmat(len_id,(char *)0,0);
empty = shmat(empty_id,(char *)0,0);
full = shmat(full_id,(char *)0,0);
mutex = shmat(mutex_id,(char *)0,0);

//---------------------------------------------------------------------------------------------------------------------------


//---------------------------------------------------------------------------------------------------------------------------
//                               Initializing Semaphores - Empty , Full & Mutex
//---------------------------------------------------------------------------------------------------------------------------

sem_init(empty,1,SIZE);
sem_init(full,1,0);
sem_init(mutex,1,1);

//---------------------------------------------------------------------------------------------------------------------------


//---------------------------------------------------------------------------------------------------------------------------
//                                       Initializing Shared Variables
//---------------------------------------------------------------------------------------------------------------------------


*len = strlen(input_string);
*co_countp = 0;
*pr_countp = 0;
*buff_top = 0;
*run = 0;

//---------------------------------------------------------------------------------------------------------------------------


//-------------------------------------------------------------------------------------------------------------
//                                Initializing Producer and Consumer processes
//-------------------------------------------------------------------------------------------------------------

printf("\n Parent Process Started \n");
printf("\n Enter the input string (20 characters MAX) : ");
scanf("%s",input_string);

printf("Entered string : %s",input_string);
*len = strlen(input_string);
printf("\n\nEnter number of Producer processes desired : ");
scanf("%d",&n_producers);
printf("\n\nEnter number of Consumer processes desired : ");
scanf("%d",&n_consumers);

n_total=n_producers+n_consumers;

parent_pid = getpid();

for(i=0;i<n_total;i++)
{
    current_pid=getpid();
    if(current_pid==parent_pid)
    {
        temp_pid=fork();
        current_pid=getpid();
        if(temp_pid == 0 && i<n_producers)
        {
        printf("\n Producer created with process id : %d \n",getpid());
        produce();
        }

        if(temp_pid == 0 && i>=n_producers)
        {
        printf("\n Consumer created with process id : %d \n",getpid());
        consume();
        }

        if(current_pid==parent_pid)
        {
            child_pids[i]=temp_pid;
        }
    }
}

//-------------------------------------------------------------------------------------------------------------


//--------------------------------------------------------------------------------------------------------------------------
//           Actual execution of initialized Producer and Consumer processes starts when run is set to 1
//--------------------------------------------------------------------------------------------------------------------------

current_pid=getpid();
    if(current_pid==parent_pid)
    { 
        *run = 1;
    }

//--------------------------------------------------------------------------------------------------------------------------


//--------------------------------------------------------------------------------------------------------------------------
//                              Waiting for child producers and consumers to terminate
//--------------------------------------------------------------------------------------------------------------------------
 
current_pid=getpid();
    if(current_pid==parent_pid)
    {
        for(i=0;i<n_total;i++)
        {
            waitpid(child_pids[i],&child_status,WUNTRACED);
        }
    }

//--------------------------------------------------------------------------------------------------------------------------



//--------------------------------------------------------------------------------------------------------------------------
//                             Removing shared memory buffers, variables & semaphores
//--------------------------------------------------------------------------------------------------------------------------
 
shmdt(buff);
shmdt(pr_countp);
shmdt(co_countp);
shmdt(input_string);
shmdt(empty);
shmdt(full);
shmdt(mutex);
shmdt(buff_top);
shmdt(len);
shmdt(run); 
 
     
shmctl(segid, IPC_RMID, NULL);           
shmctl(pr_countid,IPC_RMID,NULL);      
shmctl(co_countid,IPC_RMID,NULL);      ;      
shmctl(input_string_id,IPC_RMID,NULL);      
semctl( empty_id, 0, IPC_RMID, NULL);
semctl( full_id, 0, IPC_RMID, NULL);
semctl( mutex_id, 0, IPC_RMID, NULL);
semctl( buff_top_id, 0, IPC_RMID, NULL);
semctl( run_id, 0, IPC_RMID, NULL);
semctl( len_id, 0, IPC_RMID, NULL);


sem_destroy(empty);
sem_destroy(full);
sem_destroy(mutex);

//--------------------------------------------------------------------------------------------------------------------------

printf("\n Parent process exited \n\n");

return(0);
}

