//Producer/Consumer mit pthread 
//Warum kommen uns diese Conditions nur so bekannt vor?

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <malloc.h>
#include <pthread.h>
#include <time.h>
#include <signal.h>

/*---------------------------------------------------------------------------*/

//Achtung: beim Kompilieren nicht die pthread-Bibliothek vergessen!
//gcc -lpthread -o threads threadexample.c

/*---------------------------------------------------------------------------*/

//Puffergröße
#define N 10

//condition empty: es gibt freie Plätze
//condition full: es gibt belegte Plätze
pthread_cond_t empty;
pthread_cond_t full;
//Mutex, um den counter zu schützen, Zugriff für die beiden condition-Variablen
pthread_mutex_t counter_mutex;

//Zähler: Anzahl der belegten Plätze im Puffer
int counter = 0;
//Index für den Consumer
int lo = 0;
//Index für den Producer
int hi = 0;
//Puffer
int buffer[N];

//Thread für den Producer
pthread_t producer;
//Thread für den Consumer
pthread_t consumer;

/*---------------------------------------------------------------------------*/

//ein Thread für den Producer
void *Producer(void *arg)
{
    //immer als erstes: Argumente freigeben
    free(arg);

    printf("Producer startet\n");

    //viel Spass nun
    while(1)
    {
	//ein wenig schlafen
	sleep(random()%5);

	//Zugriff auf den Counter, darum den Mutex belegen
	pthread_mutex_lock(&counter_mutex);

	//wenn der Puffer voll ist, kann nix reingelegt werden
	while(counter == N)
	{
	    printf("Warte auf empty\n");
	    //auf condition empty warten
	    //der Mutex wird automatisch freigegeben und belegt
	    pthread_cond_wait(&empty, &counter_mutex);
	    printf("Warten auf empty beendet\n");
	}
    
	//item produzieren und in den Puffer legen
	buffer[hi] = random()%50;
	printf("Put %d\n", buffer[hi]);
	hi = (hi+1)%N;
	counter++;

	//wenn der Puffer vorher leer war -> signalisieren, dass es nun
	//was gibt
	if(counter == 1)
	{
	    printf("Signalisiere full\n");
	    pthread_cond_signal(&full);
	}
	
	//Mutex freigeben
	pthread_mutex_unlock(&counter_mutex);
    }
}

/*---------------------------------------------------------------------------*/

//Consumer-Thread
void *Consumer(void *arg)
{
    int item;

    //Argument freigeben
    free(arg);

    printf("Consumer startet\n");


    while(1)
    {
	//ein wenig schlaffen
	sleep(random()%5);

	//Mutex für Zugriff auf counter belegen
	pthread_mutex_lock(&counter_mutex);

	//wenn kein Item im Puffer ist, warten
	while(counter == 0)
	{
	    printf("Warte auf full\n");
	    //Mutex wird automatisch freigegeben und belegt
	    pthread_cond_wait(&full, &counter_mutex);
	    printf("Warten auf full beendet\n");
	}
    
	//item aus Puffer holen
	item = buffer[lo];
	printf("Got %d\n", item);
	lo = (lo+1)%N;
	counter--;
	
	//wenn der Puffer vorher voll war -> signalisieren
	if(counter == (N-1))
	{
	    printf("Signalisiere empty\n");
	    pthread_cond_signal(&empty);
	}

	//Mutex wieder freigeben
	pthread_mutex_unlock(&counter_mutex);
    }
}

/*---------------------------------------------------------------------------*/

//Signalhandler
void sighandler(int sig)
{
    printf("Signal %d caught.\n", sig);
 
    //Threads töten
    pthread_cancel(producer);
    pthread_cancel(consumer);

    //Mutex und Conditions freigeben
    pthread_mutex_destroy(&counter_mutex);
    pthread_cond_destroy(&empty);
    pthread_cond_destroy(&full);

    //Schluss mit lustig
    exit(0); 
}

/*---------------------------------------------------------------------------*/

int main(int argv, char** args)
{
    int result;

    //auf Signale höreen
    signal(SIGTERM, sighandler);
    signal(SIGINT, sighandler);

    //Mutexe und Kondition initialisieren
    pthread_mutex_init(&counter_mutex, NULL);
    pthread_cond_init(&empty, NULL);
    pthread_cond_init(&full, NULL);

    //globale Variablen initialisieren
    counter = 0;
    hi = lo = 0;

    //Zufallszahlengenerator anschmeissen
    srandom(time(NULL));

    printf("Producer faengt an...\n");
    //Thread erzeugen
    result = pthread_create(&producer, NULL, Producer, NULL);

    //hat nicht geklappt-schade auch
    if(result)
    {
	printf("Producer will nicht...\n");
	exit(1);
    }

    printf("Consumer faengt an...\n");
    //Thread erzeugen
    result = pthread_create(&consumer, NULL, Consumer, NULL);

    //hat nicht geklappt-schade auch
    if(result)
    {
	printf("Consumer will nicht...\n");
	exit(1);
    }
    
    //wir wollen natürlich nicht das Programm beenden, also Endlosschleife
    while(1);
}

