/*
 * Esercizio 3 – Fruit Picker 2 (shared memory + semafori + thread)
 *
 * - Processo padre: genera TOTAL_FRUIT frutti casuali in shared memory,
 *   uno alla volta, attendendo che vengano raccolti prima di generarne
 *   un altro.
 * - Processo figlio: crea N_THREADS thread, ognuno prova a raccogliere.
 *   Solo UN thread alla volta raccoglie (garantito dal semaforo).
 *   Ogni thread tiene traccia di quanti frutti ha raccolto.
 *
 * Compilazione:  gcc -Wall -pthread -o es3_fruitpicker es3_fruitpicker.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/mman.h>
#include <sys/wait.h>

#define N_THREADS   4
#define TOTAL_FRUIT 10


typedef struct {
    char  fruit[32];
    int   done;           /* 1 = nessun altro frutto verra' generato */
    sem_t sem_ready;      /* padre posta (1) -> un thread si sveglia  */
    sem_t sem_picked;     /* thread posta (1) -> padre genera il prossimo */
} SharedMem;

static const char *FRUITS[] = {
    "mela", "pera", "banana", "arancia", "uva", "kiwi", "fragola"
};
#define N_TYPES (int)(sizeof(FRUITS) / sizeof(FRUITS[0]))


typedef struct {
    SharedMem *shm;
    int        id;
    int        count;   /* frutti raccolti da questo thread */
} ThreadArg;


static void *picker(void *arg)
{
    ThreadArg *a = arg;

    while (1) {
        sem_wait(&a->shm->sem_ready);   /* attende il prossimo frutto */

        if (a->shm->done) {
            /* Nessun altro frutto: sveglia il prossimo thread in attesa */
            sem_post(&a->shm->sem_ready);
            break;
        }

        /* Sezione critica: un solo thread alla volta arriva qui
         * perché sem_ready è postato una sola volta per ogni frutto. */
        printf("[thread %d] raccolto: %s\n", a->id, a->shm->fruit);
        fflush(stdout);
        a->count++;

        sem_post(&a->shm->sem_picked);  /* notifica il padre */
    }
    return NULL;
}


static void run_child(SharedMem *shm)
{
    ThreadArg  args[N_THREADS];
    pthread_t  tids[N_THREADS];

    for (int i = 0; i < N_THREADS; i++) {
        args[i] = (ThreadArg){ shm, i, 0 };
        if (pthread_create(&tids[i], NULL, picker, &args[i]) != 0) {
            perror("pthread_create"); exit(EXIT_FAILURE);
        }
    }

    for (int i = 0; i < N_THREADS; i++) {
        pthread_join(tids[i], NULL);
        printf("[figlio]  thread %-2d → %d frutti raccolti\n",
               i, args[i].count);
    }
    exit(EXIT_SUCCESS);
}


int main(void)
{
    /* Alloca shared memory accessibile a padre E figlio */
    SharedMem *shm = mmap(NULL, sizeof(SharedMem),
                          PROT_READ | PROT_WRITE,
                          MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (shm == MAP_FAILED) { perror("mmap"); exit(EXIT_FAILURE); }

    /* pshared = 1 ->i semafori sono condivisi tra processi */
    sem_init(&shm->sem_ready,  1, 0);
    sem_init(&shm->sem_picked, 1, 0);
    shm->done = 0;

    srand((unsigned)time(NULL));

    pid_t pid = fork();
    if (pid == -1) { perror("fork"); exit(EXIT_FAILURE); }

    if (pid == 0) {
        run_child(shm);   /* non ritorna */
    }

    for (int i = 0; i < TOTAL_FRUIT; i++) {
        strcpy(shm->fruit, FRUITS[rand() % N_TYPES]);
        printf("[padre]   generato: %-10s  (%d/%d)\n",
               shm->fruit, i + 1, TOTAL_FRUIT);
        fflush(stdout);

        sem_post(&shm->sem_ready);   /* segnala a UN thread */
        sem_wait(&shm->sem_picked);  /* aspetta che venga raccolto */
    }

    /* Segnala la fine a tutti i thread */
    shm->done = 1;
    for (int i = 0; i < N_THREADS; i++)
        sem_post(&shm->sem_ready);

    wait(NULL);

    sem_destroy(&shm->sem_ready);
    sem_destroy(&shm->sem_picked);
    munmap(shm, sizeof(SharedMem));
    return 0;
}
