/* OpSys sample thread code.

   This program has two threads:
    1. reads from standard input and places whatever
	it reads into a buffer.

	2. looks for stuff in the buffer and writes it
	to standard output.

   The result is simply a program that prints out whatever 
   you type in...

   This program uses condition signals to avoid busy waiting

*/

#include <stdio.h>	
#include <unistd.h>	
#include <sys/types.h>	
#include <stdlib.h>	
#include <pthread.h>	
#include <unistd.h>	

/*==========================================================*/
/* Global variables shared by the threads

   msgbuf is a circular buffer that holds whatever has been
   read from standard input by the reading thread. The writing 
   thread grabs stuff from here and writes it to stdout.

   in, out are indicies of the last character
   put in and the last char that was removed (respectively).
   if in == out, this means there is nothing in the buffer
   that hasn't been removed.
*/

#define BUFSIZE 100
char msgbuf[BUFSIZE];
int in = 0;
int out = 0;

/* mutex used to restrict access to shared data structure
*/

pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

/* condition signal used by threads to avoid busy waiting */
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;


/* functions to grab and release the mutex 
   any error is fatal here...
*/

void grablock() {
  /* grab the lock */
  if (pthread_mutex_lock(&mtx)!=0) {
    printf("error getting mutex lock\n");
    exit(1);
  }
}

void releaselock() {
  /* release the lock */
  if (pthread_mutex_unlock(&mtx)!=0) {
    printf("error releasing mutex lock\n");
    exit(1);
  }	
}


/* add character to the circular buffer. waits
   until this is possible !
*/
void put_a_char(char c) {
  int spot_avail;

  grablock();
  spot_avail = ((in+1)%BUFSIZE!=out);
  if (! spot_avail) {
	pthread_cond_wait(&cond,&mtx);
  }
  in = (in+1)%BUFSIZE;
  msgbuf[in]=c;
  // check to see if we need to wake up the other thread
  if (in==(out+1)%BUFSIZE) {
	pthread_cond_signal(&cond);
  }
  releaselock();
}


/* get next char from buffer - waits until there is a byte available */

char get_a_char() {
  int char_avail;
  char c;

  grablock();
  // check to see if there is a char available
  char_avail = (in != out);
  if (! char_avail) {
	pthread_cond_wait(&cond,&mtx);
  }
  out = (out+1)%BUFSIZE;
  c = msgbuf[out];
  // check to see if we need to wake up the other thread
  if  (in==(out+1)%BUFSIZE) {
	pthread_cond_signal(&cond);
  }
  
  releaselock();
  return(c);
}

/* reader reads from standard input (one byte at a time) and places into
   the shared buffer */

void *reader(void *arg) {
  int nbytes;
  char c;
  while (1) {
	/* attempt to read as much as we can */
	if (read(STDIN_FILENO,&c,1)!=1) {
	  /* Either EOF or an error , in either case just quit */
	  write(STDOUT_FILENO,"\nAll Done\n",10);
	  exit(1);
	}
	put_a_char(c);
  }
}	  
	
void *writer(void *arg) {
  char c;
  while (1) {
	c = get_a_char();
	if (1!= write(STDOUT_FILENO,&c,1)) {
	  /* write error ? punt... */
	  exit(1);
	}
  }
}

int main() {
  int i;
  pthread_t pt;
  /* start up the reader */
    pthread_create(&pt,NULL,reader,NULL); 
  //  reader(NULL);
  /* become the writer */
    writer(NULL);
}

