// sempc.c — productor/consumidor con semáforos POSIX.
//
// Uso: ./sempc ok        mutex DENTRO de empty/full: funciona
//      ./sempc outside   mutex AFUERA: el consumidor duerme con el mutex
//                        tomado y el productor no puede entrar (deadlock)
// Buffer de MAX elementos, 2 productores y 2 consumidores.
#include <semaphore.h>
#include <string.h>
#include "trace.h"

#define MAX   4
#define ITEMS 50000                     // por productor
#define NP    2
#define NC    2

static int buffer[MAX];
static int fill_ptr = 0, use_ptr = 0;
static sem_t empty, full, mutex;
static int outside;

static void put(int v) { buffer[fill_ptr] = v; fill_ptr = (fill_ptr + 1) % MAX; }
static int get(void) { int v = buffer[use_ptr]; use_ptr = (use_ptr + 1) % MAX; return v; }

static void produce(int v) {
    if (outside) {
        sem_wait(&mutex);               // MAL: el mutex primero
        sem_wait(&empty);
    } else {
        sem_wait(&empty);               // esperar un espacio libre
        sem_wait(&mutex);               // luego, exclusión mutua
    }
    put(v);
    sem_post(&mutex);
    sem_post(&full);                    // un dato más
}

static int consume(const char *who) {
    if (outside) {
        sem_wait(&mutex);
        TRACE("%s: tiene el mutex, sem_wait(&full)", who);
        sem_wait(&full);                // duerme CON el mutex
    } else {
        sem_wait(&full);
        sem_wait(&mutex);
    }
    int v = get();
    sem_post(&mutex);
    sem_post(&empty);
    return v;
}

static void *producer(void *arg) {
    long id = (long)arg;
    if (outside) {
        msleep(50);                     // el consumidor llega primero
        TRACE("P%ld: sem_wait(&mutex)", id);
    }
    for (int i = 0; i < ITEMS; i++) {
        produce(1);
        progress_bump();
    }
    return NULL;
}

static long sums[NC];

static void *consumer(void *arg) {
    long id = (long)arg;
    char who[8];
    snprintf(who, sizeof who, "C%ld", id);
    for (;;) {
        int v = consume(who);
        if (v == -1)
            return NULL;
        sums[id] += v;
        progress_bump();
    }
}

int main(int argc, char *argv[]) {
    const char *mode = argc > 1 ? argv[1] : "";
    if (strcmp(mode, "ok") && strcmp(mode, "outside")) {
        fprintf(stderr, "uso: %s ok|outside\n", argv[0]);
        return 1;
    }
    outside = strcmp(mode, "outside") == 0;
    sem_init(&empty, 0, MAX);           // MAX espacios libres
    sem_init(&full, 0, 0);              // 0 datos
    sem_init(&mutex, 0, 1);             // binario: un lock
    trace_start();
    watchdog_start(1, "Deadlock: C duerme en full con el mutex.", NULL);

    pthread_t p[NP], c[NC];
    for (long i = 0; i < NC; i++)
        pthread_create(&c[i], NULL, consumer, (void *)i);
    for (long i = 0; i < NP; i++)
        pthread_create(&p[i], NULL, producer, (void *)i);
    for (int i = 0; i < NP; i++)
        pthread_join(p[i], NULL);
    for (int i = 0; i < NC; i++)
        produce(-1);                    // un fin por consumidor
    long total = 0;
    for (int i = 0; i < NC; i++) {
        pthread_join(c[i], NULL);
        total += sums[i];
    }
    watchdog_done();
    printf("consumidos %ld de %d: %s\n", total, NP * ITEMS,
           total == NP * ITEMS ? "OK" : "ERROR");
    return 0;
}
