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

#define MEDICI_ORTOPEDICI 1
#define MEDICI_GENERICI 2

#define PAZIENTI_GENERICI 5
#define PAZIENTI_ORTOPEDICI 8

#define CAP 4
#define SALE_GESSI 1 // nel caso di sviluppi futuri con più sale gessi


typedef struct
{
	pthread_mutex_t mutex;
	
	int saleGessi;
	pthread_cond_t salaGessiLibera;
		
} salaGessi;
	
typedef struct
{
	pthread_mutex_t mutex;
	
	int medici_generici;
	pthread_cond_t medicoGenericoLibero;
	
} mediciGenerici;
	
typedef struct
{
	pthread_mutex_t mutex;
	
	int medici_ortopedici;
	pthread_cond_t medicoOrtopedicoLibero;
	
} mediciOrtopedici;

typedef struct
{
	pthread_mutex_t mutex;
	
	int posti_letto;
	pthread_cond_t postoLettoLibero;	
	
} prontoSoccorso;
	
typedef struct
{
	prontoSoccorso* pronto_soccorso;
	mediciGenerici* medici_generici;
	mediciOrtopedici* medici_ortopedici;
	salaGessi* sala_gessi;

} ospedale;
	
typedef struct
{
	char nome[256];
	ospedale* o;
	
} paziente;
	
/** Inizializzazione strutture */
prontoSoccorso* initProntoSoccorso()
{
	prontoSoccorso* p = (prontoSoccorso*) malloc( sizeof(prontoSoccorso) );
	pthread_mutex_init(&p->mutex, NULL);
	
	p->posti_letto = CAP;
	pthread_cond_init(&p->postoLettoLibero, NULL);	
	
	return p;
}

mediciGenerici* initMediciGeneri()
{
	mediciGenerici* mg = (mediciGenerici*) malloc (sizeof (mediciGenerici) );
	pthread_mutex_init(&mg->mutex, NULL);
	
	mg->medici_generici  = MEDICI_GENERICI;
	pthread_cond_init(&mg->medicoGenericoLibero, NULL);
	
	return mg;
}

mediciOrtopedici* initMediciOrtopedici()
{
	mediciOrtopedici* mo = (mediciOrtopedici*) malloc( sizeof (mediciOrtopedici) );
	pthread_mutex_init(&mo->mutex, NULL);
	
	mo->medici_ortopedici = MEDICI_ORTOPEDICI;
	pthread_cond_init(&mo->medicoOrtopedicoLibero, NULL);
	
	return mo;
}

salaGessi* initSalaGessi()
{
	salaGessi* s = (salaGessi*) malloc( sizeof (salaGessi) );
	pthread_mutex_init(&s->mutex, NULL);
	
	s->saleGessi = SALE_GESSI;
	pthread_cond_init(&s->salaGessiLibera, NULL);
	
	return s;
}

void initOspedale(ospedale* o)
{	
	o->pronto_soccorso = initProntoSoccorso();
	o->sala_gessi = initSalaGessi();
	
	o->medici_generici = initMediciGeneri();
	o->medici_ortopedici = initMediciOrtopedici();
}

/***************** POSTO LETTO **********************************************/
void richiestaPostoLetto(ospedale* o)
{
	pthread_mutex_lock(&o->pronto_soccorso->mutex);
	
	if(!(o->pronto_soccorso->posti_letto))
	{
		printf("Attesa posto letto libero\n");
		pthread_cond_wait(&o->pronto_soccorso->postoLettoLibero,
			&o->pronto_soccorso->mutex);
	}
	(o->pronto_soccorso->posti_letto)--;
	
	pthread_mutex_unlock(&o->pronto_soccorso->mutex);
}

void rilascioPostoLetto(ospedale *o)
{
	pthread_mutex_lock(&o->pronto_soccorso->mutex);
	
	(o->pronto_soccorso->posti_letto)++;
	pthread_cond_signal(&o->pronto_soccorso->postoLettoLibero);
	
	pthread_mutex_unlock(&o->pronto_soccorso->mutex);	
}

/************************* MEDICO GENERICO **********************************/
void richiestaMedicoGenerico(ospedale *o)
{
	pthread_mutex_lock(&o->medici_generici->mutex);
	
	if(!(o->medici_generici->medici_generici))
	{
		printf("Attesa medico generico\n");
		pthread_cond_wait(&o->medici_generici->medicoGenericoLibero,
			&o->medici_generici->mutex);
	}
	(o->medici_generici->medici_generici)--;
	
	pthread_mutex_unlock(&o->medici_generici->mutex);
}

void rilascioMedicoGenerico(ospedale* o)
{
	pthread_mutex_lock(&o->medici_generici->mutex);	
	
	(o->medici_generici->medici_generici)++;
	pthread_cond_signal(&o->medici_generici->medicoGenericoLibero);
	
	pthread_mutex_unlock(&o->medici_generici->mutex);	
}

/********************* MEDICO ORTOPEDICO **********************************/
void richiestaMedicoOrtopedico(ospedale* o)
{
	pthread_mutex_lock(&o->medici_ortopedici->mutex);
	
	if(!(o->medici_ortopedici->medici_ortopedici))
	{
		printf("Attesa di medico ortopedico\n");
		pthread_cond_wait(&o->medici_ortopedici->medicoOrtopedicoLibero,
			&o->medici_ortopedici->mutex);
	}
	(o->medici_ortopedici->medici_ortopedici)--;
	
	pthread_mutex_unlock(&o->medici_ortopedici->mutex);
}

void rilascioMedicoOrtopedico(ospedale* o)
{
	pthread_mutex_lock(&o->medici_ortopedici->mutex);	
	
	(o->medici_ortopedici->medici_ortopedici)++;
	pthread_cond_signal(&o->medici_ortopedici->medicoOrtopedicoLibero);
	
	pthread_mutex_unlock(&o->medici_ortopedici->mutex);	
}

/****************************** SALA GESSI *********************************/
void richiestaSalaGessi(ospedale* o)
{
	pthread_mutex_lock(&o->sala_gessi->mutex);
	
	if(!(o->sala_gessi->saleGessi))
	{
		printf("Attesa di salagessi libera\n");
		pthread_cond_wait(&o->sala_gessi->salaGessiLibera,&o->sala_gessi->mutex);
	}
	(o->sala_gessi->saleGessi)--;
	
	pthread_mutex_unlock(&o->sala_gessi->mutex);
}

void rilascioSalaGessi(ospedale* o)
{
	pthread_mutex_lock(&o->sala_gessi->mutex);	
	
	(o->sala_gessi->saleGessi)++;
	pthread_cond_signal(&o->sala_gessi->salaGessiLibera);
	
	pthread_mutex_unlock(&o->sala_gessi->mutex);	
}

// PROCEDURE DI AVVIO DEI THREADS
void* pazienteGenerico(void* arg)
{
	paziente* p = (paziente*) arg;

	sleep((rand()%5));
	printf("%s: chiedo un posto letto\n", p->nome);
	richiestaPostoLetto(p->o);		

	printf("%s: chiedo l'intervento di un medico generico\n", p->nome);
	richiestaMedicoGenerico(p->o);

	sleep((rand()%5));
	printf("%s: il medico genrico ha finito la visita\n", p->nome);
	rilascioMedicoGenerico(p->o);

	printf("%s: libero il letto\n", p->nome);
	rilascioPostoLetto(p->o);
}

void* pazienteOrtopedico(void* arg)
{
	paziente* p = (paziente*) arg;
	
	int sono_da_ingessare = 0;	
	
	sleep((rand() %5));
	printf("%s: chiedo un posto letto...\n", p->nome);
	richiestaPostoLetto(p->o);
	
	printf("%s: richiesta medico ortopedico...\n", p->nome);
	richiestaMedicoOrtopedico(p->o);
	
	printf("%s: richiesta medico generico...\n", p->nome);
	richiestaMedicoGenerico(p->o);
	
	printf("%s: inizia la visita\n", p->nome);
	sleep((rand() %5));
	sono_da_ingessare = rand() %2;
	printf("%s: i medici hanno finito di visitarmi\n", p->nome);
	rilascioMedicoGenerico(p->o);
	rilascioMedicoOrtopedico(p->o);
	
	if(sono_da_ingessare)
	{
		printf("%s: devo essere ingessato, vado in sala gessi\n", p->nome);
		richiestaSalaGessi(p->o);
		
		sleep((rand()%5));
		printf("%s: hanno finito di ingessarmi..\n", p->nome);
		rilascioSalaGessi(p->o);
	}
	else
		printf("%s: non devo essere ingessato...\n", p->nome);

	printf("%s: libero il letto...\n",p->nome);
	rilascioPostoLetto(p->o);
}

int main()
{
	ospedale* o = (ospedale*) malloc(sizeof(ospedale));
	initOspedale(o);
	
	pthread_t pazientiGenerici[PAZIENTI_GENERICI];
	pthread_t pazientiOrtopedici[PAZIENTI_ORTOPEDICI];
	int i;
	
	/* Creazione dei threads */
	for(i = 0; i < PAZIENTI_GENERICI; i++)	
	{
		paziente* p = (paziente*) malloc(sizeof(paziente));
		sprintf(p->nome,"Paziente Generico %d",i);
		p->o = o;
		pthread_create(&pazientiGenerici[i],NULL,pazienteGenerico,p);
		sleep(1);
	}
	for(i = 0; i < PAZIENTI_ORTOPEDICI; i++)	
	{
		paziente* p = (paziente*) malloc(sizeof(paziente));
		sprintf(p->nome,"Paziente Ortopedico %d",i);
		p->o = o;
		pthread_create(&pazientiOrtopedici[i],NULL,pazienteOrtopedico,p);
		sleep(1);
	}
	
	/** Attesa terminazione dei threads */
	for(i = 0; i < PAZIENTI_GENERICI; i++)
	{
		pthread_join(pazientiGenerici[i],NULL);
	}
	for(i = 0; i < PAZIENTI_ORTOPEDICI; i++)
	{
		pthread_join(pazientiOrtopedici[i],NULL);
	}
	
	return (0);
}

