#include "internal.h"

#undef malloc
#undef realloc
#undef free

typedef struct CC_Allocation
{
	void *ptr;
	uint64_t size;

	uint64_t val_1;
	uint64_t val_2;

	const char *file;
	uint64_t line;
} CC_Allocation;

typedef struct CC_DebugData
{
	const char *prev_file;
	uint64_t prev_line;

	uint64_t count_c;
	uint64_t count_m;
	CC_Allocation *allocations;

	CL_Logger *logger;
} CC_DebugData;

CC_DebugData *cc_debug_data = NULL;

void cc_debug_set_values(CC_Allocation *al, void *ptr, uint64_t size)
{
	al->ptr = ptr;
	al->size = size;

	al->val_1 = cc_hash(&ptr, sizeof(ptr), 0);
	al->val_2 = cc_hash(&ptr, sizeof(ptr), al->val_1);

	((uint64_t *)ptr)[0] = al->val_1;
	((uint64_t *)((uint8_t *)ptr + size + sizeof(uint64_t)))[0] = al->val_2;
}

void cc_debug_heap_check(const char *file, uint64_t line)
{
	for (uint64_t i = 0; i < cc_debug_data->count_c; i++)
	{
		CC_Allocation *al = cc_debug_data->allocations + i;
		uint64_t *ptr = al->ptr;
		uint64_t val_1_different = (al->val_1 != ptr[0]);
		uint64_t val_2_different = (al->val_2 != *((uint64_t *)(((uint8_t *)ptr) + sizeof(uint64_t) + al->size)));
		if (val_1_different || val_2_different)
		{
			CL_LOG_FATAL(cc_debug_data->logger, "Heap is corrupted on ptr %p of size %llu\nregistered_values = { %llu, %llu }\n actual_values = { %llu, %llu }.\ncall occured at %s, %llu.\ncurrent call is at %s, %llu.",
				ptr, al->size, al->val_1, al->val_2, ptr[0], *((uint64_t *)(((uint8_t *)ptr) + sizeof(uint64_t) + al->size)), al->file, al->line, file, line);
			exit(EXIT_FAILURE);
		}
		//else
		//{
		//	CL_LOG_INFO(cc_debug_data->logger, "registered_values = { %llu, %llu }\nactual_values = { %llu, %llu }", al->val_1, al->val_2, ptr[0], *((uint64_t *)(((uint8_t *)ptr) + sizeof(uint64_t) + al->size)));
		//}
	}
}

void *cc_debug_malloc(uint64_t size, const char *file, uint64_t line)
{
	// first call only
	if (cc_debug_data == NULL)
	{
		cc_debug_data = malloc(sizeof(*cc_debug_data));
		if (cc_debug_data != NULL)
		{
			cc_debug_data->logger = CL_LOGGER_CREATE("CC_DEBUG_MALLOC", CL_DEFAULT_PATTERN, 1, stderr);

			cc_debug_data->prev_file = NULL;
			cc_debug_data->prev_line = 0;
			
			cc_debug_data->count_c = 0;
			cc_debug_data->count_m = 16;
			cc_debug_data->allocations = malloc(cc_debug_data->count_m * sizeof(*cc_debug_data->allocations));
			if (cc_debug_data->allocations == NULL)
			{
				// error
				CL_LOG_FATAL(cc_debug_data->logger, "cc_debug_data->allocations malloc failed at %s, %llu.", file, line);
				exit(EXIT_FAILURE);
				return NULL;
			}
		}
		else
		{
			// error
			fprintf(stderr, "[CC_DEBUG_ERROR] cc_debug_data malloc failed at %s, %llu.", file, line);
			return NULL;
		}
	}

	// check heap
	cc_debug_heap_check(file, line);

	// extend alloc_data if needed
	if (cc_debug_data->count_c == cc_debug_data->count_m)
	{
		cc_debug_data->count_m <<= 1;
		CC_Allocation *new_data = realloc(cc_debug_data->allocations, cc_debug_data->count_m * sizeof(*cc_debug_data->allocations));
		if (new_data != NULL)
		{
			cc_debug_data->allocations = new_data;
		}
		else
		{
			// error
			CL_LOG_FATAL(cc_debug_data->logger, "Failed to realloc cc_debug_data->allocations at %s, %llu.", file, line);
			exit(EXIT_FAILURE);
			return NULL;
		}
	}

	// alloc memory and set alloc_data
	void *ptr = malloc(size + sizeof(uint64_t) * 2);
	if (ptr != NULL)
	{
		cc_debug_data->allocations[cc_debug_data->count_c].file = file;
		cc_debug_data->allocations[cc_debug_data->count_c].line = line;

		cc_debug_set_values(cc_debug_data->allocations + cc_debug_data->count_c, ptr, size);

		cc_debug_data->count_c++;

		cc_debug_data->prev_file = file;
		cc_debug_data->prev_line = line;
		return ((uint64_t *)ptr) + 1;
	}
	else
	{
		CL_LOG_FATAL(cc_debug_data->logger, "Failed to allocate requested memory of %llu size at %s, %llu.", size, file, line);
		exit(EXIT_FAILURE);
		return NULL;
	}
}

void *cc_debug_realloc(void *ptr, uint64_t new_size, const char *file, uint64_t line)
{
	void *actual_ptr = ((uint64_t *)ptr) - 1;

	cc_debug_heap_check(file, line);

	void *new_ptr = realloc(actual_ptr, new_size + sizeof(uint64_t) * 2);

	if (new_ptr != NULL)
	{
		if (new_ptr != ptr)
		{
			CC_Allocation *al = NULL;
			for (uint64_t i = 0; i < cc_debug_data->count_c; i++)
			{
				if (cc_debug_data->allocations[i].ptr == actual_ptr)
				{
					al = cc_debug_data->allocations + i;
					break;
				}
			}

			if (al != NULL)
			{
				cc_debug_set_values(al, new_ptr, new_size);
			}
			else
			{
				// error
				CL_LOG_FATAL(cc_debug_data->logger, "Passed pointer hasn't been registered at %s, %llu. pointer = %p", file, line, ptr);
				exit(EXIT_FAILURE);
			}
		}
	}
	else
	{
		// error
		CL_LOG_FATAL(cc_debug_data->logger, "Realloc failed at %s, %llu.", file, line);
		exit(EXIT_FAILURE);
	}

	return ((uint64_t *)new_ptr) + 1;
}

void cc_debug_free(void *ptr, const char *file, uint64_t line)
{
	void *actual_ptr = ((uint64_t *)ptr) - 1;

	cc_debug_heap_check(file, line);
	
	CC_Allocation *al = NULL;
	for (uint64_t i = 0; i < cc_debug_data->count_c; i++)
	{
		if (cc_debug_data->allocations[i].ptr == actual_ptr)
		{
			al = cc_debug_data->allocations + i;
			break;
		}
	}
	if (al != NULL)
	{
		cc_debug_data->count_c--;
		*al = cc_debug_data->allocations[cc_debug_data->count_c];
	}
	else
	{
		// error
		CL_LOG_FATAL(cc_debug_data->logger, "Passed pointer hasn't been registered at %s, %llu. pointer = %p", file, line, ptr);
		exit(EXIT_FAILURE);
	}

	free(actual_ptr);
}