#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>

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

/* number of items max. in a pipe */
#define BUFFER_SIZE 10

/* our named pipe will be created in tmp - write permissions are expected */
#define NAMED_PIPE "/tmp/fifo"

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

/* 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... Goodbye -- \n\n");
	exit(0);
}

void runProducer()
{
	int i;
	int pipeHandler;
	char buffer[2];

	printf("Producer started\n");

	if ( (pipeHandler=open(NAMED_PIPE, O_WRONLY)) < 0 )
	{
		fprintf(stderr, "Producer could not connect to pipe [%s] [%d]", NAMED_PIPE, errno);
		shutdown(SIGINT);
	}

	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 */
		buffer[0] = (char)i;
		buffer[1] = '\0';
		write(pipeHandler, buffer, sizeof(buffer));
		printf("Producer produced: [%s]\n", buffer);

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

	close(pipeHandler);

	printf("Producer finished\n");
}

void runConsumer()
{
	int i;
	char buffer[2];
	int pipeHandler;

	printf("Consumer started\n");
	if ( (pipeHandler=open(NAMED_PIPE, O_RDONLY)) < 0 )
	{
		fprintf(stderr, "Consumer could not connect to pipe [%s] [%d]", NAMED_PIPE, errno);
		shutdown(SIGINT);
 	}

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

		/* critical region */
		read(pipeHandler, &buffer, sizeof(buffer));
		printf("Consumer got: [%s]\n", buffer);

		/* to make this more fun: sleep 1sec... */
		sleep(1);

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

	close(pipeHandler);

	printf("Consumer finished\n");
}

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

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

	unlink(NAMED_PIPE);
	if ( mkfifo(NAMED_PIPE, O_RDWR | S_IWUSR | S_IRUSR) < 0 )
	{
		fprintf(stderr, "Could not set up named pipe");
		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 )
	{
		printf("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;
}

