/*
File name: buffer.c
Compiler: Microsoft Visual Studio 2015
Course: Compilers CST-8152
Lab Section: Lab 011
Assignment 1: The Buffer
Date: September 21st 2017
Professor: Svillen Ranev
Purpose: Used to actually define all the functions that are stored in buffer header buffer.h
Function list: b_allocate(), b_addc(), b_clear(), b_free(), b_isfull(), b_limit(), b_capacity(), b_mark(), b_mode(), b_incfactor(), b_load(), b_isempty(), b_eob(), b_getc(), b_print(), b_compact(), b_rflag(), b_retract(), b_reset(), b_getcoffset(), b_rewind(), b_location()
*/

#include "buffer.h"

/*
Purpose: Allocate heap memory for the buffer structure and the char aray
History/Versions: 1.0
Called functions: calloc(), malloc(), free()
Parameters: The initial capacity for the buffer, increment factor used to increase the buffer, The mode to run the buffer. 
Return value: A pointer to the Buffer structure which is created , Ranges and Values and Mesurments of the Buffer
Algorithm: Check if valid, get memory for the buffer structure , char array, set mode, inc_factor and capacity then return a pointer to the Buffer.
*/
Buffer * b_allocate(short init_cap, char inc_factor, char o_mode)
{
	Buffer * bufferP = NULL; /*Pointer to the buffer structure*/

	if (init_cap < 0)
		return NULL;

	else if (!(o_mode == FIXED_IDENTIFIER || o_mode == ADDITIVE_IDENTIFIER || o_mode == MULT_IDENTIFIER))
		return NULL;

	/*Allocates memory for the buffer*/
	bufferP = (Buffer *)calloc(1, sizeof(Buffer));
	if (bufferP == NULL)
		return NULL;

	/* Allocate memory for the character array*/
	bufferP->cb_head = (char *)malloc((size_t)init_cap);
	if (bufferP->cb_head == NULL)
	{
		free(bufferP);
		bufferP = NULL;

		return NULL;
	}

	/*Fixed mode for the buffer vufferP*/
	if ((o_mode == FIXED_IDENTIFIER || inc_factor == 0) && init_cap > 0)
	{
		bufferP->inc_factor = 0;
		bufferP->mode = FIXED_VALUE;
	}

	/* Additive mode for the buffer bufferP*/
	else if (o_mode == ADDITIVE_IDENTIFIER)
	{
		bufferP->inc_factor = inc_factor;
		bufferP->mode = ADDITIVE_VALUE;
	}

	/* Multiplicative mode for the buffer bufferP*/
	else if (o_mode == MULT_IDENTIFIER && (inc_factor >= MIN_INC_FACTOR && inc_factor <= MAX_INC_FACTOR))
	{
		bufferP->inc_factor = inc_factor;
		bufferP->mode = MULT_VALUE;
	}
	else
	{
		return NULL;
	}

	/*Sets capacity for bufferP*/
	bufferP->capacity = init_cap;

	return bufferP;
}

/*
Purpose: Add a character to the buffer, increase it if required
History/Versions: 1.0
Called functions: b_isfull(), realloc(), printf()
Parameters: A pointer to the buffer structure, The char to add to the buffer
Return value: A pointer to the Buffer
Algorithm: Check if valid, check if the buffer is full and increase it and add char to it if possible
*/
pBuffer b_addc(pBuffer const pBD, char symbol)
{

	if (pBD == NULL)
		return NULL;

	pBD->r_flag = UNSET_R_FLAG; /*Unset the r_flag*/

								/*Increase the buffer if it is full*/
	if (b_isfull(pBD))
	{
		short	space = 0; /*Free space for the buffer to be expanded into*/
		short	inc = 0; /*Amount to add to the existing capacity*/
		float	incPercent = (float)pBD->inc_factor / 100; /*The increment factor as a decimal percentage*/
		int		newCapacity = 0; /*The new capacity to realloc with, an integer so that we can compare to the buffer max size (short max)*/
		char	* newBuffer = NULL; /*A pointer to the reallocated buffer*/

									/*If buffer is full and capacity is reached exit */
		if (pBD->capacity == MAX_BUFFER)
			return NULL;

#ifdef DEBUGBUFF
		printf("Buffer is full, Increasing buffer\n");
#endif

		/*Depending on the mode selected capacity is increased*/
		switch (pBD->mode)
		{
			/*if mode is fixed you can't expand*/
		case FIXED_VALUE:
			return NULL;
			break;

			/*if mode is additive you add inc_factor to the capacity*/
		case ADDITIVE_VALUE:
			newCapacity = pBD->capacity + (unsigned char)pBD->inc_factor;

			/*If there isn't enough free space then rturn NULL since it can't be increased*/
			if (newCapacity > MAX_BUFFER || newCapacity < 0)
				return NULL;

#ifdef DEBUGBUFF
			printf("Current Capacity: %d, New Capacity: %d\n", pBD->capacity, newCapacity);
#endif

			break;

			/*if mode is multiplicative add percent of inc_factor */
		case MULT_VALUE:
			/*get free space*/
			space = MAX_BUFFER - pBD->capacity;
			inc = (short)(space * incPercent); /*Get the value to add to the capacity*/

			if (inc == 0)
				inc = space;

			newCapacity = (pBD->capacity + inc) * sizeof(char); /*get the new capacity*/

#ifdef DEBUGBUFF
			printf("Percent inc factor: %f\n", (float)(pBD->inc_factor) / 100);

			printf("Available space: %d, New Increment: %d, The Inc Factor: %d, The Inc Factor percentage: %f\n", space, inc, pBD->inc_factor, incPercent);

			printf("Current Capacity: %d, New Capacity: %d\n", pBD->capacity, newCapacity);
#endif

			break;
		}

		/*Reallocate the buffer with the new capacity*/
		newBuffer = (char *)realloc(pBD->cb_head, (size_t)newCapacity);
		if (newBuffer == NULL)
			return NULL;

#ifdef DEBUGBUFF
		printf("realloca buffer: %d, cb_head address: %d\n", newBuffer, pBD->cb_head);
#endif

		if (newBuffer != pBD->cb_head)
		{
			pBD->r_flag = SET_R_FLAG;

#ifdef DEBUGBUFF
			printf("r_flag set\n");
#endif

		}

		pBD->cb_head = newBuffer;
		pBD->capacity = (short)newCapacity;
	}

#ifdef DEBUGBUFF
	printf(" Adding char: %c, addc_offset: %d\n", symbol, pBD->addc_offset);
#endif

	/*Add the character to the buffer and increment the addc_offset*/
	pBD->cb_head[pBD->addc_offset++] = symbol;
	//++pBD->addc_offset;

	return pBD;
}

/*
Purpose: Clear all the values of the buffer, but does not clear memory location
History/Versions: 1.0
Called functions: None
Parameters: A pointer to the buffer structure
Return value: return 0, -1 if fail
Algorithm: Check if valid, set the values for addc_offset, eob, getc_offset, and marck_offset all to 0
*/
int b_clear(Buffer * const pBD)
{
	/*Parameter check*/
	if (pBD == NULL)
		return RT_FAIL1;

	/*Reset offsets and eob*/
	pBD->addc_offset = 0;
	pBD->eob = UNSET_R_FLAG;
	pBD->getc_offset = 0;
	pBD->markc_offset = 0;
	pBD->r_flag = UNSET_R_FLAG;

	return 0;
}

/*
Purpose: Frees heap memory
History/Versions: 1.0
Called functions: free()
Parameters: A pointer to the buffer structure
Return value: None
Algorithm: Check if valid, free up the char array and th buffer
*/
void b_free(Buffer * const pBD)
{
	/*Parameter check*/
	if (pBD == NULL)
		return;

	free(pBD->cb_head); /*Free the character array*/
	free(pBD); /*Free the buffer structure*/
}

/*
Purpose: Check if the buffer is full
History/Versions: 1.0
Called functions: None
Parameters: A pointer to the buffer structure
Return value: An integer 1 if full 0 if not full, -1 on error
Algorithm: Check if valid, check if the addc_offset and capacity are equal
*/
int b_isfull(Buffer * const pBD)
{
	/*Parameter check*/
	if (pBD == NULL)
		return RT_FAIL1;

	if ((pBD->addc_offset * sizeof(char)) == (size_t)pBD->capacity)
		return TRUE;

	else
		return FALSE;
}

/*
Purpose: Get the size of the buffer being used
History/Versions: 1.0
Called functions: None
Parameters: A pointer to the buffer structure
Return value: Short for size of buffer, -1 on error
Algorithm: Checks if valid, return the addc_offset
*/
short b_limit(Buffer * const pBD)
{
	/*Parameter check*/
	if (pBD == NULL)
		return RT_FAIL1;

	return (pBD->addc_offset * sizeof(char));
}

/*
Purpose: Get the total capacity of the buffer
History/Versions: 1.0
Called functions: None
Parameters: A pointer to the buffer structure
Return value: A short as the capacity, -1 on error
Algorithm: Checks if vald, return capacity
*/
short b_capacity(Buffer * const pBD)
{
	/*Parameter check*/
	if (pBD == NULL)
		return RT_FAIL1;

	return pBD->capacity;
}

/*
Purpose: Set the mark in the buffer
History/Versions: 1.0
Called functions: None
Parameters: A pointer to the buffer structure, and a short to the mark value for offset
Return value: A pointer to the character at the mark location, NULL if error
Algorithm: Checks if valid, set the mark offset to mark, return pointer
*/
short b_mark(Buffer * const pBD, short mark)
{
	/*Parameter checks*/
	if (pBD == NULL)
		return RT_FAIL1;

	else if (mark < 0 || mark > pBD->addc_offset)
		return RT_FAIL1;


	pBD->markc_offset = mark; /*Set the mark_offset*/
	return pBD->markc_offset;
	//return pBD->cb_head + pBD->markc_offset; /*pointer to new value*/
}

/*
Purpose: Get the mode value
History/Versions: 1.0
Called functions: None
Parameters: A pointer to the buffer structure
Return value: A char as the o_mode, -2 on error
Algorithm: Checkif valid , return the mode
*/
int b_mode(Buffer * const pBD)
{
	/*Parameter check*/
	if (pBD == NULL)
		return RT_FAIL2;

	return pBD->mode;
}

/*
Purpose: Get the inc_factor
History/Versions: 1.0
Called functions: None
Parameters: A pointer to the buffer structure
Return value: An unsigned int (size_t) as the inc_factor, -3 on error
Algorithm: Checks if valid, store the inc_factor, cast it to size_t  and return
*/
size_t b_incfactor(Buffer * const pBD)
{
	unsigned char newFactor;
	/*Parameter check*/
	if (pBD == NULL)
		return RT_FAIL3;

	newFactor = (unsigned char)pBD->inc_factor; /*Store the unsigned value of the inc_factor*/

	return (size_t)newFactor; /*Cast it to size_t and return*/
}

/*
Purpose: Load a file into the buffer
History/Versions: 1.0
Called functions: feof(), printf(), b_addc()
Parameters: A pointer to the file to load, A pointer to the buffer structure
Return value: integer for num chars loaded, -1, or -2 errors
Algorithm: Checks if valid, read a chars from file, add the chars to the buffer if eof is not reached.
*/
int b_load(FILE * const fi, Buffer * const pBD)
{
	int loading = 0; /*Character to load */
	short loadingChars = 0; /*The number of characters loaded*/

							/*Parameter checks*/
	if (fi == NULL)
		return RT_FAIL1;

	else if (pBD == NULL)
		return RT_FAIL1;

	/*Load while the EOF character is not reached*/
	while (feof(fi) == FALSE)
	{
		loading = fgetc(fi);

#ifdef DEBUGBUFF
		printf(" The character from file: %c\n", (char)loading);
#endif
		if (loading == EOF)
			break;

		/*Add the character to the buffer*/
		if (b_addc(pBD, (char)loading) == NULL)
			return LOAD_FAIL;

		++loadingChars;
	}

#ifdef DEBUGBUFF
	printf(" Characters loaded: %d\n", loadingChars);
#endif

	return loadingChars;
}

/*
Purpose: Check if the buffer is empty
History/Versions: 1.0
Called functions: None
Parameters: A pointer to the buffer structure
Return value: An integer 1 if empty 0 if not empty, -1 on error
Algorithm: Checks if valid, check the addc_offset and return
*/
int b_isempty(Buffer * const pBD)
{
	/*Parameter check*/
	if (pBD == NULL)
		return RT_FAIL1;

	/*Buffer is empty if the offset is in the first position*/
	if (pBD->addc_offset == 0)
		return TRUE;

	return FALSE;
}

/*
Purpose: Get the eob flag
History/Versions: 1.0
Called functions: None
Parameters: A pointer to the buffer structure
Return value:  int eob, -1 on error
Algorithm: Check if valid, then return the eob
*/
int b_eob(Buffer * const pBD)
{
	/*Parameter check*/
	if (pBD == NULL)
		return RT_FAIL1;

	return pBD->eob;
}

/*
Purpose: Get the next character in the buffer.
History/Versions: 1.0
Called functions: None
Parameters: A pointer to the buffer structure
Return value: The character at the getc_offset, -1 if the buffer is full, -2 on error
Algorithm: Checks if valid, check if buffer is full, unset the eob, increment the getc_offset then return
*/
char b_getc(Buffer * const pBD)
{
	/*Parameter check*/
	if (pBD == NULL)
		return RT_FAIL2;

#ifdef DEBUGBUFF
	printf(" The addc offset is : %d, The getc offset is: %d\n", pBD->addc_offset, pBD->getc_offset);
#endif

	/*If getc offset and addc offset are equal*/
	if (pBD->getc_offset == pBD->addc_offset)
	{
		pBD->eob = SET_R_FLAG;

#ifdef DEBUGBUFF
		printf("THE EOB is reached\n");
#endif
		return RT_FAIL1;
	}

	pBD->eob = UNSET_R_FLAG;

	return pBD->cb_head[pBD->getc_offset++];
}

/*
Purpose: Print the contents of the buffer
History/Versions: 1.0
Called functions: b_isempty(), b_eob(), b_getc(), putcha(), b_size()
Parameters: A pointer to the buffer structure
Return value: An integer as the number of characters printed, -1 on paramter error, or -2 if b_getc () returns an error
Algorithm: Checks if valid, if empty display message, then resets the eob, getcoffset, then prints one char at a time if the eob is not reached, then reset the eob and getc offset again. returns.
*/
int b_print(Buffer * const pBD)
{
	char charsToPrint = 0; /*Character to print*/
	short charsPrinted = 0; /*Number of characters printed*/

							/*Parameter check*/
	if (pBD == NULL)
		return RT_FAIL1;

	if (b_isempty(pBD))
	{
		printf("Empty Buffer\n");
		return 0;
	}

	pBD->getc_offset = 0;
	pBD->eob = UNSET_R_FLAG;

	/*Print characters while the end of the buffer has not been reached*/
	while (!b_eob(pBD))
	{
		charsToPrint = b_getc(pBD);

		if (b_eob(pBD))
			break;

		putchar(charsToPrint);
		++charsPrinted;
	}
	printf("\n");

	pBD->getc_offset = 0;
	pBD->eob = UNSET_R_FLAG;

#ifdef DEBUGBUFF
	printf(" the totals chars printed: %d\n", charsPrinted);
#endif

	return charsPrinted;
}

/*
Purpose: Compacts the buffer, changes capacity to size + 1  more char, adds symbol to buffer if possible
History/Versions: 1.0
Called functions: realloc()
Parameters: A pointer to the buffer structure, and a char for a new character called symbol
Return value: A pointer to the Buffer structure, NULL on error
Algorithm: Checks if valid, Calculates the new capacity, reallocate the char array, update pointer and capacity and flag, adds the character if possible
*/
Buffer * b_compact(Buffer * const pBD, char symbol)
{
	short newCapacity = 0; /*The new capacity to rellocate with (the buffer size + 1 more space)*/
	char * newBuffer = NULL; /*A pointer to the reallocated buffer*/

							 /*Parameter checks*/
	if (pBD == NULL)
		return NULL;

	pBD->r_flag = UNSET_R_FLAG;

	newCapacity = (pBD->addc_offset + 1) * sizeof(char); /*Calculate the new capacity*/

	if (newCapacity < 0 || newCapacity > MAX_BUFFER)
		return NULL;

#ifdef DEBUGBUFF
	printf("The newCapacity is: %d\n", newCapacity);
	printf("The newCapacity with size_t is: %d\n", (size_t)newCapacity);
#endif

	/*Reallocateed buffer capaity*/
	newBuffer = (char *)realloc(pBD->cb_head, (size_t)newCapacity);
	if (newBuffer == NULL)
		return NULL;

	if (pBD->cb_head != newBuffer)
		pBD->r_flag = SET_R_FLAG;

	/*Update the buffer*/
	pBD->capacity = newCapacity;
	pBD->cb_head = newBuffer;

	pBD->cb_head[pBD->addc_offset] = symbol;
	++pBD->addc_offset;

	return pBD;
}

/*
Purpose: Gets r_flag
History/Versions: 1.0
Called functions: None
Parameters:A pointer to the buffer structure
Return value: char the r_flag, -1 on error
Algorithm: Check if valid, return the r_flag
*/
char b_rflag(Buffer * const pBD)
{
	/*Parameter check*/
	if (pBD == NULL)
		return RT_FAIL1;

	return pBD->r_flag;
}

/*
Purpose: Decrement the getcoffset
History/Versions: 1.0
Called functions: None
Parameters: A pointer to the buffer struct
Return value: A short as the getc_offset, -1 on error
Algorithm: Check if valid, decrement the getc_offset and return it
*/
short b_retract(Buffer * const pBD)
{
	/*Parameter check*/
	if (pBD == NULL)
		return RT_FAIL1;
	if (pBD->getc_offset == 0)
		return pBD->getc_offset;

	return --pBD->getc_offset;
}

/*
Purpose: Retract the getc_offset to the mark_offset
History/Versions: 1.0
Called functions: None
Parameters: A pointer to the buffer struct
Return value: A short as the getc_offset, -1 on error
Algorithm: Checks if valid, set the getc_offset to the markc_offset, return
*/
short b_reset(Buffer * const pBD)
{
	/*Parameter check*/
	if (pBD == NULL)
		return RT_FAIL1;

	pBD->getc_offset = pBD->markc_offset;
	return pBD->getc_offset;
}

/*
Purpose: Get the c offset
History/Versions: 1.0
Called functions: None
Parameters: A pointer to the buffer struct
Return value: A short as the getcoffset, -1 on error
Algorithm: Checks if valid, return the getcoffset
*/
short b_getcoffset(Buffer * const pBD)
{
	/*Parameter check*/
	if (pBD == NULL)
		return RT_FAIL1;

	return pBD->getc_offset;
}
/*
Purpose: Sets both markc offset and getc offset to 0 
History/Versions: 1.0
Called functions: None
Parameters: A pointer to the buffer struct
Return value: Returns 0 on success else, -1 on error
Algorithm: Checks if valid, set both getcoffset and markcoffset to 0 return success
*/
int b_rewind(Buffer * const pBD)
{
	/*Parameter check*/
	if (pBD == NULL)
		return RT_FAIL1;

	pBD->getc_offset = 0;
	pBD->markc_offset = 0;

	return 0;
}
/*
Purpose: Find the location as specified by loc_offset a

History/Versions: 1.0
Called functions: None
Parameters: A pointer to the buffer struct, and a short to the location loc_offset
Return value: Pointer to loc_offset, NULL on error
Algorithm: Checks if valid, ireturns pointer to loc_offset on success else NULL
*/
char * b_location(Buffer * const pBD, short loc_offset)
{
	if (pBD == NULL)
		return NULL;
	else if (loc_offset < 0)
		return NULL;

	else if (loc_offset > pBD->addc_offset)
		return NULL;


	return pBD->cb_head + loc_offset;
}
