// Includes
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>
#include <semaphore.h>
 
// Constantes
#define NB_PERS_MAX 100
#define NB_ATTRACTION 5
#define TIME_ATTRAC_MAX 12
#define PERS_MAX_ATTRAC 10
#define TPS_OUVERTURE_PARC 120
#define ARGENT_MAX 50
#define COUT_MAX 2
 
// Definition du type state correspondant
// aux états qu'une personne peut avoir
typedef enum
{
    ARR = 0, FREE, PAY, QUIT
}STATE;
 
// Définition de la structure personne
typedef struct
{
    int id;
    STATE etat;
    int temps; // temps passé dans le parc d'attractions
     int money; // Optionnel
}PERSONNE;
// Définition de la structure Attraction gratuite
typedef struct
{
    int id;
    int visite;
}ATTRACTION_FREE;
// Définition de la structure attraction payante
typedef struct
{
    int id;
    int capacite; // Capacité de l'attraction
    int temps; // Durée de l'attraction
    int visite;
}ATTRACTION_PAY;
// Structure permmettant de stocker l'id et le nombre de visite de l'attraction la plus visitée
typedef struct
{
    int max;
    int id;
}SEARCHMAX;
 
 
// Variables globales
// Tableau de sémaphore
sem_t* sem;
 
ATTRACTION_PAY* tabAttrac_P;
ATTRACTION_FREE* tabAttrac_F;
int nbAttrac_P;
int nbAttrac_F;
int temps;
// Prototypes des handlers
void * handlerAttrac (void*);
void * handlerPers (void*);
int timer (void);
 
int main (void)
{
    /// Déclarations
 
    int nbPers;
 
    int i;
    SEARCHMAX maxAP;
    SEARCHMAX maxAF;
    int somme = 0;
    PERSONNE* tabPers;
    pthread_t* tabThreadP; // Une personne correspond à un thread
 
    // Une attraction payante correspond à un thread
    pthread_t* tabThreadAttrac_P;
 
 
    // Définition des nombres aléatoires
 
    // Génère un nombre aléatoire de personnes entre 1 et 100
    nbPers = rand()%NB_PERS_MAX+1;
    // Génère un nombre aléatoire d'attraction payante entre 1 et 4
    nbAttrac_P = (rand()%(NB_ATTRACTION-1)) + 1;
    // Les attractions qui restent sont donc gratuites
    nbAttrac_F = NB_ATTRACTION - nbAttrac_P;
    // Allocation dynamique pour les personnes
    tabPers = (PERSONNE*) malloc (nbPers*sizeof (PERSONNE));
    tabThreadP = (pthread_t*) malloc (nbPers*sizeof (pthread_t));
    // Allocation dynamique pour les attractions payantes
    tabAttrac_P = (ATTRACTION_PAY*) malloc (nbAttrac_P*sizeof (ATTRACTION_PAY));
    tabThreadAttrac_P = (pthread_t*) malloc (nbAttrac_P*sizeof (pthread_t));
    // Allocation dynamique pour les attractions gratuites
    tabAttrac_F = (ATTRACTION_FREE*) malloc (nbAttrac_F*sizeof (ATTRACTION_FREE));
    // Allocation dynamique du tableau de semaphore
    sem = (sem_t*) malloc (nbAttrac_P*sizeof (sem_t));
    // Initialisation des structures personnes
    for (i = 0; i < nbPers; i++)
    {
        tabPers[i].id = i;
        tabPers[i].etat = ARR;        // Toutes les personnes arrivent
        tabPers[i].temps = 0;    // Gestion du temps passé dans le parc
        tabPers[i].money = rand ()%ARGENT_MAX;
    }
    // Initialisation des structures attractions payantes et des semaphores
    for (i = 0; i < nbAttrac_P; i++)
    {
        tabAttrac_P[i].id = i;
        tabAttrac_P[i].temps = rand()%TIME_ATTRAC_MAX+1;
        tabAttrac_P[i].capacite = rand()%PERS_MAX_ATTRAC+1;
        tabAttrac_P[i].visite = 0;
        tabAttrac_P
[i
].
cout = rand
()%COUT_MAX
+1 ;
        sem_init (&sem[i], 0, rand()%PERS_MAX_ATTRAC+1);
    }
    // Initialisation des structures attractions gratuites
    for (i = 0; i < nbAttrac_F; i++)
    {
        tabAttrac_F[i].id = i;
        tabAttrac_F[i].visite = 0;
    }
    puts ("Bienvenue à DigiWorld ! : OUVERTURE DES PORTES DU PARC" );
 
    // Création des threads pour les personnes
    for (i = 0; i < nbPers; i++)
        pthread_create (&tabThreadP[i], NULL, handlerPers, &tabPers[i]);
    // Création des threads pour les attractions payantes
    for (i = 0; i < nbAttrac_P; i++)
        pthread_create (&tabThreadAttrac_P[i], NULL, handlerAttrac, NULL);
 
    // Attente des threads
    while (temps < TPS_OUVERTURE_PARC)
        timer();
 
    for (i = 0; i < nbPers; i++)
        pthread_join(tabThreadP[i], NULL);
    for (i = 0; i < nbAttrac_P; i++)
        pthread_join(tabThreadAttrac_P[i], NULL);
 
    // Expulsion des personnes
    for (i = 0; i < nbPers; i++)
    {
        tabPers[i].etat = QUIT;        // Toutes les personnes partent
        somme += tabPers[i].temps;
    }
    puts ("DigiWorld : A Bientot  ! : FERMETURE DES PORTES DU PARC" );
    // Recherche des max dans les deux types d'attractions
    maxAP.max = 0;
    maxAP.id = 0;
    maxAF.max = 0;
    maxAF.id = 0;
    // Attractions payantes
    for ( i = 0 ; i < nbAttrac_P ; i++)
    {
        if (tabAttrac_P[i].visite > maxAP.max)
        {
            maxAP.max = tabAttrac_P[i].visite;
            maxAP.id = i;
        }
        else
        {
            maxAP.max = maxAP.max;
            maxAP.id = maxAP.id;
        }
    }
 
    // Attractions gratuites
    for ( i = 0 ; i < nbAttrac_F ; i++)
    {
        if (tabAttrac_F[i].visite > maxAF.max)
        {
            maxAF.max = tabAttrac_F[i].visite;
            maxAF.id = i;
        }
 
    }
    // Résumé de la journée
    printf ("%d personnes ont visité le parc aujourd'hui \n", nbPers
);
     printf ("et ont passé en moyenne %d min %d s dans le parc \n", 
(somme/nbPers
)/
60, 
(somme/nbPers
)%
60);
     if (nbAttrac_F > 0)
        printf ("L'attraction gratuite la plus visitée est l'attraction %d avec %d visites\n", maxAF.
id, maxAF.
max);
     printf ("L'attraction payante la plus visitée est l'attraction %d avec %d visites\n", maxAP.
id, maxAP.
max);
     // Libération de la mémoire
    free (tabPers);
    free (tabThreadP);
    free (tabAttrac_P);
    free (tabThreadAttrac_P);
    free (tabAttrac_F);
    return 0;
}
// Fonction qui compte le temps
int timer ()
{
    sleep(1);
    return ++temps;
}
 
// Fonction pour les threads des attractions payantes
void * handlerAttrac (void* personne)
{
    int ind;
    PERSONNE * p = (PERSONNE *) personne;
    float tmpn;
    int actuelTemps;
    // Indice aléatoir du tableau
    ind = rand()%nbAttrac_P;
    // Vérification si la personne est solvable
    tmpn = p->money - tabAttrac_P
[ind
].
cout;
    if (tmpn < 0)
    {
        p->etat = (rand ()%2)+1;
        handlerPers(p);
        return;
    }
    else
    {
        actuelTemps = temps;
        // TQ que le parc est ouvert on essaye de faire une prise de sémaphore
        while (temps < TPS_OUVERTURE_PARC)
        {
            while (sem_wait (&sem[ind]) == -1);
        }
 
        if (temps >= TPS_OUVERTURE_PARC)
        {
            p->etat = QUIT;
            return;
        }
        else
        {
            tabAttrac_P[ind].visite++;
            p->money -= tabAttrac_P
[ind
].
cout;
            p->temps += temps - actuelTemps;
            return;
        }
 
    }
 
}
void * handlerPers (void* visiteur)
{
    PERSONNE* pers = (PERSONNE*) visiteur;
    // TQ on ne dépasse pas le temps d'ouverture du parc
    while (temps <= TPS_OUVERTURE_PARC)
    {
        // Selon l'état dans lequel la personne se trouve...
        switch (pers->etat)
        {
            case ARR :
                    // Etat initial
                    // Changement d'état
                    pers->etat = (rand ()%2)+1;
                    return;
                    break;
            case FREE:
                    // Etat Attraction libre
                    // Incrémentation du temps
                    pers->temps += (rand()%TPS_OUVERTURE_PARC)+1;
                    // +1 visite
                    tabAttrac_F [(rand ()%nbAttrac_F)+1].visite++;
                    // Changement d'état
                    pers->etat = (rand ()%3)+1;
                    return;
                    break;
            case PAY :
                    // Etat Attraction payante
                    handlerAttrac(pers);
                    return;
                    break;
            case QUIT :
                    // Quitte Digiworld
                    return;
                    break;
        }
    }
    pers->etat = QUIT;
    return;
}