/* -*- mode: C++; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: t -*- */
// pool - A pooled memory allocator

// Copyright (C) 2005-2007 Editopia Inc.
// Released under the LGPL.
// For more information, see http://www.openlibraries.org.

#include "pool.hpp"
#include "log.hpp"

// std
#include <cstring>
#include <cassert>
#include <sstream>

using namespace std;
using namespace olib::openpluginlib;

// Declaration of mutex
OPENPLUGINLIB_DECLSPEC boost::mutex pool::mutex_;

const unsigned int POOL_COUNT = 31;

// Constructor creates 31 pools sized at 2^i
pool::pool( )
{
}

// Destroys pools
pool::~pool( )
{
	for( int i = 0; i < POOL_COUNT; i ++ )
	{
		typedef std::vector< unsigned char * >::iterator iterator;
		for( iterator I = pools_[ i ].begin( ); I != pools_[ i ].end( ); ++I )
			delete[ ] *I;
	}
}

// Singleton accessor - ensures pools are destroyed on exit
pool *pool::get_instance( )
{
	static pool *instance = NULL;
	boost::mutex::scoped_lock scoped_lock( mutex_ );
	if ( instance == NULL )
	{
		instance = new pool( );
		atexit( destroy );
	}
	return instance;
}

// Singleton destructor

static const char PREAMBLE_TEXT[] = { 'p', 'o', 'o', 'l' };

template < typename T > 
int array_size( T& a ) { return sizeof( a )/sizeof( a[0] ); }

void pool::destroy( ) 
{ 
	delete get_instance( ); 
}

int preamble_size()
{
	return array_size( PREAMBLE_TEXT ) + sizeof( int );
}

void write_preamble( unsigned char* p, int index )
{
	int s = array_size( PREAMBLE_TEXT );
	::memcpy( p, PREAMBLE_TEXT, s );
	*(int*)(p+s) = index;
}

bool is_valid_preamble( unsigned char* p )
{
	int s = array_size( PREAMBLE_TEXT );
	return (::memcmp( p, PREAMBLE_TEXT, s ) == 0) && ( *(int*)(p+s) < POOL_COUNT ) ; 
}

int get_index( unsigned char* p )
{
	if ( !is_valid_preamble( p ) )
	{
		WARNING_LOG << "invalid preamble found";
		return -1;
	}

	return *(int *)(p+array_size( PREAMBLE_TEXT ));
}

// Allocate memory >= size
unsigned char *pool::malloc( int size )
{
#ifdef HAVE_POOL
	unsigned char *result = NULL;

	if ( size > 0 )
	{
		// Account for index storage
		size += preamble_size();

		pool *self = get_instance( );
		int index = 1;
		while ( ( 1 << index ) < size ) index ++;

		boost::mutex::scoped_lock scoped_lock( mutex_ );

		int used = index;
		while ( used < POOL_COUNT && self->pools_[ used ].size( ) == 0 ) used ++;
		if ( used == POOL_COUNT ) used = index;

		if ( self->pools_[ used ].size( ) == 0 )
		{
			result = new unsigned char[ 1 << used ];
			if ( result != 0 )
			{
				write_preamble( result, used );
				result += preamble_size();
			}
		}
		else
		{
			result = *( self->pools_[ used ].end( ) - 1 );
			write_preamble( result, used );
			result += preamble_size();
			self->pools_[ used ].pop_back( );
		}
	}

	return result;
#else
	return static_cast< unsigned char * >( std::malloc( size ) );
#endif
}

// Reallocate a chunk to given size
unsigned char *pool::realloc( unsigned char *chunk, int size )
{
#ifdef HAVE_POOL
	unsigned char *result = chunk;

	if ( chunk != 0 )
	{
		int old_index = get_index( chunk - preamble_size() );
		int index = 1;
		while ( ( 1 << index ) < ( size + preamble_size() ) ) index ++;
		if ( old_index < index )
		{
			result = pool::malloc( size );
			memcpy( result, chunk, int( 1 << old_index ) );
			pool::free( chunk );
		}
	}
	else
	{
		result = pool::malloc( size );
	}

	return result;
#else
	return static_cast< unsigned char * >( std::realloc( chunk, size ) );
#endif
}

// Free memory 
void pool::free( unsigned char *chunk )
{
#ifdef HAVE_POOL
	if ( chunk != 0 )
	{
		pool *self = get_instance( );
		
		chunk -= preamble_size();
		if ( !is_valid_preamble( chunk ) )
		{
			WARNING_LOG << "possibly trying to recycle memory that wasn't allocated by pool";
			abort();
			return;
		}

		int index = get_index( chunk );
		boost::mutex::scoped_lock scoped_lock( mutex_ );
		self->pools_[ index ].push_back( chunk );
	}
#else
	std::free( chunk );
#endif
}

void pool::dump_state()
{
#ifdef HAVE_POOL
	std::stringstream ss;
	
	ss << "\npool counts:\n";
	for ( unsigned int i=0; i<POOL_COUNT; ++i )
		ss << " [" << i << "]: " << get_instance()->pools_[i].size() << "\n";
	
	DEBUG_LOG << ss.str();
#endif
}
