// pc.c — productor/consumidor (bounded buffer) con dos variables de condición.
//
// Uso: ./pc MAX P C N
//   MAX  tamaño del buffer          P  productores
//   C    consumidores               N  elementos por productor
// Cada productor mete N números; los consumidores los sacan hasta recibir
// un -1 (fin). Al final se comprueba que la suma de lo consumido sea la
// de lo producido.
#include "trace.h"

static int *buffer;
static int max;
static int fill_ptr = 0, use_ptr = 0, count = 0;

static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t empty = PTHREAD_COND_INITIALIZER; // hay espacio
static pthread_cond_t fill = PTHREAD_COND_INITIALIZER;  // hay datos

static void put(int value) {
    buffer[fill_ptr] = value;
    fill_ptr = (fill_ptr + 1) % max;
    count++;
}

static int get(void) {
    int tmp = buffer[use_ptr];
    use_ptr = (use_ptr + 1) % max;
    count--;
    return tmp;
}

static void produce(int value) {
    pthread_mutex_lock(&mutex);
    while (count == max)                // lleno: esperar espacio
        pthread_cond_wait(&empty, &mutex);
    put(value);
    pthread_cond_signal(&fill);         // despierta a un consumidor
    pthread_mutex_unlock(&mutex);
}

static int consume(void) {
    pthread_mutex_lock(&mutex);
    while (count == 0)                  // vacío: esperar datos
        pthread_cond_wait(&fill, &mutex);
    int tmp = get();
    pthread_cond_signal(&empty);        // despierta a un productor
    pthread_mutex_unlock(&mutex);
    return tmp;
}

static int nitems;

static void *producer(void *arg) {
    long id = (long)arg;
    for (int i = 0; i < nitems; i++)
        produce((int)(id * nitems + i) % 1000);
    return NULL;
}

static void *consumer(void *arg) {
    long *sum = arg;
    for (;;) {
        int v = consume();
        if (v == -1)
            break;
        *sum += v;
    }
    return NULL;
}

int main(int argc, char *argv[]) {
    if (argc != 5) {
        fprintf(stderr, "uso: %s MAX P C N\n", argv[0]);
        return 1;
    }
    max = atoi(argv[1]);
    int np = atoi(argv[2]), nc = atoi(argv[3]);
    nitems = atoi(argv[4]);
    buffer = malloc(sizeof(int) * max);

    pthread_t prod[np], cons[nc];
    long sums[nc];
    for (long i = 0; i < np; i++)
        pthread_create(&prod[i], NULL, producer, (void *)i);
    for (int i = 0; i < nc; i++) {
        sums[i] = 0;
        pthread_create(&cons[i], NULL, consumer, &sums[i]);
    }
    for (int i = 0; i < np; i++)
        pthread_join(prod[i], NULL);
    for (int i = 0; i < nc; i++)
        produce(-1);                    // un fin por consumidor

    long expected = 0, got = 0;
    for (long id = 0; id < np; id++)
        for (int i = 0; i < nitems; i++)
            expected += (id * nitems + i) % 1000;
    for (int i = 0; i < nc; i++) {
        pthread_join(cons[i], NULL);
        printf("consumidor %d: suma %ld\n", i, sums[i]);
        got += sums[i];
    }
    printf("esperado %ld, obtenido %ld: %s\n", expected, got,
           expected == got ? "OK" : "ERROR");
    free(buffer);
    return expected == got ? 0 : 1;
}
