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

#define POISON -1

typedef struct {
    int *buf;
    int n;
    int outpos;
    int inpos;
    sem_t mutex;
    sem_t slots;
    sem_t items;
} sbuf_t;

long long produced_sum = 0;
long long consumed_sum = 0;
sem_t sum_mutex;
int total_items;

void sbuf_init(sbuf_t *sp, int n)
{
    sp->buf = (int *)malloc(n * sizeof(int));
    sp->n = n;
    sp->outpos = 0;
    sp->inpos = 0;
    sem_init(&sp->mutex, 0, 1);
    sem_init(&sp->slots, 0, n);
    sem_init(&sp->items, 0, 0);
}

void sbuf_deinit(sbuf_t *sp)
{
    free(sp->buf);
    sem_destroy(&sp->mutex);
    sem_destroy(&sp->slots);
    sem_destroy(&sp->items);
}

void sbuf_insert(sbuf_t *sp, int item)
{
    sem_wait(&sp->slots);
    sem_wait(&sp->mutex);
    sp->buf[sp->inpos] = item;
    sp->inpos = (sp->inpos + 1) % sp->n;
    sem_post(&sp->mutex);
    sem_post(&sp->items);
}

int sbuf_remove(sbuf_t *sp)
{
    sem_wait(&sp->items);
    sem_wait(&sp->mutex);
    int item = sp->buf[sp->outpos];
    sp->outpos = (sp->outpos + 1) % sp->n;
    sem_post(&sp->mutex);
    sem_post(&sp->slots);
    return item;
}

typedef struct {
    sbuf_t *sp;
    int num_items;
    int id;
} producer_arg_t;

typedef struct {
    sbuf_t *sp;
    int id;
} consumer_arg_t;

void *producer(void *arg)
{
    producer_arg_t *pa = (producer_arg_t *)arg;
    for (int i = 0; i < pa->num_items; i++) {
        int val = rand() % 1000;
        sbuf_insert(pa->sp, val);
        sem_wait(&sum_mutex);
        produced_sum += val;
        sem_post(&sum_mutex);
        printf("[Producer %d] produced %d\n", pa->id, val);
    }
    return NULL;
}

void *consumer(void *arg)
{
    consumer_arg_t *ca = (consumer_arg_t *)arg;
    while (1) {
        int item = sbuf_remove(ca->sp);
        if (item == POISON) {
            sbuf_insert(ca->sp, POISON);
            break;
        }
        sem_wait(&sum_mutex);
        consumed_sum += item;
        sem_post(&sum_mutex);
        printf("[Consumer %d] consumed %d\n", ca->id, item);
    }
    return NULL;
}

int main(int argc, char *argv[])
{
    if (argc != 5) {
        fprintf(stderr, "Usage: %s <k_producers> <items_per_producer> <m_consumers> <N_buffer>\n",
                argv[0]);
        return 1;
    }

    int k = atoi(argv[1]);
    int items_per_producer = atoi(argv[2]);
    int m = atoi(argv[3]);
    int N = atoi(argv[4]);
    total_items = k * items_per_producer;

    srand(time(NULL));
    sem_init(&sum_mutex, 0, 1);

    sbuf_t buf;
    sbuf_init(&buf, N);

    pthread_t *producers = malloc(k * sizeof(pthread_t));
    pthread_t *consumers = malloc(m * sizeof(pthread_t));
    producer_arg_t *pargs = malloc(k * sizeof(producer_arg_t));
    consumer_arg_t *cargs = malloc(m * sizeof(consumer_arg_t));

    for (int i = 0; i < k; i++) {
        pargs[i].sp = &buf;
        pargs[i].num_items = items_per_producer;
        pargs[i].id = i + 1;
        pthread_create(&producers[i], NULL, producer, &pargs[i]);
    }

    for (int i = 0; i < m; i++) {
        cargs[i].sp = &buf;
        cargs[i].id = i + 1;
        pthread_create(&consumers[i], NULL, consumer, &cargs[i]);
    }

    for (int i = 0; i < k; i++) {
        pthread_join(producers[i], NULL);
    }

    sbuf_insert(&buf, POISON);

    for (int i = 0; i < m; i++) {
        pthread_join(consumers[i], NULL);
    }

    printf("\n=== Verification ===\n");
    printf("Produced sum : %lld\n", produced_sum);
    printf("Consumed sum : %lld\n", consumed_sum);
    if (produced_sum == consumed_sum) {
        printf("SUCCESS: Sums match! Program is correct.\n");
    } else {
        printf("ERROR: Sum mismatch! Difference = %lld\n",
               produced_sum - consumed_sum);
    }

    free(producers);
    free(consumers);
    free(pargs);
    free(cargs);
    sbuf_deinit(&buf);
    sem_destroy(&sum_mutex);

    return 0;
}
