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

#define NB_MENEURS 5
#define NB_SUIVEURS 6

/* attendre delai microsecondes */
void microsleep(int delai);

void *meneur(void *indice);
void *suiveur(void *indice);
void se_preparer();
void suiveur_danser(int num_suiveur, int num_meneur);
void meneur_danser(int num_meneur, int num_suiveur);

int numero_suiveur, numero_meneur;
int nb_meneurs_en_attente, nb_suiveurs_en_attente;
pthread_mutex_t choisir_partenaire;
sem_t file_suiveurs;
sem_t file_meneurs;
sem_t qui_es_tu;

int main(void)
{
	pthread_t meneurs[NB_MENEURS], suiveurs[NB_SUIVEURS];
	int indices_meneurs[NB_MENEURS], indices_suiveurs[NB_SUIVEURS];
	int i;

	pthread_mutex_init(&choisir_partenaire, NULL);
	sem_init(&file_suiveurs, 0, 0);
	sem_init(&file_meneurs, 0, 0);
	sem_init(&qui_es_tu, 0, 0);

	for (i = 0; i < NB_MENEURS; ++i) {
		indices_meneurs[i] = i;
		pthread_create(&meneurs[i], NULL, meneur, &indices_meneurs[i]);
	}
	for (i = 0; i < NB_SUIVEURS; ++i) {
		indices_suiveurs[i] = i;
		pthread_create(&suiveurs[i], NULL, suiveur, &indices_suiveurs[i]);
	}

	for (i = 0; i < NB_MENEURS; ++i)
		pthread_join(meneurs[i], NULL);
	for (i = 0; i < NB_SUIVEURS; ++i)
		pthread_join(suiveurs[i], NULL);
	return 0;
}
void se_preparer()
{
	microsleep(500000 + 400000 * (rand() % 10));
}
void *meneur(void *indice)
{
	int mon_indice = *((int *) indice);
	int indice_partenaire;
	for (;;) {
		se_preparer();
		pthread_mutex_lock(&choisir_partenaire);
		if (nb_suiveurs_en_attente > 0) {
			--nb_suiveurs_en_attente;
			numero_meneur = mon_indice;
			sem_post(&file_suiveurs);
			sem_wait(&qui_es_tu);
			indice_partenaire = numero_suiveur;
			pthread_mutex_unlock(&choisir_partenaire);
		} else {
			++nb_meneurs_en_attente;
			/* meneur n'a pas pu choisir de partenaire. */
			pthread_mutex_unlock(&choisir_partenaire);
			sem_wait(&file_meneurs);
			numero_meneur = mon_indice;
			indice_partenaire = numero_suiveur;
			sem_post(&qui_es_tu);
		}
		meneur_danser(mon_indice, indice_partenaire);
	}
	return NULL;
}
void *suiveur(void *indice)
{
	int mon_indice = *((int *) indice);
	int indice_partenaire;
	for (;;) {
		se_preparer();
		pthread_mutex_lock(&choisir_partenaire);
		if (nb_meneurs_en_attente > 0) {
			/* suiveur va choisir un partenaire */
			--nb_meneurs_en_attente;
			numero_suiveur = mon_indice;
			sem_post(&file_meneurs);
			sem_wait(&qui_es_tu);
			indice_partenaire = numero_meneur;
			pthread_mutex_unlock(&choisir_partenaire);
		} else {
			++nb_suiveurs_en_attente;
			/* suiveur n'a pas pu choisir de partenaire. */
			pthread_mutex_unlock(&choisir_partenaire);
			sem_wait(&file_suiveurs);
			numero_suiveur = mon_indice;
			indice_partenaire = numero_meneur;
			sem_post(&qui_es_tu);
		}
		suiveur_danser(mon_indice, indice_partenaire);
	}
	return NULL;
}

void suiveur_danser(int num_suiveur, int num_meneur)
{
	printf("suiveur %d danse avec meneur %d\n", num_suiveur, num_meneur);
	microsleep(1000000 * 5);
	printf("suiveur %d arrête de danser avec meneur %d\n", num_suiveur, num_meneur);
}
void meneur_danser(int num_meneur, int num_suiveur)
{
	printf("meneur %d danse avec suiveur %d\n", num_meneur, num_suiveur);
	microsleep(1000000 * 5);
	printf("meneur %d arrête de danser avec suiveur %d\n", num_meneur, num_suiveur);
}

void microsleep(int delai)
{
	struct timespec ts = { .tv_sec = delai / 1000000,
		.tv_nsec = 1000 * (delai % 1000000)  };
	nanosleep(&ts, NULL);
}
