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

#define BUFFER_SIZE 10
#define NUM_ITEMS 20     // items to be produced/consumed

int buffer[BUFFER_SIZE];
int in = 0;
int out = 0;

sem_t empty;  // empty slots semaphore
sem_t full;   // available items semaphore

pthread_mutex_t mutex;  // mutex for shared vars

void* producer(void* arg) {
    int item;
    for (int i = 0; i < NUM_ITEMS; i++) {
        item = i;  // item is set to index

        // waiting for empty slot
        sem_wait(&empty);

        // critical section
        pthread_mutex_lock(&mutex);

        // item placement into buffer
        buffer[in] = item;
        printf("Produced: %d at index %d\n", item, in);
        in = (in + 1) % BUFFER_SIZE;

        // critical section exit
        pthread_mutex_unlock(&mutex);

        // empty slot available
        sem_post(&full);

        // production time sim using sleep
        sleep(1);
    }
    pthread_exit(NULL);
}

void* consumer(void* arg) {
    int item;
    for (int i = 0; i < NUM_ITEMS; i++) {
        // waiting for at least one item in the buffer
        sem_wait(&full);

        // critical section
        pthread_mutex_lock(&mutex);

        // item removal from buffer
        item = buffer[out];
        printf("Consumed: %d from index %d\n", item, out);
        out = (out + 1) % BUFFER_SIZE;

        // critical section exit
        pthread_mutex_unlock(&mutex);

        // empty slot
        sem_post(&empty);

        // consumption time sim using sleep
        sleep(2);
    }
    pthread_exit(NULL);
}

int main(void) {
    pthread_t producer_tid, consumer_tid;

    // semaphore init
    sem_init(&empty, 0, BUFFER_SIZE);  // empty slots init
    sem_init(&full, 0, 0);             // available items init

    // mutex init
    pthread_mutex_init(&mutex, NULL);

    // thread creator
    if (pthread_create(&producer_tid, NULL, producer, NULL) != 0) {
        perror("Failed to create producer thread");
        exit(EXIT_FAILURE);
    }
    if (pthread_create(&consumer_tid, NULL, consumer, NULL) != 0) {
        perror("Failed to create consumer thread");
        exit(EXIT_FAILURE);
    }

    // thread completion wait
    pthread_join(producer_tid, NULL);
    pthread_join(consumer_tid, NULL);

    // cleanup of semaphores and mutex
    sem_destroy(&empty);
    sem_destroy(&full);
    pthread_mutex_destroy(&mutex);

    return 0;
}