/**
 * Dynamic memory allocation routines with memory corruption detection and
 * memory leaks detection. Each allocated block may have a defined destructor
 * function that gets invoked before actually release the memory block.
 * 
 * A specific header block is prepended to each allocated block to carry
 * memory leaks detection and corruption. There is no currently way to disable
 * this safety mechanism.
 * 
 * Each allocated block may define a destructor function that gets called before
 * the block is actually released. In this way a single memory_dispose() function
 * can be used to safely release blocks allocated by any module.
 * 
 * Modules may register a cleanup function. All these registered cleanup functions
 * are then called along with the memory reporting routine and then de-registered.
 * The main function of each program may then call this reporting function
 * at the very end of its code, just before its normal ending, and may also
 * exit with the error status code returned by the reporting function itself:
 * 
 * <pre>
 * int main()
 * {
 *     ...
 *     return memory_report();
 * }
 * </pre>
 * 
 * Reporting memory leaks is particularly important in test programs, and can be
 * only optionally compiled in released programs.
 * 
 * Mixing calls to the functions of this module with calls to the original
 * stdlib.h system calls over the same allocated blocks leads to memory corruption
 * and (hopefully :-) to an immediate crash.
 * 
 * @file
 * @author Umberto Salsi <salsi@icosaedro.it>
 * @version $Date: 2017/11/02 07:37:59 $
 */

#ifndef MEMORY_H
#define	MEMORY_H

#include <string.h>

#ifdef memory_IMPORT
	#define EXTERN
#else
	#define EXTERN extern
#endif

/**
 * Allocates a memory block of at least the given size. If fails or the requested
 * size is not positive, gives error and aborts.
 * @param size Capacity of the block (bytes). Must be non-negative.
 * @param destructor Destructor function to call before actually release this
 * block from memory. Can be NULL.
 * @return Pointer to the allocated block.
 */
#define memory_allocate(size, destructor) memory_allocate_PRIVATE(__FILE__, __LINE__, size, destructor)

/**
 * Releases a memory block allocated with this module. If the destructor function
 * is defined for this block, calls that function before actually releasing the
 * memory. Releasing an invalid pointer or a block already released aborts the
 * program.
 * @param p Block of memory to release. Does nothing if NULL.
 */
EXTERN void memory_dispose(void * p);

/**
 * Re-allocates a previously allocated block of memory by also copying the
 * original content to the new block as required. If fails or the requested size
 * is not positive, gives error and exits with status code 1.
 * @param p Block to re-allocate. If NULL, this function performs just like
 * memory_allocate().
 * @param size New capacity of the block (bytes). Must be positive.
 * @return Pointer to the block of the given capacity.
 */
#define memory_realloc(p,size) memory_realloc_PRIVATE(__FILE__,__LINE__,p,size)

/**
 * Copies a NUL-terminated string. Maximum dst_capacity-1 bytes are copied from
 * the source string to leave room for the NUL byte. The destination string is
 * always NUL-terminated. If dst_capacity is less than 1, a fatal error occurs.
 * Bytes of the destination beyond the NUL terminator are not set.
 * Please note that this function is very similar to the standard strncpy(), but
 * here the destination is always properly NUL-terminated.
 * @param dst Destination string.
 * @param dst_capacity Capacity of the destination string. Must be at least 1.
 * @param src Source string.
 */
EXTERN void memory_strcpy(char *dst, int dst_capacity, char *src);

/**
 * Returns a copy of the given NUL-terminated string. If it fails, aborts.
 * Must be released with memory_dispose().
 * @param s NUL-terminated string to copy.
 * @return Dynamically allocated block containing a copy of the string.
 */
EXTERN char * memory_strdup(char * s);

/**
 * Returns true if the string starts with the specified head. An empty head is
 * the beginning of any string.
 * @param s
 * @param head
 * @return True if 's' starts with the bytes of 'head'.
 */
EXTERN int memory_startsWith(char * s, char * head);

/**
 * Returns 1 if the string begins with the given leading bytes of the tag,
 * otherwise returns 0. The comparison is case-insensitive according to the
 * function "toupper()" which is locale-aware, so strange and unexpected
 * results may occur if the tag contains non-ASCII codes.
 * @param s NUL-terminated string.
 * @param tag NUL-terminated string of leading bytes.
 * @return 1 if the string begins with the given leading bytes of the tag,
 * otherwise returns 0.
 */
EXTERN int memory_startsWithIgnoreCase(char * s, char * tag);

/**
 * Returns true if the string ends with the specified tail. An empty tail is
 * the end of any string.
 * @param s
 * @param tail
 * @return True if 's' ends with the bytes of 'tail'.
 */
EXTERN int memory_endsWith(char * s, char * tail);

/**
 * Search for a target substring.
 * memmem() seems to be a GNU extension still not widely available,
 * here is its replacement.
 * @param s Search area.
 * @param slen Length of the search area, not negative.
 * @param target Target to search.
 * @param targetlen Length of the target, not negative.
 * @return Pointer to the first occurrence of the target in the search area,
 * or NULL if not found. The empty target is always "found" at the beginning
 * of the search area. If either slen or targetlen is negative, aborts.
 */
EXTERN char * memory_indexOfMem(char *s, int slen, char *target, int targetlen);

/**
 * Returns the index to the first occurrence of a sub-string in a string.
 * The comparison is case-insensitive according to the function "toupper()"
 * which is locale-aware, so strange and unexpected results may occur if the
 * tag contains non-ASCII codes.
 * @param str NUL-terminated string.
 * @param word NUL-terminated sub-string to search in the string.
 * @return Byte offset of the found sub-string, or -1 if not found.
 */
EXTERN int memory_indexOfStringIgnoreCase(char * str, char * word);

/**
 * Returns the pointer to the value of the given environmental variable.
 * @param name Name of the environmental variable.
 * @param default_value Default value. If NULL, and the requested environment
 * variable does not exist, it is a fatal error with exit code 1.
 * @return Pointer to the value of the given environmental variable, or the
 * default value if not NULL.
 */
EXTERN char * memory_getEnvKey(char * name, char * default_value);

/**
 * Returns a pointer to a string formatted according to the given format.
 * @param fmt Format descriptor, just like "printf".
 * @param ... Parameters.
 * @return Pointer to the resulting formatted string. The string is statically
 * allocated with a fixed capacity of 1000 bytes, and does not need to be
 * deallocated.
 */
EXTERN char *memory_format(char *fmt, ...);

/**
 * Registers a cleanup function to call by memory_report(). Modules may register
 * here their specific cleanup function that releases from memory any temporary
 * data allocated. Cleanup functions are called in the reverse order of their
 * registration and then are de-registered. Trying to register the same function
 * more than once has no effect.
 * @param cleanup Cleanup function to register.
 */
EXTERN void memory_registerCleanup(void (*cleanup)());

/**
 * Calls any registered cleanup function and then reports memory leaks. Cleanup
 * functions are invoked in reverse order of registration and, once called, are
 * de-registered. The report is written to stderr; it includes the first 100
 * remaining allocated blocks along with the file name and line number of the
 * location where the block was firstly allocated; for each block the first 20
 * bytes are also displayed; a summary of the total number of remaining allocated
 * blocks and their total size is also displayed. If there are not memory leaks,
 * nothing is sent to stderr.
 * @return Zero if there are not remaining allocated blocks, 1 otherwise.
 */
EXTERN int memory_report(void);

/**
 * USE THE MACRO.
 * Allocates a memory block of at least the given size. If fails or the requested
 * size is not positive, gives error and exits with status code 1.
 * @param file File name of the source for memory leaks reporting.
 * @param line Line number of the source for memory leaks reporting.
 * @param size Capacity of the block (bytes). Must be non-negative.
 * @param destructor Destructor function to call before actually release this
 * block.
 * @return Pointer to the allocated block.
 */
EXTERN void * memory_allocate_PRIVATE(char *file, int line, int size, void (*destructor)(void *data));

/**
 * USE THE MACRO.
 * For internal use only, use the memory_realloc(p,s) macro instead.
 */
EXTERN void * memory_realloc_PRIVATE(char *file, int line, void * p, int size);

#define memory_zero(ptr) memset(ptr, 0, sizeof(*(ptr)))

#undef EXTERN

#ifndef memory_IMPORT
// Catch possible direct usages of malloc() realloc(), strdup() and free() from here on:
#define malloc  ERROR_invalid_direct_usage_of_malloc
#define free    ERROR_invalid_direct_usage_of_free
#define realloc ERROR_invalid_direct_usage_of_realloc
#define strdup  ERROR_invalid_direct_usage_of_strdup
#endif

#ifdef WINNT
#define bcopy(s,d,n) memmove(d,s,n)
#endif

#endif	/* MEMORY_H */

