/*
 * parexec_lim.c
 * Q3 : Lancer au plus N instances en parallèle :  ./parexec_lim prog N arg1 [arg2 ...]
 * Q4 : Si une instance se termine à cause d'un SIGNAL -> tuer toutes les autres et arrêter.
 */

#include <stdio.h>      // fprintf, perror
#include <stdlib.h>     // atoi, calloc, free, EXIT_*
#include <unistd.h>     // fork, execlp, usleep
#include <sys/wait.h>   // wait, WIFSIGNALED, WTERMSIG
#include <signal.h>     // kill
#include <errno.h>      // errno

int main(int argc, char *argv[]) {
    // Vérifie la syntaxe minimale : prog + N + au moins 1 argument pour prog
    if (argc < 4) {
        fprintf(stderr, "Usage: %s prog N arg1 [arg2 ...]\n", argv[0]);
        return EXIT_FAILURE;
    }

    char *prog = argv[1];         // le programme à lancer (ex: "./rebours")
    int N = atoi(argv[2]);        // nombre maximum d'enfants en parallèle
    if (N <= 0) {                 // N doit être strictement positif
        fprintf(stderr, "N doit être > 0\n");
        return EXIT_FAILURE;
    }

    int next = 3;                 // index du prochain argument à donner à prog (argv[3] au début)
    int running = 0;              // nombre d'enfants actuellement en cours d'exécution
    int stop = 0;                 // =1 si on a détecté une fin anormale -> on arrête de relancer

    // Tableau des PIDs des enfants "actifs" (taille N). 0 signifie "case libre".
    pid_t *slots = calloc((size_t)N, sizeof(pid_t));
    if (!slots) { perror("calloc"); return EXIT_FAILURE; }

    // ===== 1) Lancer tout de suite jusqu'à N enfants (ou jusqu'à épuisement des args) =====
    while (!stop && running < N && next < argc) {
        pid_t pid = fork();                 // crée un nouveau processus
        if (pid == 0) {                     // dans l'enfant
            execlp(prog, prog, argv[next], NULL);   // remplace l'enfant par "prog argi"
            _exit(127);                     // si exec échoue, sortir avec code 127
        }
        if (pid < 0) {                      // fork a échoué dans le parent
            perror("fork");
            stop = 1;                       // on arrête les lancements
            break;
        }
        // Ici on est dans le parent et fork a réussi : on mémorise le PID dans une case libre  2 5 4 3 
        for (int i = 0; i < N; ++i) {
            if (slots[i] == 0) {            // 0 = case libre
                slots[i] = pid;             // on range ce PID
                break;
            }
        }
        running++;                           // on a un enfant de plus
        next++;                              // prochain argument à utiliser
    }

    // ===== 2) Boucle principale : attendre les fins, réagir, et (éventuellement) relancer =====
    while (running > 0) {                    // tant qu'il reste des enfants actifs
        int status = 0;
        pid_t pid = wait(&status);           // attendre qu'UN enfant se termine
        if (pid < 0) {                       // erreur (souvent EINTR). On simplifie : on réessaie.
            if (errno == EINTR){
                continue;
            } 
            perror("wait");
            break;
        }

        // Retirer ce PID du tableau des actifs (libérer sa case)
        for (int i = 0; i < N; ++i) {
            if (slots[i] == pid) {           // on a trouvé la case de cet enfant
                slots[i] = 0;                // on marque la case vide
                break;
            }
        }
        running--;                            // un enfant en moins

        // ===== Q4 : si l'enfant s'est terminé à cause d'un SIGNAL =====
        if (WIFSIGNALED(status)) {
            stop = 1;                         // ne plus lancer de nouveaux enfants
            // D'abord demander gentiment aux autres de s'arrêter
            for (int i = 0; i < N; ++i) {
                if (slots[i] > 0) kill(slots[i], SIGTERM);
            }
            usleep(100000);                   // petite pause (100 ms)
            // Puis forcer l'arrêt si certains sont encore vivants
            for (int i = 0; i < N; ++i) {
                if (slots[i] > 0) kill(slots[i], SIGKILL);
            }
            // on n'ajoute PAS de relance ici (stop=1)
        }

        // ===== Relancer si : pas d'arrêt global ET il reste des arguments à lancer =====
        if (!stop && next < argc) {
            pid_t npid = fork();              // crée un nouvel enfant
            if (npid == 0) {                  // dans le nouvel enfant
                execlp(prog, prog, argv[next], NULL); // lance "prog arg"
                _exit(127);                   // si exec échoue
            }
            if (npid < 0) {                   // fork a échoué
                perror("fork");
                stop = 1;                     // arrêter les relances
            } else {
                // ranger ce nouveau PID dans une case libre
                for (int i = 0; i < N; ++i) {
                    if (slots[i] == 0) { slots[i] = npid; break; }
                }
                running++;                    // un enfant de plus en cours
                next++;                       // passera à l'argument suivant lors de la prochaine relance
            }
        }
    }

    free(slots);                               // libérer la mémoire
    if (stop) return 1;      // échec
return 0;                // succès
}
