#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/shm.h>

#include "resource.h"
#include "semaphores.h"

/* we use 26 items to produce to send a-z */
#define PRODUCTION_START 97
#define PRODUCTION_COUNT 26

#define BUFFER_SIZE 10

int readPosition;
int writePosition;
char *sharedResource;

/* our global shared resources object - defined in resource.h */
SharedResource resource;

/* we have to free mem etc... */
void shutdown(int signal)
{
	/* destroy our semaphores */
	deleteSemaphore(resource.mutex);
	deleteSemaphore(resource.fullSlots);
	deleteSemaphore(resource.emptySlots);

	printf("Safely exiting...\n Goodbye -- \n\n");
	exit(0);
}

void runProducer()
{
	int i;

	printf("Producer started\n");

	writePosition = 0;
	for ( i=PRODUCTION_START; i<PRODUCTION_START+PRODUCTION_COUNT; i++ )
	{
		/* preserve a slot or wait if none are available */
		semaphoreDown(resource.emptySlots);
		/* mutex */
		semaphoreDown(resource.mutex);

		/* critical region */
		sharedResource[writePosition] = i;
		printf("Producer produced: [%c]\n", sharedResource[writePosition]);

		writePosition++;
		if ( writePosition >= BUFFER_SIZE )
		{
			writePosition = 0;
		}

		/* release mutex */
		semaphoreUp(resource.mutex);
		/* increase produced items */
		semaphoreUp(resource.fullSlots);
	}

	printf("Producer finished\n");
}

void runConsumer()
{
	int i;

	printf("Consumer started\n");

	readPosition = 0;
	for ( i=0; i<26; i++ )
	{
		/* preserve a slot to consume or wait if none are produced */
		semaphoreDown(resource.fullSlots);
		/* mutex */
		semaphoreDown(resource.mutex);

		/* critical region */
		printf("Consumer got: [%c]\n", sharedResource[readPosition]);
		readPosition++;
		if ( readPosition >= BUFFER_SIZE )
		{
			readPosition = 0;
		}

		/* here some more fun too -> make us slower... */
		sleep(1);

		/* release mutex */
		semaphoreUp(resource.mutex);
		/* release one empty slot */
		semaphoreUp(resource.emptySlots);
	}

	printf("Consumer finished\n");
}

int main(int argc, char **argv)
{
	int childExitStatus;
	pid_t pid;
	int shmId;

	/* register shutdown handlers */
	signal(SIGINT,  &shutdown);
	signal(SIGTERM, &shutdown);


	shmId = shmget(IPC_PRIVATE, BUFFER_SIZE, IPC_CREAT | IPC_EXCL | 0666);
	if ( shmId < 0 )
	{
		fprintf(stderr, "could not preserve shared memory");
		exit(1);
	}

	sharedResource = (char *)shmat(shmId, NULL, 0);
	if ( sharedResource == (char *)-1 )
	{
		fprintf(stderr, "shmat failed [%d]", errno);
		exit(1);
	}

	/* init our shared resource semaphores */
	/* we use IPC_PRIVATE as key because there is no need to externally access one of those semaphores */
	resource.mutex = createSemaphore();
	if ( resource.mutex < 0 )
	{
		fprintf(stderr, "Could not create mutex semaphore\n");
		shutdown(SIGTERM);
	}

	resource.fullSlots = createSemaphore();
	if ( resource.fullSlots < 0 )
	{
		fprintf(stderr, "Could not create fullSlots semaphore\n");
		shutdown(SIGTERM);
	}

	resource.emptySlots = createSemaphore();
	if ( resource.emptySlots < 0 )
	{
		fprintf(stderr, "Could not create emptySlots semaphore\n");
		shutdown(SIGTERM);
	}

	/* init the semaphores */

	/* mutex has to be available - otherwise we will run into a deadlock without doing anything ;-) */
	if ( initSemaphore(resource.mutex, 1) == -1 )
	{
		fprintf(stderr, "Could not initialize mutex semaphore\n");
		shutdown(SIGTERM);
	}

	/* no produced items for now */
	if ( initSemaphore(resource.fullSlots, 0) == -1 )
	{
		fprintf(stderr, "Could not initialize fullSlots semaphore\n");
		shutdown(SIGTERM);
	}

	/* but BUFFER_SIZE empty slots.. */
	if ( initSemaphore(resource.emptySlots, BUFFER_SIZE) == -1 )
	{
		fprintf(stderr, "Could not initialize emptySlots semaphore\n");
		shutdown(SIGTERM);
	}

	/* fork our process -> and start consumer and producer - order does not matter */
	pid = fork();
	/*
		if we are in the parent thread pid will hold the pid of the child
		while being in the child process 0 is returned...
	*/
	if ( pid < 0 )
	{
		fprintf(stderr, "Could not fork\n");
		shutdown(SIGTERM);
	}
	else if ( pid == 0 ) /* child */
	{
		runConsumer();
	}
	else if ( pid > 0 )
	{
		runProducer();

		/* wait for termination of our baby */
		waitpid(-1, &childExitStatus, 0);

		/* shutdown our app */
		shutdown(SIGTERM);
	}

	return 0;
}

