#include <pthread.h>
#include <stdio.h>
#define MAX_THREADS 1000
#include <sys/time.h>

typedef struct {
        int readers;
        int writer;
        pthread_cond_t readers_proceed;
        pthread_cond_t writer_proceed;
        int pending_writers;
        pthread_mutex_t read_write_lock;
} mylib_rwlock_t;
 
void mylib_rwlock_init (mylib_rwlock_t *l)
{
       l -> readers = l -> writer = l -> pending_writers = 0;
       pthread_mutex_init(&(l -> read_write_lock), NULL);
       pthread_cond_init(&(l -> readers_proceed), NULL);
       pthread_cond_init(&(l -> writer_proceed), NULL);
}
 
void mylib_rwlock_rlock(mylib_rwlock_t *l)
{
       /* if there is a write lock or pending writers, perform condition
       wait.. else increment count of readers and grant read lock */
 
       pthread_mutex_lock(&(l -> read_write_lock));
       while ((l -> pending_writers > 0) || (l -> writer > 0))
               pthread_cond_wait(&(l -> readers_proceed),
                      &(l -> read_write_lock));
       l -> readers ++;
       pthread_mutex_unlock(&(l -> read_write_lock));
}
 
void mylib_rwlock_wlock(mylib_rwlock_t *l)
{
       /* if there are readers or writers, increment pending writers
       count and wait. On being woken, decrement pending writers
       count and increment writer count */
 
       pthread_mutex_lock(&(l -> read_write_lock));
       while ((l -> writer > 0) || (l -> readers > 0))  {
               l -> pending_writers ++;
               pthread_cond_wait(&(l -> writer_proceed),
                      &(l -> read_write_lock));
       }
       l -> pending_writers --;
       l -> writer ++;
       pthread_mutex_unlock(&(l -> read_write_lock));
}
  
void mylib_rwlock_unlock(mylib_rwlock_t *l)
{
       /* if there is a write lock then unlock else if there are
       read locks, decrement count of read locks. If the count
       is 0 and there is a pending writer, let him through, else
       if there are pending readers, let them all go through */
 
       pthread_mutex_lock(&(l -> read_write_lock));
       if (l -> writer > 0)
              l -> writer = 0;
       else if (l -> readers > 0)
              l -> readers --;
       pthread_mutex_unlock(&(l -> read_write_lock));
       if ((l -> readers == 0) && (l -> pending_writers > 0))
              pthread_cond_signal(&(l -> writer_proceed));
       else if (l -> readers > 0)
              pthread_cond_broadcast(&(l -> readers_proceed));
}

int list[10000];

void *rw_test(void *s);
pthread_t p_threads[MAX_THREADS];
pthread_attr_t attr;
mylib_rwlock_t rwl;
int min, elements_per_thread;
pthread_mutex_t min_lock;

main()
{
	int i, hits[MAX_THREADS];

	double start, end;
	struct timeval tz;
	struct timezone tx;

	FILE *fp;
	min = 99999999;
	elements_per_thread = 10000 / MAX_THREADS;
	/* fp = fopen("random_list", "r"); */
	pthread_mutex_init(&min_lock, NULL);
        pthread_attr_init (&attr);
        pthread_attr_setscope (&attr,PTHREAD_SCOPE_SYSTEM);
	mylib_rwlock_init (&rwl);

	for (i = 0; i < 10000; i++) 
		list[i] = rand()*rand();
	/*	fscanf(fp, "%d", &list[i]); */

	gettimeofday(&tz, &tx);
	start = (double)tz.tv_sec + (double) tz.tv_usec / 1000000.0;

	for (i = 0; i < MAX_THREADS; i ++)
	{
		hits[i] = i;
		pthread_create(&p_threads[i], &attr, rw_test,
			(void *) &hits[i]);
	}
	for (i = 0; i < MAX_THREADS; i ++)
		pthread_join(p_threads[i], NULL);
	gettimeofday(&tz, &tx);
	end = (double)tz.tv_sec + (double) tz.tv_usec / 1000000.0;

	printf("%d\n", min);
	printf("Avg time = %lf sec\n", (end - start)/1000000000.0 );
}

void *rw_test (void *start_ptr)
{
	int my_id, i, my_min;

	my_id = *((int *) start_ptr);
	my_min = 99999999;

	for (i = my_id * elements_per_thread;
		i < (my_id + 1) * elements_per_thread; i++)
		if (list[i] < my_min)
			my_min = list[i];

	mylib_rwlock_rlock(&rwl);
	if (my_min < min)
	{
		mylib_rwlock_unlock(&rwl);
		mylib_rwlock_wlock(&rwl);
		min = my_min;
	}
	mylib_rwlock_unlock(&rwl);
}
