/*
 * This file is part of the PCEPlib, a PCEP protocol library.
 *
 * Copyright (C) 2020 Volta Networks https://voltanet.io/
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 *
 * Author : Brady Johnson 
 *
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include 
#include 
#include "pcep_utils_logging.h"
#include "pcep_utils_memory.h"
/* Set default values for memory function pointers */
static pceplib_malloc_func mfunc = NULL;
static pceplib_calloc_func cfunc = NULL;
static pceplib_realloc_func rfunc = NULL;
static pceplib_strdup_func sfunc = NULL;
static pceplib_free_func ffunc = NULL;
/* Internal memory types */
struct pceplib_memory_type pceplib_infra_mt = {
	.memory_type_name = "PCEPlib Infrastructure memory",
	.total_bytes_allocated = 0,
	.num_allocates = 0,
	.total_bytes_freed = 0,
	.num_frees = 0};
struct pceplib_memory_type pceplib_messages_mt = {
	.memory_type_name = "PCEPlib Messages memory",
	.total_bytes_allocated = 0,
	.num_allocates = 0,
	.total_bytes_freed = 0,
	.num_frees = 0};
/* The memory type pointers default to the internal memory types */
void *PCEPLIB_INFRA = &pceplib_infra_mt;
void *PCEPLIB_MESSAGES = &pceplib_messages_mt;
/* Initialize memory function pointers and memory type pointers */
bool pceplib_memory_initialize(void *pceplib_infra_mt,
			       void *pceplib_messages_mt,
			       pceplib_malloc_func mf, pceplib_calloc_func cf,
			       pceplib_realloc_func rf, pceplib_strdup_func sf,
			       pceplib_free_func ff)
{
	PCEPLIB_INFRA = (pceplib_infra_mt ? pceplib_infra_mt : PCEPLIB_INFRA);
	PCEPLIB_MESSAGES =
		(pceplib_messages_mt ? pceplib_messages_mt : PCEPLIB_MESSAGES);
	mfunc = (mf ? mf : mfunc);
	cfunc = (cf ? cf : cfunc);
	rfunc = (rf ? rf : rfunc);
	sfunc = (sf ? sf : sfunc);
	ffunc = (ff ? ff : ffunc);
	return true;
}
void pceplib_memory_reset()
{
	pceplib_infra_mt.total_bytes_allocated = 0;
	pceplib_infra_mt.num_allocates = 0;
	pceplib_infra_mt.total_bytes_freed = 0;
	pceplib_infra_mt.num_frees = 0;
	pceplib_messages_mt.total_bytes_allocated = 0;
	pceplib_messages_mt.num_allocates = 0;
	pceplib_messages_mt.total_bytes_freed = 0;
	pceplib_messages_mt.num_frees = 0;
}
void pceplib_memory_dump()
{
	if (PCEPLIB_INFRA) {
		pcep_log(
			LOG_INFO,
			"%s: Memory Type [%s] Total [allocs, alloc bytes, frees] [%d, %d, %d]",
			__func__,
			((struct pceplib_memory_type *)PCEPLIB_INFRA)
				->memory_type_name,
			((struct pceplib_memory_type *)PCEPLIB_INFRA)
				->num_allocates,
			((struct pceplib_memory_type *)PCEPLIB_INFRA)
				->total_bytes_allocated,
			((struct pceplib_memory_type *)PCEPLIB_INFRA)
				->num_frees);
	}
	if (PCEPLIB_MESSAGES) {
		pcep_log(
			LOG_INFO,
			"%s: Memory Type [%s] Total [allocs, alloc bytes, frees] [%d, %d, %d]",
			__func__,
			((struct pceplib_memory_type *)PCEPLIB_MESSAGES)
				->memory_type_name,
			((struct pceplib_memory_type *)PCEPLIB_MESSAGES)
				->num_allocates,
			((struct pceplib_memory_type *)PCEPLIB_MESSAGES)
				->total_bytes_allocated,
			((struct pceplib_memory_type *)PCEPLIB_MESSAGES)
				->num_frees);
	}
}
/* PCEPlib memory functions:
 * They either call the supplied function pointers, or use the internal
 * implementations, which just increment simple counters and call the
 * C stdlib memory implementations. */
void *pceplib_malloc(void *mem_type, size_t size)
{
	if (mfunc == NULL) {
		if (mem_type != NULL) {
			((struct pceplib_memory_type *)mem_type)
				->total_bytes_allocated += size;
			((struct pceplib_memory_type *)mem_type)
				->num_allocates++;
		}
		return malloc(size);
	} else {
		return mfunc(mem_type, size);
	}
}
void *pceplib_calloc(void *mem_type, size_t size)
{
	if (cfunc == NULL) {
		if (mem_type != NULL) {
			((struct pceplib_memory_type *)mem_type)
				->total_bytes_allocated += size;
			((struct pceplib_memory_type *)mem_type)
				->num_allocates++;
		}
		return calloc(1, size);
	} else {
		return cfunc(mem_type, size);
	}
}
void *pceplib_realloc(void *mem_type, void *ptr, size_t size)
{
	if (rfunc == NULL) {
		if (mem_type != NULL) {
			/* TODO should add previous allocated bytes to
			 * total_bytes_freed */
			((struct pceplib_memory_type *)mem_type)
				->total_bytes_allocated += size;
			((struct pceplib_memory_type *)mem_type)
				->num_allocates++;
		}
		return realloc(ptr, size);
	} else {
		return rfunc(mem_type, ptr, size);
	}
}
void *pceplib_strdup(void *mem_type, const char *str)
{
	if (sfunc == NULL) {
		if (mem_type != NULL) {
			((struct pceplib_memory_type *)mem_type)
				->total_bytes_allocated += strlen(str);
			((struct pceplib_memory_type *)mem_type)
				->num_allocates++;
		}
		return strdup(str);
	} else {
		return sfunc(mem_type, str);
	}
}
void pceplib_free(void *mem_type, void *ptr)
{
	if (ffunc == NULL) {
		if (mem_type != NULL) {
			/* TODO in order to increment total_bytes_freed, we need
			 * to keep track of the bytes allocated per pointer.
			 * Currently not implemented. */
			((struct pceplib_memory_type *)mem_type)->num_frees++;
			if (((struct pceplib_memory_type *)mem_type)
				    ->num_allocates
			    < ((struct pceplib_memory_type *)mem_type)
				      ->num_frees) {
				pcep_log(
					LOG_ERR,
					"%s: pceplib_free MT N_Alloc < N_Free: MemType [%s] NumAllocates [%d] NumFrees [%d]",
					__func__,
					((struct pceplib_memory_type *)mem_type)
						->memory_type_name,
					((struct pceplib_memory_type *)mem_type)
						->num_allocates,
					((struct pceplib_memory_type *)mem_type)
						->num_frees);
			}
		}
		return free(ptr);
	} else {
		return ffunc(mem_type, ptr);
	}
}