/* : mem handling stuff
 *
 * 2/11/99 JC
 *	- from im_open.c and callback.c
 *	- malloc tracking stuff added
 * 11/3/01 JC
 * 	- im_strncpy() added
 * 20/4/01 JC
 * 	- im_(v)snprintf() added
 * 6/7/05
 *	- more tracking for DEBUGM
 * 20/10/06
 * 	- return NULL for size <= 0
 * 11/5/06
 * 	- abort() on malloc() failure with DEBUG
 * 20/10/09
 * 	- gtkdoc comment
 * 6/11/09
 *	- im_malloc()/im_free() now call g_try_malloc()/g_free() ... removes 
 *	  confusion over whether to use im_free() or g_free() for things like 
 *	  im_header_string()
 */

/*

    This file is part of VIPS.
    
    VIPS 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 program 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, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

 */

/*

    These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk

 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <assert.h>

#include <vips/vips.h>
#include <vips/thread.h>

#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif /*WITH_DMALLOC*/

/**
 * SECTION: memory
 * @short_description: memory utilities
 * @stability: Stable
 * @include: vips/vips.h
 *
 * Simple memory allocation utilities. These functions and macros help
 * allocate and free memory. Most of VIPS uses them, though some parts use
 * the g_malloc() system instead, confusingly.
 *
 * If you compile with %DEBUGM it will track allocations for you, though
 * valgrind or dmalloc are better solutions.
 */

/* Define for simple malloc tracking ... better to use dmalloc if you can.
#define DEBUGM
 */

/* g_assert( 0 ) on memory errors.
#define DEBUG
 */

#ifdef DEBUG
#  warning DEBUG on in libsrc/iofuncs/memory.c
#endif /*DEBUG*/

/* Track total alloc/total free here for debugging.
 */
#ifdef DEBUGM
static size_t int total_mem_alloc = 0;
static unsigned int total_allocs = 0;
static size_t int high_water_mark = 0;
static GMutex *malloc_mutex = NULL;
static GSList *malloc_list = NULL;
static const int trace_freq = 100;	/* Msg every this many malloc/free */
static int next_trace = 0;
#endif /*DEBUGM*/

/**
 * IM_NEW:
 * @IM: allocate memory local to @IM, or %NULL for no auto-free
 * @T: type of thing to allocate
 *
 * Returns: A pointer of type @T *, or %NULL on error.
 */

/**
 * IM_ARRAY:
 * @IM: allocate memory local to @IM, or %NULL for no auto-free
 * @N: number of @T 's to allocate
 * @T: type of thing to allocate
 *
 * Returns: A pointer of type @T *, or %NULL on error.
 */

/**
 * im_free:
 * @s: memory to free
 *
 * VIPS free function. VIPS tries to use this instead of free(). It always
 * returns zero, so it can be used as a callback handler. 
 *
 * Only use it to free
 * memory that was previously allocated with im_malloc() with a %NULL first
 * argument.
 *
 * Returns: 0
 */
int
im_free( void *s )
{
#ifdef DEBUGM
{
	size_t size;

	s = (void *) ((char*)s - 16);
	size = *((size_t*)s);
	g_mutex_lock( malloc_mutex );

	assert( g_slist_find( malloc_list, s ) );
	malloc_list = g_slist_remove( malloc_list, s );
	assert( !g_slist_find( malloc_list, s ) );
	malloc_list = g_slist_remove( malloc_list, s );
	assert( total_allocs > 0 );

	total_mem_alloc -= size;
	total_allocs -= 1;

	next_trace += 1;
	if( next_trace > trace_freq ) {
		printf( "im_free: %d, %d allocs, total %.3gM, "
			"high water %.3gM\n", 
			size,
			total_allocs,
			total_mem_alloc / (1024.0 * 1024.0), 
			high_water_mark / (1024.0 * 1024.0) );
		next_trace = 0;
	}

	g_mutex_unlock( malloc_mutex );
}
#endif /*DEBUGM*/

#ifdef DEBUG
	if( !s )
		g_assert( 0 );
#endif /*DEBUG*/

	g_free( s );

	return( 0 );
}

/**
 * im_malloc:
 * @im: allocate memory local to this #IMAGE, or %NULL
 * @size: number of bytes to allocate
 *
 * Malloc local to @im, that is, the memory will be automatically 
 * freed for you when the image is closed. If @im is %NULL, you need to free
 * the memory explicitly with im_free().
 * If allocation fails im_malloc() returns %NULL and 
 * sets an error message.
 *
 * If two threads try to allocate local to the same @im at the same time, you 
 * can get heap corruption. 
 *
 * Returns: a pointer to the allocated memory, or %NULL on error.
 */
void *
im_malloc( IMAGE *im, size_t size )
{
        void *buf;

#ifdef DEBUGM
	/* Assume the first im_malloc() is single-threaded.
	 */
	if( !malloc_mutex )
		malloc_mutex = g_mutex_new();
#endif /*DEBUGM*/

#ifdef DEBUGM
	/* If debugging mallocs, need an extra sizeof(uint) bytes to track 
	 * size of this block. Ask for an extra 16 to make sure we don't break
	 * alignment rules.
	 */
	size += 16;
#endif /*DEBUGM*/

        if( !(buf = g_try_malloc( size )) ) {
#ifdef DEBUG
		g_assert( 0 );
#endif /*DEBUG*/

		im_error( "im_malloc", 
			_( "out of memory --- size == %dMB" ), 
			(int) (size / (1024.0*1024.0))  );
		im_warn( "im_malloc", 
			_( "out of memory --- size == %dMB" ), 
			(int) (size / (1024.0*1024.0))  );
                return( NULL );
	}

#ifdef DEBUGM
	/* Record number alloced.
	 */
	g_mutex_lock( malloc_mutex );
	assert( !g_slist_find( malloc_list, buf ) );
	malloc_list = g_slist_prepend( malloc_list, buf );
	*((size_t*)buf) = size;
	buf = (void *) ((char*)buf + 16);
	total_mem_alloc += size;
	if( total_mem_alloc > high_water_mark ) 
		high_water_mark = total_mem_alloc;
	total_allocs += 1;

	next_trace += 1;
	if( next_trace > trace_freq ) {
		printf( "im_malloc: %d, %d allocs, total %.3gM, "
			"high water %.3gM\n", 
			size, 
			total_allocs,
			total_mem_alloc / (1024.0 * 1024.0),
			high_water_mark / (1024.0 * 1024.0) );
		next_trace = 0;
	}

	g_mutex_unlock( malloc_mutex );

	/* Handy to breakpoint on this printf() for catching large mallocs().
	 */
	if( size > 1000000 ) 
		printf( "woah! big!\n" );
#endif /*DEBUGM*/
 
        if( im && im_add_close_callback( im, 
		(im_callback_fn) im_free, buf, NULL ) ) {
                im_free( buf );
                return( NULL );
        }
 
        return( buf );
}
