/** \file halloc.c

    A hierarchical memory allocation system. Works just like talloc
	used in Samba, except that an arbitrary block allocated with
	malloc() can be registered to be freed by halloc_free.

*/

#include "config.h"


#include <stdlib.h>
#include <unistd.h>

#include "fallback.h"
#include "util.h"

#include "common.h"
#include "halloc.h"

/**
   Extra size to allocate whenever doing a halloc, in order to fill uyp smaller halloc calls
*/
#define HALLOC_BLOCK_SIZE 128

/**
   Maximum size of trailing halloc space to refuse to discard
*/
#define HALLOC_SCRAP_SIZE 16

#ifdef HALLOC_DEBUG
/**
   Debug statistic parameter
*/
static int child_count=0;
/**
   Debug statistic parameter
*/
static int child_size=0;
/**
   Debug statistic parameter
*/
static int alloc_count =0;
/**
   Debug statistic parameter
*/
static int alloc_spill = 0;
/**
   Debug statistic parameter
*/
static pid_t pid=0;
/**
   Debug statistic parameter
*/
static int parent_count=0;
#endif

/**
   The main datastructure for a main halloc context
*/
typedef struct halloc
{
	/**
	   List of all addresses and functions to call on them
	*/
	array_list_t children;
	/**
	   Memory scratch area used to fullfil smaller memory allocations
	*/
	void *scratch;
	/**
	   Amount of free space in the scratch area
	*/
	size_t scratch_free;
#if __STDC_VERSION__ < 199901L
	/**
	   The actual data. MAde to be of type long long to make sure memory alignment is in order.
	*/
	long long data[1]; // Waste one byte on non-C99 compilers... :-( 
#else
	long long data[];
#endif
}
	halloc_t;

/**
   Get the offset of the halloc structure before a data block
*/
static halloc_t *halloc_from_data( void *data )
{
	return (halloc_t *)(((char *)data) - sizeof( halloc_t ) );
}

/**
   A function that does nothing
*/
static void late_free( void *data)
{
}

#ifdef HALLOC_DEBUG
/**
   Debug function, called at exit when in debug mode. Prints usage
   statistics, like number of allocations and number of internal calls
   to malloc.
*/
static void woot()
{
	if( getpid() == pid )
	{
		debug( 1, L"%d parents, %d children with average child size of %.2f bytes caused %d allocs, average spill of %.2f bytes", 
			   parent_count, child_count, (double)child_size/child_count,
			   parent_count+alloc_count, (double)alloc_spill/(parent_count+alloc_count) );				
	}
}
#endif

void *halloc( void *context, size_t size )
{	
	halloc_t *me, *parent;
	if( context )
	{
		void *res;
		
#ifdef HALLOC_DEBUG
			
		if( !child_count )
		{
			pid = getpid();
			atexit( woot );
		}
		
		child_count++;
		child_size += size;
#endif	
		parent = halloc_from_data( context );
		if( size <= parent->scratch_free )
		{
			res = parent->scratch;
			parent->scratch_free -= size;
			parent->scratch = ((char *)parent->scratch)+size;
		}
		else
		{

#ifdef HALLOC_DEBUG
			alloc_count++;
#endif	
		
			if( parent->scratch_free < HALLOC_SCRAP_SIZE )
			{
#ifdef HALLOC_DEBUG
				alloc_spill += parent->scratch_free;
#endif
				res = calloc( 1, size + HALLOC_BLOCK_SIZE );
				parent->scratch = (char *)res + size;
				parent->scratch_free = HALLOC_BLOCK_SIZE;
			}
			else
			{
				res = calloc( 1, size );
			}
			al_push( &parent->children, &late_free );
			al_push( &parent->children, res );
			
		}
		return res;
	
	}
	else
	{
		me = (halloc_t *)calloc( 1, sizeof(halloc_t) + size + HALLOC_BLOCK_SIZE );
		
		if( !me )
			return 0;
#ifdef HALLOC_DEBUG
		parent_count++;
#endif		
		me->scratch = ((char *)me) + sizeof(halloc_t) + size;
		me->scratch_free = HALLOC_BLOCK_SIZE;
		
		al_init( &me->children );
		return &me->data;
	}
}

void halloc_register_function( void *context, void (*func)(void *), void *data )
{
	halloc_t *me;
	if( !context )
		return;
	
	me = halloc_from_data( context );
	al_push( &me->children, func );
	al_push( &me->children, data );
}

void halloc_free( void *context )
{
	halloc_t *me;
	int i;
	
	if( !context )
		return;

	
	me = halloc_from_data( context );

#ifdef HALLOC_DEBUG
	alloc_spill += me->scratch_free;
#endif
	for( i=0; i<al_get_count(&me->children); i+=2 )
	{
		void (*func)(void *) = (void (*)(void *))al_get( &me->children, i );
		void * data = (void *)al_get( &me->children, i+1 );
		if( func != &late_free )
			func( data );
	}
	for( i=0; i<al_get_count(&me->children); i+=2 )
	{
		void (*func)(void *) = (void (*)(void *))al_get( &me->children, i );
		void * data = (void *)al_get( &me->children, i+1 );
		if( func == &late_free )
			free( data );
	}
	al_destroy( &me->children );
	free(me);
}