#define _POSIX_C_SOURCE 200809L
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef CAPACITY
#define CAPACITY 4
#endif
_Static_assert(CAPACITY > 0, "capacity must be positive");
#define ITEMS 10000
static pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t not_empty = PTHREAD_COND_INITIALIZER;
static pthread_cond_t not_full = PTHREAD_COND_INITIALIZER;
static int buffer[CAPACITY], in, out, count, closed;
/* Only the consumer writes these; main reads after join. */
static long consumed, sum;
static void check(int e) {
    if (e) { fprintf(stderr, "pthread: %s\n", strerror(e)); exit(EXIT_FAILURE); }
}
static void *producer(void *unused) {
    (void)unused;
    for (int item = 1; item <= ITEMS; item++) {
        check(pthread_mutex_lock(&m));
        while (count == CAPACITY) check(pthread_cond_wait(&not_full, &m));
        buffer[in] = item; in = (in + 1) % CAPACITY; count++;
        check(pthread_cond_signal(&not_empty));
        check(pthread_mutex_unlock(&m));
    }
    check(pthread_mutex_lock(&m)); closed = 1;
    check(pthread_cond_broadcast(&not_empty));
    check(pthread_mutex_unlock(&m));
    return NULL;
}
static void *consumer(void *unused) {
    (void)unused;
    for (;;) {
        check(pthread_mutex_lock(&m));
        while (count == 0 && !closed) check(pthread_cond_wait(&not_empty, &m));
        if (count == 0 && closed) { check(pthread_mutex_unlock(&m)); break; }
        int item = buffer[out]; out = (out + 1) % CAPACITY; count--;
        check(pthread_cond_signal(&not_full));
        check(pthread_mutex_unlock(&m));
        if (item != consumed + 1) { fprintf(stderr, "FIFO violation\n"); exit(EXIT_FAILURE); }
        consumed++; sum += item;
    }
    return NULL;
}
int main(void) {
    pthread_t p, c;
    check(pthread_create(&c, NULL, consumer, NULL));
    check(pthread_create(&p, NULL, producer, NULL));
    check(pthread_join(p, NULL)); check(pthread_join(c, NULL));
    long expected = (long)ITEMS * (ITEMS + 1) / 2;
    printf("capacity=%d consumed=%ld sum=%ld expected=%ld\n", CAPACITY, consumed, sum, expected);
    check(pthread_cond_destroy(&not_empty)); check(pthread_cond_destroy(&not_full));
    check(pthread_mutex_destroy(&m));
    return consumed == ITEMS && sum == expected ? EXIT_SUCCESS : EXIT_FAILURE;
}
