#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <time.h>

#define MAX_THREADS 10
#define LINES 80
#define width 80
#define WIDTH width
#define GENERATIONS 1000

pthread_mutex_t crmtx;
pthread_mutex_t writing[LINES];

int arr[LINES][width];

void monitor_get_line(int *j,int* line)
{
    static int current_row = 0;
    int i;
    
    pthread_mutex_lock(&crmtx);
    *j = (current_row++)%LINES;
    pthread_mutex_unlock(&crmtx);
    
    for(i=0;i<WIDTH;i++)
    {
        line[i]=arr[*j][i];
    }
}

void monitor_read_line(int j,int* line)
{
    int i;
    for(i=0;i<WIDTH;i++)
    {
        line[i]=arr[j][i];
    }
}


void monitor_put_line(int j,int* line )
{
    int i;
    pthread_mutex_lock(&writing[j]);
        
    for(i=0;i<width;i++)
    {
        arr[j][i]=line[i];
    }
    
    pthread_mutex_unlock(&writing[j]);
    
    
}



void *worker(void *pparam)
{
	int k,j,pos,cnt;
        int line[width],above[width],below[width],new[width];
        
        for(cnt=0;cnt<GENERATIONS;cnt++)
        {
        
            monitor_get_line(&j,line);
            if(j>0)  monitor_read_line(j-1,above);
            else
            memcpy(above,line,width*sizeof(int));
            
            if(j<LINES-1) monitor_read_line(j+1,below);
            else
                memcpy(below,line,width*sizeof(int));
            
            for(k=1;k<WIDTH-2;k++)
            {
                    pos = rand()%8;
                    switch(pos)
                    {
                        case 0: new[k] = above [(k-1)%width] ; break;
                        case 1: new[k] = above [k] ; break;
                        case 2: new[k] = above [(k+1)%width]; break;
                        case 3: new[k] = above [(k-1)%width]; break;
                        case 4: new[k] = line[(k+1)%width];break;
                        case 5: new[k] = below[(k-1)%width];break;
                        case 6: new[k] = below[k];break;
                        case 7: new[k] = below[(k+1)%width];break;
                    }
                    
                monitor_put_line(j,new);
            }
        }
}

int main(void)
{
	pthread_t thread[MAX_THREADS];
	int task_ids[MAX_THREADS];
	int i,j;
        
        srand(time(NULL));

        pthread_mutex_init(&crmtx,NULL);
        
        for(i=0;i<LINES;i++)
        {
            pthread_mutex_init(&writing[i],NULL);
        }
        
        pthread_mutex_init(&crmtx,NULL);
        
        /*Initialise array */
        for(j=0;j<LINES;j++)
            for(i=0;i<width;i++)
                arr[j][i] = rand()%256;
                
         /*Print results */
         /*
        for(j=0;j<LINES;j++){
            for(i=0;i<width;i++)
            {
                if(arr[j][i]>128)
                    printf("*");
                else
                    printf("_");
            }
            printf("\n");
        }

exit(-1); */
                
	for(i=0;i<MAX_THREADS;i++)
	{
		task_ids[i] = i;
		
		pthread_create(&thread[i],NULL,worker,(void*) (&task_ids[i]));
	}
	printf("Hello from main thread \n");
	
	for(i=0;i<MAX_THREADS;i++)
		pthread_join(thread[i],NULL);
                
        pthread_mutex_destroy(&crmtx);
        for(i=0;i<LINES;i++)
        {
            pthread_mutex_destroy(&writing[i]);
        }
        
        /*Print results */
        for(j=0;j<LINES;j++){
            for(i=0;i<width;i++)
            {
                if(arr[j][i]>128)
                    printf("*");
                else
                    printf("_");
            }
            printf("\n");
        }

       
}
