#include <ebcl/Alloc.hh>
#include <ebcl/Pointers.hh>
#include <cppunit/extensions/HelperMacros.h>
using namespace ebcl;


class AllocPoolTest : public CppUnit::TestFixture
{
	CPPUNIT_TEST_SUITE( AllocPoolTest );
		CPPUNIT_TEST( testUnused );

		CPPUNIT_TEST( testAllocPartial );
		CPPUNIT_TEST( testAllocFull );
		CPPUNIT_TEST( testAllocNewList );

		CPPUNIT_TEST( testFreePartialToFree );
		CPPUNIT_TEST( testFreePartialToPartial );
		CPPUNIT_TEST( testFreeFullToPartial );

		CPPUNIT_TEST( testFreeLists );
		CPPUNIT_TEST( testFreeFragmented );
		CPPUNIT_TEST( testFreeMaxLists );

		CPPUNIT_TEST( testReallocation );
	CPPUNIT_TEST_SUITE_END( );

public:
	void setUp( ) override;

	void testUnused( );

	void testAllocPartial( );
	void testAllocFull( );
	void testAllocNewList( );

	void testFreePartialToFree( );
	void testFreePartialToPartial( );
	void testFreeFullToPartial( );

	void testFreeLists( );
	void testFreeFragmented( );
	void testFreeMaxLists( );

	void testReallocation( );
};
CPPUNIT_TEST_SUITE_REGISTRATION( AllocPoolTest );

/*----------------------------------------------------------------------------*/

namespace {

class T_Test_
{
    public:
	static size_t counters[ 256 ];
	uint8_t index;

	T_Test_( ) = delete;

	T_Test_( const uint8_t v ) noexcept
		: index( v )
		{ counters[ index ] ++; }

	T_Test_( T_Test_ const& other ) noexcept
		: index( other.index )
		{ counters[ index ] ++; }

	T_Test_( T_Test_&& other ) noexcept
		: index( other.index )
		{ counters[ index ] ++; }

	~T_Test_( )
		{ counters[ index ] --; }

	void* operator new( size_t count ) noexcept;
	void operator delete( void* object ) noexcept;

	static size_t total( ) noexcept
	{
		size_t sum = 0;
		for ( auto i = 0 ; i < 255 ; i ++ ) {
			sum += counters[ i ];
		}
		return sum;
	}
};
M_CLASS_POINTERS( Test_ );

size_t T_Test_::counters[ 256 ];

static constexpr size_t PerList_ = 15;
static constexpr size_t MaxFreeLists_ = 4;
static constexpr size_t MFLObjects_ = PerList_ * MaxFreeLists_;
using T_Pool_ = T_PoolAllocator<
	sizeof( T_Test_ ) , alignof( T_Test_ ) ,
	PerList_ , MaxFreeLists_ >;

static T_OwnPtr< T_Pool_ > Pool_;

void InitPool_( )
{
	Pool_ = NewOwned< T_Pool_ >( );
}

void KillPool_( )
{
	Pool_.clear( );
}

void* T_Test_::operator new(
		const size_t count ) noexcept
{
	assert( Pool_ );
	return Pool_->allocate( count );
}

void T_Test_::operator delete(
		void* object ) noexcept
{
	assert( Pool_ );
	Pool_->free( object );
}

}

#define M_CHECK_LISTS_( T , F , U ) \
	do { \
		CPPUNIT_ASSERT_EQUAL( size_t( T ) , Pool_->countFreeLists( ) ); \
		CPPUNIT_ASSERT_EQUAL( size_t( F ) , Pool_->countPartialLists( ) ); \
		CPPUNIT_ASSERT_EQUAL( size_t( U ) , Pool_->countFullLists( ) ); \
	} while ( 0 )

#define M_CHECK_USAGE_( T , F , U ) \
	do { \
		size_t _t_ , _f_ , _u_; \
		Pool_->getUsage( _t_ , _f_ , _u_ ); \
		CPPUNIT_ASSERT_EQUAL( size_t( T ) , _t_ ); \
		CPPUNIT_ASSERT_EQUAL( size_t( F ) , _f_ ); \
		CPPUNIT_ASSERT_EQUAL( size_t( U ) , _u_ ); \
	} while ( 0 )

#define M_CHECK_COUNT_( N ) \
	CPPUNIT_ASSERT_EQUAL( size_t( N ) , T_Test_::total( ) )

void AllocPoolTest::setUp( )
{
	memset( T_Test_::counters , 0 , sizeof( T_Test_::counters ) );
}

/*----------------------------------------------------------------------------*/

void AllocPoolTest::testUnused( )
{
	InitPool_( );
	M_CHECK_LISTS_( 1 , 0 , 0 );
	M_CHECK_USAGE_( PerList_ , PerList_ , 0 );
	KillPool_( );
	M_CHECK_COUNT_( 0 );
}

/*----------------------------------------------------------------------------*/

void AllocPoolTest::testAllocPartial( )
{
	InitPool_( );
	{	
		OP_Test_ test( NewOwned< T_Test_ >( 0 ) );
		M_CHECK_COUNT_( 1 );
		M_CHECK_LISTS_( 0 , 1 , 0 );
		M_CHECK_USAGE_( PerList_ , PerList_ - 1 , 1 );
	}
	KillPool_( );
	M_CHECK_COUNT_( 0 );
}

void AllocPoolTest::testAllocFull( )
{
	InitPool_( );
	{
		OP_Test_ test[ PerList_ ];
		for ( auto i = 0u ; i < PerList_ ; i ++ ) {
			test[ i ] = NewOwned< T_Test_ >( i );
		}
		M_CHECK_COUNT_( PerList_ );
		M_CHECK_LISTS_(  0 , 0 , 1 );
		M_CHECK_USAGE_( PerList_ , 0 , PerList_ );
	}
	KillPool_( );
	M_CHECK_COUNT_( 0 );
}

void AllocPoolTest::testAllocNewList( )
{
	InitPool_( );
	{
		OP_Test_ test[ PerList_ + 1 ];
		for ( auto i = 0u ; i <= PerList_ ; i ++ ) {
			test[ i ] = NewOwned< T_Test_ >( i );
		}
		M_CHECK_COUNT_( PerList_ + 1 );
		M_CHECK_LISTS_( 0 , 1 , 1 );
		M_CHECK_USAGE_( PerList_ * 2 , PerList_ - 1 , PerList_ + 1 );
	}
	KillPool_( );
	M_CHECK_COUNT_( 0 );
}

/*----------------------------------------------------------------------------*/

void AllocPoolTest::testFreePartialToFree( )
{
	InitPool_( );
	{
		OP_Test_ obj( NewOwned< T_Test_ >( 0 ) );
		obj.clear( );
		M_CHECK_COUNT_( 0 );
		M_CHECK_LISTS_( 1 , 0 , 0 );
		M_CHECK_USAGE_( PerList_ , PerList_ , 0 );
	}
	KillPool_( );
}

void AllocPoolTest::testFreePartialToPartial( )
{
	InitPool_( );
	{
		OP_Test_ persist( NewOwned< T_Test_ >( 0 ) );
		OP_Test_ test( NewOwned< T_Test_ >( 1 ) );
		test.clear( );
		M_CHECK_COUNT_( 1 );
		M_CHECK_LISTS_( 0 , 1 , 0 );
		M_CHECK_USAGE_( PerList_ , PerList_ - 1 , 1 );
	}
	KillPool_( );
}

void AllocPoolTest::testFreeFullToPartial( )
{
	InitPool_( );
	{
		OP_Test_ objs[ PerList_ ];
		for ( auto i = 0u ; i < PerList_ ; i ++ ) {
			objs[ i ] = NewOwned< T_Test_ >( i );
		}
		objs[ PerList_ - 1 ].clear( );
		M_CHECK_COUNT_( PerList_ - 1 );
		M_CHECK_LISTS_( 0 , 1 , 0 );
		M_CHECK_USAGE_( PerList_ , 1 , PerList_ - 1 );
	}
	KillPool_( );
}

/*----------------------------------------------------------------------------*/

void AllocPoolTest::testFreeLists( )
{
	InitPool_( );
	{
		OP_Test_ obj[ MFLObjects_ ];
		for ( auto i = 0u ; i < MFLObjects_ ; i ++ ) {
			obj[ i ] = NewOwned< T_Test_ >( i );
		}
		for ( auto i = 0u ; i < MFLObjects_ ; i ++ ) {
			obj[ i ].clear( );
		}
		M_CHECK_COUNT_( 0 );
		M_CHECK_LISTS_( MaxFreeLists_ , 0 , 0 );
		M_CHECK_USAGE_( MFLObjects_ , MFLObjects_ , 0 );
	}
	KillPool_( );
}

void AllocPoolTest::testFreeFragmented( )
{
	InitPool_( );
	{
		OP_Test_ obj[ MFLObjects_ ];
		for ( auto i = 0u ; i < MFLObjects_ ; i ++ ) {
			obj[ i ] = NewOwned< T_Test_ >( i );
		}
		for ( auto i = 0u ; i < MaxFreeLists_ ; i ++ ) {
			obj[ i * PerList_ ].clear( );
		}
		M_CHECK_COUNT_( MFLObjects_ - MaxFreeLists_ );
		M_CHECK_LISTS_( 0 , MaxFreeLists_ , 0 );
		M_CHECK_USAGE_( MFLObjects_ , MaxFreeLists_ , MFLObjects_ - MaxFreeLists_ );
	}
	KillPool_( );
}

void AllocPoolTest::testFreeMaxLists( )
{
	InitPool_( );
	{
		OP_Test_ obj[ MFLObjects_ + PerList_ ];
		for ( auto i = 0u ; i < MFLObjects_ + PerList_ ; i ++ ) {
			obj[ i ] = NewOwned< T_Test_ >( i );
		}
		for ( auto i = 0u ; i < MFLObjects_ + PerList_ ; i ++ ) {
			obj[ i ].clear( );
		}
		M_CHECK_COUNT_( 0 );
		M_CHECK_LISTS_( MaxFreeLists_ , 0 , 0 );
		M_CHECK_USAGE_( MFLObjects_ , MFLObjects_ , 0 );
	}
	KillPool_( );
}

/*----------------------------------------------------------------------------*/

void AllocPoolTest::testReallocation( )
{
	InitPool_( );
	{
		OP_Test_ obj[ PerList_ ];
		T_Test_* ptr[ PerList_ ];
		for ( auto i = 0u ; i < PerList_ ; i ++ ) {
			obj[ i ] = NewOwned< T_Test_ >( i );
			ptr[ i ] = obj[ i ].get( );
		}
		for ( auto i = 1u ; i < PerList_ ; i += 2 ) {
			obj[ i ].clear( );
		}
		for ( auto i = 1u ; i < PerList_ ; i += 2 ) {
			obj[ i ] = NewOwned< T_Test_ >( i );
			CPPUNIT_ASSERT( obj[ i ].get( ) == ptr[ i ] );
		}
		M_CHECK_LISTS_( 0 , 0 , 1 );
		M_CHECK_USAGE_( PerList_ , 0 , PerList_ );
	}
	KillPool_( );
}