/*
  Name: Smoked Threads
  Author: Mark Dehus
  Date: 13/11/03 23:25
  Description: 
  	An example of multithreading with synchronization using the posix win32
  	thread library port. Three "Smoker" threads check a table to see what
  	ingredients the "Agent" thread puts on it. If the smoker thread has
  	enough ingredients to go smoking it does so. The program prints out
  	which smoker threads got what, then executes till the user tells
  	the program to stop.

  	Tested on Windows XP, 2000, and 98.
  	Built and compiled on Dev-c++ 4.9.8.4 using pThreadsGC SNAPSHOT 2003-09-18
  	You must have the correct pThreads library installed
    to compile this program.
  	
   	Powered by nicotine!

*/

#include <stdio.h>
#include <pthread.h>
#include <time.h>
#include <iostream>
#include <signal.h>

// Default number of threads
#define NUM_INGREDIENT 3
#define NUM_SMOKERS 3
#define NUM_AGENTS 1

using namespace std;

// Create our table
string TABLE[2];
// Declear and init our counter and ingredients
int SMOKED = 0;
char *INGREDIENT[3] = {"Paper", "Tobacco", "Match"};

// Declare and Initalize mutex and cond variables
pthread_mutex_t LOCK = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t ADD_INGREDIENTS = PTHREAD_COND_INITIALIZER;
pthread_cond_t START_SMOKING = PTHREAD_COND_INITIALIZER;

void* agent(void *)
{
	int selected_ingredient;
	int last_ingredient = -1;
 	srand(static_cast<unsigned>(time(0)));
 	
 	while(true){
		pthread_mutex_lock(&LOCK);
		
  		// Select our ingredient
		for (int i = 0; i < NUM_INGREDIENT - 1; i++){
			selected_ingredient = (int)(3.0 * rand()/(RAND_MAX+1.0));

			while (selected_ingredient == last_ingredient)
				selected_ingredient = (int)(3.0 * rand()/(RAND_MAX+1.0));
			
			TABLE[i] = INGREDIENT[selected_ingredient];
			last_ingredient = selected_ingredient;
		}
		
  		cout << "Agent  ID# " << (int)pthread_self() << ": Put " << TABLE[0] 
            << " and " << TABLE[1] << " on the table " << endl;
		
		// Wait for a smoker to get the ingredients
		pthread_cond_wait(&ADD_INGREDIENTS, &LOCK);
   		pthread_mutex_unlock(&LOCK);
    }	
}

void* smoker(void *ing)
{
	char *my_ingredient = (char *)ing;
	
	// Get a lock on the mutex then print out the ingredients
	// after that wait on the start smoking cond variable
	pthread_mutex_lock(&LOCK);
	cout << "Smoker ID# " << (int)pthread_self() << ": Started with "
		<< my_ingredient << endl;
	pthread_cond_wait(&START_SMOKING, &LOCK);
	pthread_mutex_unlock(&LOCK);
	
	while (true){
		pthread_mutex_lock(&LOCK);
		
		// Check to see if this threads ingredient
  		//  does not eqal any on the table
		if (TABLE[0] != my_ingredient && TABLE[1] != my_ingredient 
			&& TABLE[0] != "" && TABLE[1] != ""){
			
			cout << "Smoker ID# " << (int)pthread_self() 
				<< ": Went out for a smoke" << endl;
			TABLE[0] = "";
			TABLE[1] = "";
			SMOKED++;
			// Tell our agent(s) to wake up and do their thing
  			pthread_cond_broadcast(&ADD_INGREDIENTS); 	
		}
	
   		pthread_mutex_unlock(&LOCK);
   		// Because this thread does not wait we need a cancelation point
   		pthread_testcancel();
	}
}

int main()
{
	char anykey;
	/// Declare threads
    pthread_t agents[NUM_AGENTS];
    pthread_t smokers[NUM_SMOKERS];
    
    cout << "------------------------------------------------" << endl;
   	cout << "NOTE: Press any key then enter to start " << endl;
    cout << "------------------------------------------------" << endl;
    cout << "NOTE: Press a key then enter to stop" << endl;
    cout << "------------------------------------------------" << endl;
    
    // Initialize and start smoker threads
    for (int i = 0; i < NUM_SMOKERS; i++)
        pthread_create(&smokers[i], NULL, smoker, (void *)INGREDIENT[i]);
    
    // Wait for the user to tell the program to start
   	cin >> anykey;
	pthread_cond_broadcast(&START_SMOKING); 
	
    // Initialize and start agent threads
    for (int i = 0; i < NUM_AGENTS; i++)
        pthread_create(&agents[i], NULL, agent, NULL);

   	// Wait for user to tell program to stop
    cin >> anykey;
   	
    // Force the cancelation of all the threads
    for (int i = 0; i < NUM_AGENTS; i++)
    	pthread_cancel(agents[i]);
	for (int i = 0; i < NUM_SMOKERS; i++)
       	pthread_cancel(smokers[i]);
    
    // Wait for all threads to finish before moving on
    for (int i = 0; i < NUM_AGENTS; i++)
   	    pthread_join(agents[i], NULL); 
    for (int i = 0; i < NUM_SMOKERS; i++)
        pthread_join(smokers[i], NULL);
     
    cout << SMOKED << " coffin nails smoked!" << endl;
    
    // Destroy our mutex and condition variables
    pthread_mutex_destroy(&LOCK);
    pthread_cond_destroy(&ADD_INGREDIENTS);
    pthread_cond_destroy(&START_SMOKING);
    
    system("PAUSE");
    return 0;
}

