/***************************************************************************
 *   Copyright (C) 2005 by Brian Lauber   *
 *   bml8@case.edu   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#define _REENTRANT

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


typedef enum {FALSE = 0, TRUE = 1, false = 0, true = 1} bool;

// This time I will use global variables instead of passing them in structures
// (simply because I am lazy)
int  current_value = 0;
bool updated       = false;

// Next, I will initialize the mutex and cond variable for multithreaded, single-process communication
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  cond  = PTHREAD_COND_INITIALIZER;

// Checks to see if there is a number available to print (updated == true).  If not,
// the thread will wait on the conditional variable until NumberThread signals the
// condition.  If updated is still false, the thread will wait on the condition
// variable again; otherwise, it will print current_value and reset updated to false.
void* PrintThread(void *args)
{
  int i = 0;
  int iterations = (int) args; // Recast the arguments to their appropriate type
  printf("Created writer thread %u\n", pthread_self());

  for(i = 0; i < iterations; ++i)
  {
    pthread_mutex_lock(&mutex);
    printf("Printer %8u: Enters the critical region\n", pthread_self());

    while(updated == false)
    {
      printf("Printer %8u: Waits on condition\n");
      pthread_cond_wait(&cond, &mutex);
    }
    printf("Printer %8u: Received %i\n", pthread_self(), current_value);
    updated = false;

    pthread_mutex_unlock(&mutex);
  }

  printf("Printer %8u: Exits\n", pthread_self() );
}



// This thread will repeatedly increment current_value, set updated to TRUE,
// and signal the condition variable.  Signalling the condition variable wakes
// up 1 thread that is waiting on the condition.  If no threads are waiting, then
// the signal essenially does nothing (no-op).
void* NumberThread(void *args)
{
  int i = 0;
  int iterations = (int) args; // Recast arguments
  printf("Created reader thread %u\n", pthread_self());

  for(i = 0; i < iterations; ++i)
  {
    sleep(1);
    pthread_mutex_lock(&mutex);

    // Need to create a new number for the Print threads
    if(updated == false)
    {
      printf("Number: Generates new number\n");
      ++current_value;
      updated = true;

      // Inform any Print threads waiting on condition cond that
      // cond has now been satisfied
      pthread_cond_signal(&cond);

      // Comment out the pthread_cond_signal above and uncomment
      // this next line.  What happens?
      // pthread_cond_broadcast(&cond);
    }

    // Try uncommenting this line and see what happens
    pthread_mutex_unlock(&mutex);
  }

  printf("Number: Exits\n");
}


int main(int argc, char **argv)
{
  pthread_t    *id;           // A list of thread id's
  unsigned int  thread_count; // The number writer threads created
  unsigned int  i;            // Used for counting

  printf("How many threads? ");
  scanf("%u", &thread_count);

  // Create a place to store id's for all Print threads, +1 for Number thread
  id = malloc( (thread_count + 1) * sizeof(pthread_t));

  // Generate the writer threads, each w/ "3" as an argument
  // (note: I'm passing an integer even though it expects a pointer)
  for(i = 0; i < thread_count; ++i)
    pthread_create( &(id[i]), NULL, PrintThread, (void *) 3);

  // Generate the reader thread
  pthread_create( &(id[thread_count]), NULL, NumberThread, (void*) (3 * thread_count));

  // Wait for all the threads to terminate
  for(i = 0; i <= thread_count; ++i)
    pthread_join(id[i], NULL);

  // Clean up the leftovers
  pthread_mutex_destroy(&mutex);
  pthread_cond_destroy(&cond);

  return EXIT_SUCCESS;
}

