//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
//Anzahl Producer
#define PRODS 2
//Anzahl Consumer
#define CONS 4

//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[PRODS];
//Thread für den Consumer
pthread_t consumer[CONS];

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

//ein Thread für den Producer
void *Producer(void *arg)
{
    int argument = *(int *)arg;

    //immer als erstes: Argumente freigeben
    free(arg);

    printf("Producer %d startet\n", argument);

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

	//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("Producer %d: warten auf empty\n", argument);
	    //auf condition empty warten
	    //der Mutex wird automatisch freigegeben und belegt
	    pthread_cond_wait(&empty, &counter_mutex);
	    printf("Producer %d: warten auf empty beendet\n", argument);
	}
    
	//item produzieren und in den Puffer legen
	buffer[hi] = random()%50;
	printf("Producer %d: put %d\n", argument, buffer[hi]);
	hi = (hi+1)%N;
	counter++;

	//wenn der Puffer vorher leer war -> signalisieren, dass es nun
	//was gibt
	if(counter == 1)
	{
	    printf("Producer %d: signalisiere full\n", argument);
	    pthread_cond_broadcast(&full);
	}
	
	//Mutex freigeben
	pthread_mutex_unlock(&counter_mutex);
    }
}

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

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

    //Argument freigeben
    free(arg);

    printf("Consumer %d startet\n", argument);

    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("                              ");
	    printf("Consumer %d: warte auf full\n", argument);
	    //Mutex wird automatisch freigegeben und belegt
	    pthread_cond_wait(&full, &counter_mutex);
	    printf("                              ");
	    printf("Consumer %d: warten auf full beendet\n", argument);
	}
    
	//item aus Puffer holen
	item = buffer[lo];
	printf("                              ");
	printf("Consumer %d: got %d\n", argument, item);
	lo = (lo+1)%N;
	counter--;
	
	//wenn der Puffer vorher voll war -> signalisieren
	if(counter == (N-1))
	{
	    printf("                              ");
	    printf("Consumer %d: signalisiere empty\n", argument);
	    pthread_cond_broadcast(&empty);
	}

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

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

//Signalhandler
void sighandler(int sig)
{
    int i;
    printf("Signal %d caught.\n", sig);
 
    //Threads töten
    for(i = 0; i < PRODS; i++)
	pthread_cancel(producer[i]);
    for(i = 0; i < CONS; i++)
	pthread_cancel(consumer[i]);

    //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;
    int i;
    int *argument;

    //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));

    for(i = 0; i < PRODS; i++)
    {
	argument = (int *)malloc(sizeof(int));
	*argument = i;

	printf("Producer %d faengt an...\n", *argument);
	//Thread erzeugen
	result = pthread_create(&(producer[i]), NULL, Producer, argument);

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

    for(i = 0; i < CONS; i++)
    {
	argument = (int *)malloc(sizeof(int));
	*argument = i;

	printf("Consumer %d faengt an...\n", *argument);
	//Thread erzeugen
	result = pthread_create(&(consumer[i]), NULL, Consumer, argument);

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

