// pcbug.c — los dos errores del productor/consumidor, forzados paso a paso.
//
// Buffer de UN elemento, un productor (Tp) y dos consumidores (Tc1, Tc2).
//
//   ./pcbug if      if + una CV: Tc2 se cuela y Tc1 lee el buffer vacío
//   ./pcbug while   la misma intercalación con while: Tc1 vuelve a revisar
//   ./pcbug onecv   while + una CV: Tc1 despierta a Tc2 en vez de a Tp
//                   y los tres terminan dormidos
//   ./pcbug twocv   while + dos CVs (empty/fill): siempre funciona
//
// Los msleep() fuerzan el orden de llegada al mutex. Un watchdog detecta
// cuando nadie avanza.
#include <string.h>
#include "trace.h"

static int buffer, count = 0;           // buffer de un elemento
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;  // la única CV
static pthread_cond_t empty = PTHREAD_COND_INITIALIZER; // solo en twocv
static pthread_cond_t fill = PTHREAD_COND_INITIALIZER;  // solo en twocv
static int use_if, two_cvs;
static atomic_int bug;

static void put(int v) { buffer = v; count = 1; }

static int get(const char *who) {
    if (count == 0) {                   // en OSTEP: assert(count == 1)
        TRACE("%s: get() con el buffer VACÍO: ¡error!", who);
        bug = 1;
        return -1;
    }
    count = 0;
    return buffer;
}

// Espera mientras (o si) se cumple la condición `cond_expr`.
#define WAIT_WHILE(cond_expr, cv, who, what)                   \
    do {                                                       \
        if (use_if) {                                          \
            if (cond_expr) {                                   \
                TRACE("%s: %s, wait", who, what);              \
                pthread_cond_wait(cv, &mutex);                 \
                TRACE("%s: despierta con el lock", who);       \
            }                                                  \
        } else {                                               \
            while (cond_expr) {                                \
                TRACE("%s: %s, wait", who, what);              \
                pthread_cond_wait(cv, &mutex);                 \
                TRACE("%s: despierta con el lock", who);       \
            }                                                  \
        }                                                      \
    } while (0)

// ---------- Escenario 1 (if / while): Tc2 se cuela ----------
static void *s1_tc1(void *arg) {
    (void)arg;
    pthread_mutex_lock(&mutex);
    WAIT_WHILE(count == 0, &cond, "Tc1", "buffer vacío");
    int v = get("Tc1");
    if (v != -1)
        TRACE("Tc1: consume %d", v);
    pthread_mutex_unlock(&mutex);
    progress_bump();
    return NULL;
}

static void *s1_tc2(void *arg) {
    (void)arg;
    msleep(20);                         // llega mientras Tp tiene el lock
    TRACE("Tc2: pide el lock");
    pthread_mutex_lock(&mutex);
    WAIT_WHILE(count == 0, &cond, "Tc2", "buffer vacío");
    TRACE("Tc2: consume %d (se coló)", get("Tc2"));
    pthread_mutex_unlock(&mutex);
    progress_bump();
    return NULL;
}

static void *s1_tp(void *arg) {
    (void)arg;
    for (int i = 1; i <= 2; i++) {
        msleep(i == 1 ? 10 : 100);
        pthread_mutex_lock(&mutex);
        WAIT_WHILE(count == 1, &cond, "Tp ", "buffer lleno");
        if (i == 1)
            msleep(20);                 // Tc2 se forma en el mutex
        put(i);
        TRACE("Tp : put(%d), signal", i);
        pthread_cond_signal(&cond);
        if (i == 1)
            msleep(20);                 // Tc1 despierta y se forma detrás
        pthread_mutex_unlock(&mutex);
        progress_bump();
    }
    return NULL;
}

// ---------- Escenario 2 (onecv / twocv): todos duermen ----------
// Tp mete 1, 2 y luego un -1 por consumidor. Los consumidores repiten.
static void *s2_consumer(void *arg) {
    const char *who = arg;
    pthread_cond_t *wait_cv = two_cvs ? &fill : &cond;
    pthread_cond_t *sig_cv = two_cvs ? &empty : &cond;
    if (who[2] == '2')
        msleep(5);                      // Tc1 duerme primero, luego Tc2
    for (;;) {
        pthread_mutex_lock(&mutex);
        WAIT_WHILE(count == 0, wait_cv, who, "buffer vacío");
        int v = get(who);
        TRACE("%s: consume %d, signal", who, v);
        pthread_cond_signal(sig_cv);
        pthread_mutex_unlock(&mutex);
        progress_bump();
        if (v == -1)
            return NULL;
    }
}

static void *s2_tp(void *arg) {
    (void)arg;
    int items[] = {1, 2, -1, -1};
    pthread_cond_t *wait_cv = two_cvs ? &empty : &cond;
    pthread_cond_t *sig_cv = two_cvs ? &fill : &cond;
    msleep(20);
    for (int i = 0; i < 4; i++) {
        pthread_mutex_lock(&mutex);
        WAIT_WHILE(count == 1, wait_cv, "Tp ", "buffer lleno");
        put(items[i]);
        TRACE("Tp : put(%d), signal", items[i]);
        pthread_cond_signal(sig_cv);
        pthread_mutex_unlock(&mutex);
        progress_bump();
    }
    return NULL;
}

int main(int argc, char *argv[]) {
    const char *mode = argc > 1 ? argv[1] : "";
    int scenario;
    if (strcmp(mode, "if") == 0) {
        scenario = 1; use_if = 1;
    } else if (strcmp(mode, "while") == 0) {
        scenario = 1;
    } else if (strcmp(mode, "onecv") == 0) {
        scenario = 2;
    } else if (strcmp(mode, "twocv") == 0) {
        scenario = 2; two_cvs = 1;
    } else {
        fprintf(stderr, "uso: %s if|while|onecv|twocv\n", argv[0]);
        return 1;
    }
    trace_start();
    watchdog_start(1, "Tp, Tc1 y Tc2 duermen: nadie los despertará.", NULL);

    pthread_t tp, tc1, tc2;
    if (scenario == 1) {
        pthread_create(&tc1, NULL, s1_tc1, NULL);
        pthread_create(&tc2, NULL, s1_tc2, NULL);
        pthread_create(&tp, NULL, s1_tp, NULL);
    } else {
        pthread_create(&tc1, NULL, s2_consumer, "Tc1");
        pthread_create(&tc2, NULL, s2_consumer, "Tc2");
        pthread_create(&tp, NULL, s2_tp, NULL);
    }
    pthread_join(tc1, NULL);
    pthread_join(tc2, NULL);
    if (scenario == 1 && bug) {         // nadie consumirá el 2: no esperar a Tp
        watchdog_done();
        printf("resultado: ERROR, un consumidor leyó un buffer vacío\n");
        return 0;
    }
    pthread_join(tp, NULL);
    watchdog_done();
    printf("resultado: %s\n", bug ? "ERROR" : "OK");
    return 0;
}
