/*!\file n_debug_mem.c
 * Helpers for debugging memory
 *\author Castagnier Mickael
 *\date 2014-10-10
 */

#include "nilorea/n_debug_mem.h"
#include "nilorea/n_common.h"
#include "nilorea/n_log.h"
#include "nilorea/n_hash.h"



/*! global table of allocation */
static DEBUG_MEM_TABLE __debug_mem_table ;


/*!\fn int init_debug_mem()
 *\brief initialize debug tables
 *\return TRUE or FALSE
 */
int init_debug_mem()
{
    __debug_mem_table . nb_malloc = 0 ;
    __debug_mem_table . nb_free = 0 ;

    /* A huge table because there could be a ton of memory allocation */
    __debug_mem_table . table = new_ht( 1048576 );
    if( ! __debug_mem_table . table )
    {
        n_log( LOG_ERR, "Unable to allocate a 1048576 item hash table" );
        return FALSE ;
    }

    if( pthread_mutex_init( &__debug_mem_table . lock, NULL ) != 0 )
    {
        n_log( LOG_ERR, "Unable to initialize lock" );
        return FALSE ;
    }

    return TRUE ;
} /* init_debuf_mem() */



/*!\fn DEBUG_MEM_ITEM *new_db_mem_item( void *ptr , size_t size , int line , char *file , char *func )
 *\brief allocate a new item in the debug table
 *\param ptr Pointer address to save
 *\param size the size of the allocated data
 *\param line line of the allocation
 *\param file file of the allocation
 *\param func function where the allocation was made
 *\return A new item or NULL
 */
DEBUG_MEM_ITEM *new_db_mem_item( void *ptr, size_t size, int line, char *file, char *func )
{
    DEBUG_MEM_ITEM *item = NULL ;

    item = (DEBUG_MEM_ITEM *)calloc( 1, sizeof( DEBUG_MEM_ITEM ) );
    if( !item )
    {
        n_log( LOG_ERR, "Unable to allocate a DEBUG_MEM_ITEM" );
        return NULL ;
    }
    item -> ptr = ptr ;
    item -> size = size ;
    item -> line = line ;
    item -> file = file ;
    item -> func = func ;

    return item ;
} /* new_db_item() */



/*!\fn void delete_db_mem_item( void *ptr )
 *\brief delete a debug mem item
 *\param ptr the item to destroy
 */
void delete_db_mem_item( void *ptr )
{
    DEBUG_MEM_ITEM *item = (DEBUG_MEM_ITEM *)ptr ;
    if( item )
    {
        FreeNoLog( item -> file );
        FreeNoLog( item -> func );
        FreeNoLog( item );
    }
} /* delete_db_mem_item */



/*!\fn void *db_mem_alloc( size_t size , int line , char *file , const char *func )
 *\brief allocate a new pointer with size and also put it in the debug table
 *\param size the size of the allocated data
 *\param line line of the allocation
 *\param file file of the allocation
 *\param func function where the allocation was made
 *\return A new item or NULL
 */
void *db_mem_alloc( size_t size, int line, char *file, const char *func )
{
    DEBUG_MEM_ITEM *item = NULL ;
    void *ptr = NULL ;

    __debug_mem_table . nb_malloc ++ ;

    ptr = calloc( 1, size );
    if( ptr )
    {
        char key[ 512 ] = "";
        item = new_db_mem_item( ptr, size, line, strdup( file ), strdup( func ) );
        sprintf( key, "%p", ptr );
        ht_put_ptr( __debug_mem_table . table, key, item, delete_db_mem_item );
        n_log( LOG_DEBUG, "Added %p size %zu file %s func %s line %d", item -> ptr, item -> size, item -> file, item -> func, item -> line );
    }
    else
    {
        n_log( LOG_ERR, "Unable to allocate %d oct at file %s func %s line %d ", size, file, func, line );
    }
    return ptr ;
}



/*!\fn void *db_mem_free( void *ptr )
 *\brief Free a pointer and also remove it from debug table
 *\param ptr Pointer to free
 *\param line line of the free call
 *\param file file of the free call
 *\param func function where the free call was made
 */
void db_mem_free( void *ptr, int line, char *file, const char *func )
{
    void *hashptr = NULL ;
    char key[ 512 ] = "";

    sprintf( key, "%p", ptr );
    ht_get_ptr( __debug_mem_table . table, key, &hashptr );
    if( hashptr )
    {
        DEBUG_MEM_ITEM *item = hashptr ;
        n_log( LOG_DEBUG, "Removed %p size %zu file %s func %s line %d\nCall from %s,%s:%d", item -> ptr, item -> size, item -> file, item -> func, item -> line, file, func, line );
        delete_db_mem_item( item );
        item = NULL ;
        ht_remove( __debug_mem_table . table, key );
        __debug_mem_table . nb_free ++ ;
    }
    Free( ptr );
} /* db_mem_free() */



/*!\fn int db_mem_dump_leaked( char *file )
 *\brief Dump the current debug memory table
 *\param file The name of the logfile
 *\return TRUE or FALSE
 */
int db_mem_dump_leaked( char *file )
{
    FILE *out = NULL ;
    LIST_NODE *node = NULL ;
    HASH_NODE *hash_entry = NULL ;
    DEBUG_MEM_ITEM *item = NULL ;
    size_t len = 0 ;

    out = fopen( file, "w" );
    if( !out )
    {
        n_log( LOG_ERR,"Unable to open %s", file );
        return FALSE ;
    }

    fprintf( out, "%d malloc / %d free\n", __debug_mem_table . nb_malloc, __debug_mem_table . nb_free );

    for( int it = 0 ; it < __debug_mem_table . table -> size ; it ++ )
    {
        node =  __debug_mem_table . table -> hash_table[ it ] -> start ;
        while( node )
        {
            hash_entry= (HASH_NODE *)node -> ptr ;
            item = hash_entry -> data . ptr ;

            if( item )
            {
                fprintf( out, "%p size %zu file %s func %s line %d dump:", item -> ptr, item -> size, item -> file, item -> func, item -> line );
                if( item -> size > DEBUG_MEM_DUMP_MAX_LEN )
                    len = DEBUG_MEM_DUMP_MAX_LEN ;
                else
                    len = item -> size ;

                fwrite( item -> ptr, len, 1, out );
                fprintf( out, "\n" );
                delete_db_mem_item( item );
                item = NULL ;
            }
            else
            {
                n_log( LOG_ERR,"Unable to load item from node -> ptr" );
            }
            node = node -> next ;
        }
    }

    fclose( out );
    n_log( LOG_DEBUG, "Memory dump finished" );
    return TRUE ;
} /*db_mem_dump_leaked() */
