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

#define PMAX 5
#define ADULTI 7
#define BAMBINI 7


typedef struct
{
	pthread_mutex_t lock;

	int visitaInCorso;	
	
	int proxVisitaAdulti; // 1 = si, 0 = no
	int adultiInAttesa;
	int adultiInVisita;
	
	int proxVisitaBimbi; // 1 = si, 0 = no
	int bimbiInAttesa;
	int bimbiInVisita;	

	pthread_cond_t inizioVisita;
	pthread_cond_t attesaAdulti;
	pthread_cond_t attesaBimbi;
	pthread_cond_t fineVisita;
	
} castello;

typedef struct
{
	char nome[255];
	castello* c;
	
} visitatore;

/** Inizializzazione del castello */
void init(castello* c)
{	
	pthread_mutex_init( &c->lock, NULL);
	
	pthread_cond_init( &c->inizioVisita, NULL);
	pthread_cond_init( &c->fineVisita, NULL);
	
	pthread_cond_init( &c->attesaAdulti, NULL);
	pthread_cond_init( &c->attesaBimbi, NULL);
	
	c->visitaInCorso = 0;	
	
	c->adultiInAttesa = 0;
	c->bimbiInAttesa = 0;
	
	/** Entrambe le variabili sono inizializzate a 0 perchè il tipo di thread
	 * che inizia la visita dipende strettamente dalla sequenza di thread
	 * che arrivano ad occupare le risorse
	 */
	c->proxVisitaAdulti = 0;
	c->proxVisitaBimbi = 0;
	
	c->adultiInVisita = 0;
	c->bimbiInVisita = 0;	
}

/** Determina il numero di posti occupati nel trenino che fa la visita al
 * castello. Il numero di posti occupati è definito come la somma del numero
 * dei bambini in visita più il numero di adulti in attesa, ben sapendo che se
 * un tipo di thread è entrato, allora l'altro tipo di thread non può entrare
 * e il suo numero di visitatori sarà 0
 */
int postiOccupati(castello* c)
{
	return (c->adultiInVisita) + (c->bimbiInVisita);
}

/** Procedura che permette l'inizio della visita: la visita comincia quando ci
 * sono PMAX o più visitatori dello stesso tipo in attesa per la visita
 */
void inizioVisita(castello* c)
{
	pthread_mutex_lock( &c->lock );
	
	while(postiOccupati(c) < PMAX)
	{
		printf("In attesa di almeno %d visitatori\n",PMAX);
		pthread_cond_wait( &c->inizioVisita, &c->lock);
	}
	c->visitaInCorso = 1;
	printf("Visita in corso: posti occupati: %d\n",postiOccupati(c));
	printf("Adulti in attesa: %d - Bambini in attesa: %d\n",
		c->adultiInAttesa,c->bimbiInAttesa);
		
	pthread_mutex_unlock( &c->lock);
}

/** Termine della visita e rilascio dei threads visitatori
 */
void fineVisita(castello* c)
{
	pthread_mutex_lock(&(c->lock));

	int i = 0;	
	c->visitaInCorso = 0;	
	printf("Fine visita\nAdulti in attesa: %d - Bambini in attesa: %d\n",
		c->adultiInAttesa,c->bimbiInAttesa);
	
	for(i = 0; i < postiOccupati(c); i++)
	{
		pthread_cond_signal( &c->fineVisita );
	}
	
	if( (c->adultiInAttesa) > (c->bimbiInAttesa) )
	{	
		c->proxVisitaBimbi = 0;
		c->proxVisitaAdulti = 1;
		printf("La prossima visita sara' per adulti\n");
		pthread_cond_signal(&(c->attesaAdulti));		
	}
	else
	{
		c->proxVisitaAdulti = 0;
		c->proxVisitaBimbi = 1;
		printf("La prossima visita sara' per bambini\n");		
		pthread_cond_signal(&c->attesaBimbi);
	}		
	
	pthread_mutex_unlock(&c->lock);
}

void richiestaAdulto(castello* c)
{
	pthread_mutex_lock( &c->lock );	
	while(c->visitaInCorso || c->proxVisitaBimbi 
		|| c->bimbiInVisita > 0 || postiOccupati(c) >= PMAX)
	{
		(c->adultiInAttesa)++;
		printf("Adulto in attesa\n");
		pthread_cond_wait(&(c->attesaAdulti),&(c->lock));
		(c->adultiInAttesa)--;
	}
	printf("Adulto accettato per la visita\n");
	
	(c->adultiInVisita)++;
	pthread_cond_signal(&c->attesaAdulti);
	
	if(postiOccupati(c) == PMAX)
		pthread_cond_signal(&c->inizioVisita);
	
	pthread_cond_wait(&c->fineVisita, &c->lock);
	(c->adultiInVisita)--;	
	
	pthread_mutex_unlock(&c->lock);
}


void richiestaBambino(castello* c)
{
	pthread_mutex_lock(&c->lock);
	
	while(c->visitaInCorso || c->proxVisitaAdulti 
		|| c->adultiInVisita > 0 || postiOccupati(c) >= PMAX)
	{
		(c->bimbiInAttesa)++;
		printf("Bambino in attesa\n");
		pthread_cond_wait(&c->attesaBimbi, &c->lock );
		(c->bimbiInAttesa)--;
	}	
	printf("Bambino accettato per la visita\n");
	
	(c->bimbiInVisita)++;	
	pthread_cond_signal(&c->attesaBimbi);
	
	if(postiOccupati(c) == PMAX)
		pthread_cond_signal(&c->inizioVisita );
	
	pthread_cond_wait(&c->fineVisita, &c->lock);
	(c->bimbiInVisita)--;
	
	 pthread_mutex_unlock(&c->lock);
}

// PROCEDURE DI AVVIO DEI THREADS
void* proprietario(void* arg)
{
	castello* c = (castello*) arg;
	
	/** Il proprietario continua indefinitamente ad aspettare visitatori
	 */
	while(1)
	{		
		inizioVisita(c);
		sleep(10);
		
		fineVisita(c);
		sleep(10);
	}
}

void* bambino(void* arg)
{
	visitatore* v = (visitatore*) arg;
	int sec;
	/** testing: per evitare la creazione di miriadi di thread si suppone che 
	 * i bambini, una volta terminata la visita, si rimettano in coda per fare
	 * un altro giro
	 */
	while(1)
	{
		sec = rand() %20;
		sleep(sec);
		printf("%s vuole visitare il castello\n",v->nome);
		
		richiestaBambino(v->c);
		printf("%s: fine visita\n",v->nome);				
	}	
}

void* adulto(void* arg)
{
	visitatore* v = (visitatore*) arg;
	int sec;
	
	/** testing: per evitare la creazione di miriadi di thread si suppone che 
	 * gli adulti, una volta terminata la visita, si rimettano in coda per fare
	 * un altro giro
	 */
	while(1)
	{
		sec = rand() %10;
		sleep(sec);	
		printf("%s vuole visitare il castello\n",v->nome);
		
		richiestaAdulto(v->c);
		printf("%s: fine visita\n",v->nome);		
	}	
}


int main(void)
{
	castello* c = (castello*) malloc (sizeof (castello) );
	init(c);
	
	pthread_t proprietario_th;
	pthread_t bambini_th[BAMBINI], adulti_th[ADULTI];
	int i = 0;
	
	// creazione del thread proprietario
	if( (pthread_create( &proprietario_th, NULL, proprietario, c) ) != 0)
	{
		printf("Errore nella creazione del thread proprietario\n");
		exit(0);
	}

	//Cicli di creazione dei threads visitatori
	for(i = 0; i < BAMBINI; i++)
	{
		visitatore* v = (visitatore*) malloc (sizeof (visitatore) );
		sprintf(v->nome, "Bimbo %d", i);
		v->c = c;
		pthread_create( &bambini_th[ i ], NULL, bambino, v);
		sleep(1);
	}	
	for(i = 0; i < ADULTI; i++)
	{
		visitatore* v = (visitatore*) malloc( sizeof( visitatore) );
		sprintf(v->nome, "Adulto %d", i);
		v->c = c;
		pthread_create(&adulti_th[ i ], NULL, adulto, v);
		sleep(1);
	}
	
	// Attesa della terminazione dei threads
	for(i = 0; i < BAMBINI; i++)
	{
		pthread_join(bambini_th[ i ], NULL);
		sleep(1);
	}
	for(i = 0; i < ADULTI; i++)
	{
		pthread_join(adulti_th[ i ], NULL);
		sleep(1);
	}	
	if( (pthread_join (proprietario_th, NULL) ) != 0)
	{
		printf("Join fallita (proprietario)\n");
		exit(1);
	}
	
	return 0;
}
