#include <sys/sem.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <zconf.h>
#include <wait.h>
#include <time.h>

#define SEM_CLE 1
#define SHM_CLE 2

struct sembuf op[2];

void mineur(int i);

int sem_id;     // id de la semaphore
int shm_id;     // id de la mémoire partagé
int *caisse;    // case mémoire

int randomNumber(int max);

void P();

void V();

int main(int argc, char **argv) {
    if (argc < 3) {
        printf("mineOr nbDeMineurs nbDePelles nbDePioches\n");
        exit(0);
    }

    int nbDeMineurs = atoi(argv[1]);
    int nbDePelles = atoi(argv[2]);
    int nbDePioches = atoi(argv[3]);

    /*
     * creation des semaphores pour la pelle et la pioche
     */
    key_t key_sem = ftok(argv[0], SEM_CLE); // création d'une clé unique
    sem_id = semget(key_sem, 3, IPC_CREAT | IPC_EXCL | 0666); // création de trois semaphores
    if (sem_id < 0) {
        perror("semget() creation des semaphores impossible");
        exit(EXIT_FAILURE);
    }
    semctl(sem_id, 0, SETVAL, nbDePelles);  // initialise le nombre de pelle dans la semaphore
    semctl(sem_id, 1, SETVAL, nbDePioches); // initialise le nombre de pioche dans la semaphore
    semctl(sem_id, 2, SETVAL, 1);           // pas d'accès simultanée à la caisse, une personne à la fois

    /*
     * creation du segment partagee qui represente la caisse
     */
    key_t key_shm = ftok(argv[0], SHM_CLE); // création d'une clé unique
    shm_id = shmget(key_shm, 4, IPC_CREAT | IPC_EXCL | SHM_R | SHM_W | 0666); // création du segment partagé
    if (shm_id < 0) {
        perror("shmget()");
        exit(EXIT_FAILURE);
    }

    /*
     * attachement du processus a la zone de memoire
     * recuperation du pointeur sur la zone memoire commune
     */
    caisse = shmat(shm_id, 0, 0);
    if (caisse == (int *) -1) {
        perror("shmat() attachement zone memoire impossible");
        exit(EXIT_FAILURE);
    }

    *caisse = 0; // initialisation de la caisse à 0

    /*
     * creation des mineurs avec des processus
     */
    pid_t pid;
    for (int i = 1; i <= nbDeMineurs; ++i) {
        switch (pid = fork()) {
            case -1 :
                perror("fork()");
            case 0 : // child
                mineur(i);
                exit(EXIT_SUCCESS);
            default: // father
                printf("Création du mineur %i (%i)\n", i, pid);
        }
    }

    int status;
    // wait for any child process
    while ((pid = wait(&status)) != -1)
        printf("Le mineur de pid %d a fini avec pour status %i.\n", pid, WEXITSTATUS(status));

    printf("Fin du travail des mineurs, la caisse contient: %d g d'or.\n", *caisse);

    /*
     * detachement du segment
     */
    if (shmdt(caisse) == -1) {
        perror("shmdt() detachement segment impossible");
        exit(EXIT_FAILURE);
    }

    // destruction de la memoire partagee
    if (shmctl(shm_id, IPC_RMID, 0) == -1) {
        perror("shmctl() destruction shared memory impossible");
        exit(EXIT_FAILURE);
    }
    // destruction de la sémaphore
    if (semctl(sem_id, 0, IPC_RMID, 0) == -1) {
        perror("semctl() destruction sémaphore impossible");
        exit(EXIT_FAILURE);
    }

    return 0;
}

void mineur(int numMineur) {
    srand((unsigned) time(NULL) * getpid());
    int attenteAvantTravail = randomNumber(5);
    int dureeDeTravail = randomNumber(5);
    int quantiteOr = 0;

    /* attente avant de se mettre au travail */
    printf("Le mineur %d attend %d heures avant de travailler.\n", numMineur, attenteAvantTravail);
    sleep((unsigned int) attenteAvantTravail);

    P(); /* Session critique pelle et pioche */

    printf("Le mineur %d prend une pelle et une pioche\n", numMineur);
    printf("Le mineur %d commence à travailler pour %d heures.\n", numMineur, dureeDeTravail);
    sleep((unsigned int) dureeDeTravail);
    quantiteOr = randomNumber(1000);
    printf("Le mineur %d a extrait %d g d'or\n", numMineur, quantiteOr);

    V(); /* Fin session critique pelle et pioche */
    printf("Le mineur %d rend une pelle et une pioche\n", numMineur);

    /* Prend de la caisse */
    op[0].sem_num = 2;
    op[0].sem_op = -1;
    op[0].sem_flg = SEM_UNDO;
    if (semop(sem_id, op, 1) == -1) {
        perror("semop() [P] prise de la caisse\n");
        exit(EXIT_FAILURE);
    }

    /* Dépose l'argent dans la caisse */
    *caisse += quantiteOr;
    printf("Le mineur %d met %d g d'or dans la caisse\n", numMineur, quantiteOr);

    printf("Le mineur %d rend la caisse qui contient maintenant %d\n", numMineur, *caisse);
    /* Rend de la caisse */
    op[0].sem_num = 2;
    op[0].sem_op = 1;
    op[0].sem_flg = SEM_UNDO;
    if (semop(sem_id, op, 1) == -1) {
        perror("semop() [V] rend la caisse\n");
        exit(EXIT_FAILURE);
    }
}

int randomNumber(int max) {
    return (unsigned int) (rand() % max + 1);
}

void P() {
    /* Prise de la pelle */
    op[0].sem_num = 0; // Numéro de notre sémaphore 0 pour la pelle
    op[0].sem_op = -1; // Pour un P() on décrémente
    op[0].sem_flg = SEM_UNDO; // On ne s'en occupe pas

    /* Prise de la pioche */
    op[1].sem_num = 1; // Numéro de notre sémaphore 1 pour la pioche
    op[1].sem_op = -1; // Pour un P() on décrémente
    op[1].sem_flg = SEM_UNDO;

    /* Application des operations */
    if (semop(sem_id, op, 2) == -1) {
        perror("semop() P failed");
        exit(EXIT_FAILURE);
    }
}

void V() {
    /* Rend la pelle */
    op[0].sem_num = 0; // Numéro de notre sémaphore 0 pour la pelle
    op[0].sem_op = 1;  // Pour un V() on incrémente
    op[0].sem_flg = SEM_UNDO;

    /* Rend la pioche */
    op[1].sem_num = 1; // Numéro de notre sémaphore 1 pour la pioche
    op[1].sem_op = 1;  // Pour un V() on incrémente
    op[1].sem_flg = SEM_UNDO;

    /* Application des operations */
    if (semop(sem_id, op, 2) == -1) {
        perror("semop() V failed");
        exit(EXIT_FAILURE);
    }
}