/*
 * Copyright (c) 2002, Intel Corporation. All rights reserved.
 * Created by:  crystal.xiong REMOVE-THIS AT intel DOT com
 * This file is licensed under the GPL license.  For the full content
 * of this license, see the COPYING file at the top level of this
 * source tree.
 *
 * This is a test about multiple producers and consumers. Producers send data
 * to a buffer. Consumers keeps reading data from the buffer.
 */

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <string.h>
#include <getopt.h>
#include <errno.h>
#include <pthread.h>

#include <semaphore.h>
#include "posixtest.h"

#define BUF_SIZE	5
#define Max_Num		5
#define Max_Threads	127

typedef struct {
	int buffer[BUF_SIZE];
	sem_t full;
	sem_t empty;
	sem_t lock;
} buf_t;

buf_t *buf;
int in, out;

int *producer(void *ID)
{
	int data;
	int i;
	int ThreadID = *(int *)ID;
	int full_value;

	printf("Enter into Producer Thread %d... \n", ThreadID);
	for (i = 0; i < Max_Num - 1; i++) {
		if (-1 == sem_wait(&buf->full)) {
			perror("sem_wait didn't return success \n");
			pthread_exit((void *)1);
		}
		if (-1 == sem_getvalue(&buf->full, &full_value)) {
			perror("sem_getvalue didn't return success \n");
			pthread_exit((void *)1);
		}
		printf("The value of the full semaphore is %d \n", full_value);
		if (-1 == sem_wait(&buf->lock)) {
			perror("sem_wait didn't return success \n");
			pthread_exit((void *)1);
		}
		data = 100 * ThreadID + i;
		buf->buffer[in] = data;
		printf("[%d] producer has added %d to the buffer[%d] \n",
		       ThreadID, data, in);
		in = (in + 1) % BUF_SIZE;
		if (-1 == sem_post(&buf->lock)) {
			perror("sem_wait didn't return success \n");
			pthread_exit((void *)1);
		}
		if (-1 == sem_post(&buf->empty)) {
			perror("sem_wait didn't return success \n");
			pthread_exit((void *)1);
		}
	}
	if (-1 == sem_wait(&buf->full)) {
		perror("sem_wait didn't return success \n");
		pthread_exit((void *)1);
	}
	if (-1 == sem_getvalue(&buf->full, &full_value)) {
		perror("sem_getvalue didn't return success \n");
		pthread_exit((void *)1);
	}
	printf("The value of the full is %d \n", full_value);
	if (-1 == sem_wait(&buf->lock)) {
		perror("sem_wait didn't return success \n");
		pthread_exit((void *)1);
	}
	data = -1;
	buf->buffer[in] = data;
	printf("[%d] producer has added %d to the buffer[%d] \n", ThreadID,
	       data, in);
	in = (in + 1) % BUF_SIZE;
	if (-1 == sem_post(&buf->lock)) {
		perror("sem_wait didn't return success \n");
		pthread_exit((void *)1);
	}
	if (-1 == sem_post(&buf->empty)) {
		perror("sem_wait didn't return success \n");
		pthread_exit((void *)1);
	}
	printf("Producer %d exit... \n", ThreadID);
	pthread_exit((void *)0);
}

int *consumer(void *ID)
{
	int data;
	int ThreadID = *(int *)ID;
	int full_value;

	printf("Enter into Consumer Thread %d... \n", ThreadID);
	do {
		if (-1 == sem_wait(&buf->empty)) {
			perror("sem_wait didn't return success \n");
			pthread_exit((void *)1);
		}
		if (-1 == sem_wait(&buf->lock)) {
			perror("sem_wait didn't return success \n");
			pthread_exit((void *)1);
		}
		data = buf->buffer[out];
		printf("[%d] consumer has taken %d from buffer[%d] \n",
		       ThreadID, data, out);
		out = (out + 1) % BUF_SIZE;
		if (-1 == sem_post(&buf->lock)) {
			perror("sem_wait didn't return success \n");
			pthread_exit((void *)1);
		}
		if (-1 == sem_post(&buf->full)) {
			perror("sem_wait didn't return success \n");
			pthread_exit((void *)1);
		}
		if (-1 == sem_getvalue(&buf->full, &full_value)) {
			perror("sem_getvalue didn't return success \n");
			pthread_exit((void *)1);
		}
		printf("The value of the full semaphore is %d \n", full_value);
	}
	while (data != -1);

	printf("Consumer %d exit... \n", ThreadID);
	pthread_exit((void *)0);
}

int main(int argc, char *argv[])
{
	int shared = 1;
	int full_value = BUF_SIZE;
	int empty_value = 0;
	int lock_value = 1;
	int num;
	int i;
	pthread_t con[Max_Threads], pro[Max_Threads];
	int ThreadID[Max_Threads];

#ifndef	_POSIX_SEMAPHORES
	printf("_POSIX_SEMAPHORES is not defined \n");
	return PTS_UNRESOLVED;
#endif

	buf = (buf_t *) malloc(sizeof(buf_t));

	if ((2 != argc) || ((num = atoi(argv[1])) <= 0)) {
		fprintf(stderr, "Usage: %s number_of_threads\n", argv[0]);
		return PTS_FAIL;
	}
	if (num > Max_Threads) {
		printf
		    ("The num of producers/consumers threads are too large.  Reset to %d\n",
		     Max_Threads);
		num = Max_Threads;
	}

	if (-1 == sem_init(&buf->full, shared, full_value)) {
		perror("sem_init didn't return success \n");
		return PTS_UNRESOLVED;
	}
	if (-1 == sem_getvalue(&buf->full, &full_value)) {
		perror("sem_getvalue didn't return success \n");
		return PTS_UNRESOLVED;
	}
	printf("The initial value of the full semaphore is %d \n", full_value);
	if (-1 == sem_init(&buf->empty, shared, empty_value)) {
		perror("sem_init didn't return success \n");
		return PTS_UNRESOLVED;
	}
	if (-1 == sem_init(&buf->lock, shared, lock_value)) {
		perror("sem_init didn't return success \n");
		return PTS_UNRESOLVED;
	}
	in = out = 0;

	for (i = 0; i < num; i++) {
		ThreadID[i] = i;
		pthread_create(&con[i], NULL, (void *)consumer,
			       (void *)&ThreadID[i]);
	}
	for (i = 0; i < num; i++) {
		ThreadID[i] = i;
		pthread_create(&pro[i], NULL, (void *)producer,
			       (void *)&ThreadID[i]);
	}
	for (i = 0; i < num; i++)
		pthread_join(con[i], NULL);
	for (i = 0; i < num; i++)
		pthread_join(pro[i], NULL);

	sem_destroy(&buf->full);
	sem_destroy(&buf->empty);
	sem_destroy(&buf->lock);
	return PTS_PASS;
}
