#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define N_TASKS 10000
#define N_WORKERS 4
#define QSIZE 64

typedef struct {
    int buf[QSIZE];
    int head, tail, count;
    int done;
    pthread_mutex_t mu;
    pthread_cond_t not_empty;
    pthread_cond_t not_full;
} queue_t;

typedef struct {
    long processed;
    int max_seen;
    int initialized;
    char tag[16];
} stats_t;

queue_t q;
stats_t stats;

void queue_init(queue_t *q) {
    q->head = q->tail = q->count = 0;
    q->done = 0;
    pthread_mutex_init(&q->mu, NULL);
    pthread_cond_init(&q->not_empty, NULL);
    pthread_cond_init(&q->not_full, NULL);
}

void push(queue_t *q, int x) {
    pthread_mutex_lock(&q->mu);
    while (q->count == QSIZE) {
        pthread_cond_wait(&q->not_full, &q->mu);
    }
    q->buf[q->tail] = x;
    q->tail = (q->tail + 1) % QSIZE;
    q->count++;
    pthread_cond_signal(&q->not_empty);
    pthread_mutex_unlock(&q->mu);
}

int pop(queue_t *q, int *out) {
    pthread_mutex_lock(&q->mu);
    while (q->count == 0 && !q->done) {
        pthread_cond_wait(&q->not_empty, &q->mu);
    }
    if (q->count == 0 && q->done) {
        pthread_mutex_unlock(&q->mu);
        return 0;
    }
    *out = q->buf[q->head];
    q->head = (q->head + 1) % QSIZE;
    q->count--;
    pthread_cond_signal(&q->not_full);
    pthread_mutex_unlock(&q->mu);
    return 1;
}

void maybe_init_stats() {
    if (!stats.initialized) {   
        stats.processed = 0;
        stats.max_seen = -1;
        snprintf(stats.tag, sizeof(stats.tag), "run");
        stats.initialized = 1;  
    }
}

void* worker(void *arg) {
    int x;
    while (pop(&q, &x)) {
        maybe_init_stats();

        usleep(10);

        stats.processed++;
        if (x > stats.max_seen) {
            stats.max_seen = x;
        }
    }
    return NULL;
}

void* reporter(void *arg) {
    for (int i = 0; i < 50; i++) {
        usleep(2000);
        if (stats.initialized) {
            printf("[report] tag=%s processed=%ld max=%d\n",
                   stats.tag, stats.processed, stats.max_seen);
        }
    }
    return NULL;
}

int main() {
    pthread_t workers[N_WORKERS], rep;

    queue_init(&q);

    for (int i = 0; i < N_WORKERS; i++) {
        pthread_create(&workers[i], NULL, worker, NULL);
    }
    pthread_create(&rep, NULL, reporter, NULL);

    for (int i = 0; i < N_TASKS; i++) {
        push(&q, i);
    }

    pthread_mutex_lock(&q.mu);
    q.done = 1;
    pthread_cond_broadcast(&q.not_empty);
    pthread_mutex_unlock(&q.mu);

    for (int i = 0; i < N_WORKERS; i++) {
        pthread_join(workers[i], NULL);
    }
    pthread_join(rep, NULL);

    printf("FINAL: processed=%ld expected=%d max=%d expected_max=%d\n",
           stats.processed, N_TASKS, stats.max_seen, N_TASKS - 1);
    return 0;
}
