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


class ObjectTableTest : public CppUnit::TestFixture
{
	CPPUNIT_TEST_SUITE( ObjectTableTest );
		CPPUNIT_TEST( testEmpty );

		CPPUNIT_TEST( testAdd );
		CPPUNIT_TEST( testAddMove );
		CPPUNIT_TEST( testAddDuplicate );
		CPPUNIT_TEST( testUpdate );
		CPPUNIT_TEST( testUpdateMove );
		CPPUNIT_TEST( testUpdateMissing );
		CPPUNIT_TEST( testSetNew );
		CPPUNIT_TEST( testSetExisting );
		CPPUNIT_TEST( testSetMove );

		CPPUNIT_TEST( testRemove );
		CPPUNIT_TEST( testRemoveMissing );

		CPPUNIT_TEST( testContains );
		CPPUNIT_TEST( testContainsMissing );
		CPPUNIT_TEST( testIndexOf );
		CPPUNIT_TEST( testIndexOfMissing );
		CPPUNIT_TEST( testGet );
		CPPUNIT_TEST( testGetMissing );

		CPPUNIT_TEST( testClear );
		CPPUNIT_TEST( testFree );

		CPPUNIT_TEST( testCopyCons );
		CPPUNIT_TEST( testCopyAss );

		CPPUNIT_TEST( testMoveCons );
		CPPUNIT_TEST( testMoveAss );

		CPPUNIT_TEST( testSwap );
	CPPUNIT_TEST_SUITE_END( );

	static constexpr uint32_t C_SIZE_ = 16;

   public:
	void testEmpty( );

	void testAdd( );
	void testAddMove( );
	void testAddDuplicate( );
	void testUpdate( );
	void testUpdateMove( );
	void testUpdateMissing( );
	void testSetNew( );
	void testSetMove( );
	void testSetExisting( );

	void testRemove( );
	void testRemoveMissing( );

	void testContains( );
	void testContainsMissing( );
	void testIndexOf( );
	void testIndexOfMissing( );
	void testGet( );
	void testGetMissing( );

	void testCopyCons( );
	void testCopyAss( );

	void testMoveCons( );
	void testMoveAss( );

	void testClear( );
	void testFree( );

	void testSwap( );
};
CPPUNIT_TEST_SUITE_REGISTRATION( ObjectTableTest );

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

namespace {

class T_Test_
{
    private:
	uint32_t key_;
	uint32_t value_;

    public:
	T_Test_( ) : key_( 0 ) , value_( 0 ) { }
	T_Test_( uint32_t k ) : key_( k ) , value_( k * 2 ) { }
	T_Test_( uint32_t k , uint32_t v ) : key_( k ) , value_( v ) { }

	T_Test_( T_Test_ const& other ) : T_Test_( other.key_ , other.value_ ) { }
	T_Test_( T_Test_&& other ) : T_Test_( ) { swap( *this , other ); }

	T_Test_& operator =( T_Test_ const& other ) = default;
	T_Test_& operator =( T_Test_&& other ) noexcept { swap( *this , other ); return *this; }

	friend void swap( T_Test_& lhs , T_Test_& rhs )
	{
		using std::swap;
		swap( lhs.key_ , rhs.key_ );
		swap( lhs.value_ , rhs.value_ );
	}

	uint32_t key( ) const { return key_; }
	uint32_t value( ) const { return value_; }
	void value( uint32_t v ) { value_ = v; }
};

uint32_t KeyGetter_( T_Test_ const& test )
{
	return test.key( );
}

}

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

void ObjectTableTest::testEmpty( )
{
	T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.keys( ).size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.values( ).size( ) );
}

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

void ObjectTableTest::testAdd( )
{
	T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ );
	T_Test_ test( 123 );
	bool ok( table.add( test ) );
	CPPUNIT_ASSERT( ok );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 246 ) , table.values( )[ 0 ].value( ) );
}

void ObjectTableTest::testAddMove( )
{
	T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ );
	T_Test_ test( 123 );
	bool ok( table.add( std::move( test ) ) );
	CPPUNIT_ASSERT( ok );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.key( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 246 ) , table.values( )[ 0 ].value( ) );
}

void ObjectTableTest::testAddDuplicate( )
{
	T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ );
	table.add( T_Test_( 123 ) );
	bool ok( table.add( T_Test_( 123 , 456 ) ) );
	CPPUNIT_ASSERT( !ok );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 246 ) , table.values( )[ 0 ].value( ) );
}

void ObjectTableTest::testUpdate( )
{
	T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ );
	table.add( T_Test_( 123 , 456 ) );
	T_Test_ test( 123 );
	bool ok( table.update( test ) );
	CPPUNIT_ASSERT( ok );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 246 ) , table.values( )[ 0 ].value( ) );
}

void ObjectTableTest::testUpdateMove( )
{
	T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ );
	table.add( T_Test_( 123 , 456 ) );
	T_Test_ test( 123 );
	bool ok( table.update( std::move( test ) ) );
	CPPUNIT_ASSERT( ok );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , test.value( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 246 ) , table.values( )[ 0 ].value( ) );
}

void ObjectTableTest::testUpdateMissing( )
{
	T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ );
	bool ok( table.update( T_Test_( 123 ) ) );
	CPPUNIT_ASSERT( !ok );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.keys( ).size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.values( ).size( ) );
}

void ObjectTableTest::testSetNew( )
{
	T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ );
	T_Test_ test( 123 );
	table.set( test );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 246 ) , table.values( )[ 0 ].value( ) );
}

void ObjectTableTest::testSetMove( )
{
	T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ );
	T_Test_ test( 123 );
	table.set( std::move( test ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.key( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 246 ) , table.values( )[ 0 ].value( ) );
}

void ObjectTableTest::testSetExisting( )
{
	T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ );
	table.add( T_Test_( 123 , 456 ) );
	table.set( T_Test_( 123 ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 246 ) , table.values( )[ 0 ].value( ) );
}

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

void ObjectTableTest::testRemove( )
{
	T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ );
	table.set( T_Test_( 123 ) );
	table.set( T_Test_( 456 ) );
	bool ok( table.remove( 456 ) );
	CPPUNIT_ASSERT( ok );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 246 ) , table.values( )[ 0 ].value( ) );
}

void ObjectTableTest::testRemoveMissing( )
{
	T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ );
	table.set( T_Test_( 123 ) );
	bool ok( table.remove( 456 ) );
	CPPUNIT_ASSERT( !ok );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 246 ) , table.values( )[ 0 ].value( ) );
}

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

void ObjectTableTest::testContains( )
{
	T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ );
	table.set( T_Test_( 123 ) );
	CPPUNIT_ASSERT( table.contains( 123 ) );
}

void ObjectTableTest::testContainsMissing( )
{
	T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ );
	table.set( T_Test_( 123 ) );
	CPPUNIT_ASSERT( !table.contains( 456 ) );
}

void ObjectTableTest::testIndexOf( )
{
	T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ );
	for ( uint32_t i = 0 ; i < 124 ; i += 2 ) {
		table.set( T_Test_( i ) );
	}
	for ( uint32_t i = 1 ; i < 124 ; i += 2 ) {
		table.set( T_Test_( i ) );
	}

	const uint32_t index( table.indexOf( 57 ) );
	CPPUNIT_ASSERT( index != T_HashIndex::INVALID_INDEX );
	CPPUNIT_ASSERT( index < table.size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 57 ) , table[ index ].key( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 114 ) , table[ index ].value( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 114 ) , table.values( )[ index ].value( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 57 ) , table.keys( )[ index ] );
}

void ObjectTableTest::testIndexOfMissing( )
{
	T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ );
	const uint32_t index( table.indexOf( 57 ) );
	CPPUNIT_ASSERT( index == T_HashIndex::INVALID_INDEX );
}

void ObjectTableTest::testGet( )
{
	T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ );
	for ( uint32_t i = 0 ; i < 124 ; i += 2 ) {
		table.set( T_Test_( i ) );
	}
	for ( uint32_t i = 1 ; i < 124 ; i += 2 ) {
		table.set( T_Test_( i ) );
	}

	const T_Test_* ptr( table.get( 57 ) );
	CPPUNIT_ASSERT( ptr != nullptr );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 57 ) , ptr->key( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 114 ) , ptr->value( ) );
}

void ObjectTableTest::testGetMissing( )
{
	T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ );
	const T_Test_* ptr( table.get( 57 ) );
	CPPUNIT_ASSERT( ptr == nullptr );
}

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

void ObjectTableTest::testCopyCons( )
{
	T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ );
	for ( uint32_t i = 0 ; i < 124 ; i += 2 ) {
		table.set( T_Test_( i ) );
	}
	for ( uint32_t i = 1 ; i < 124 ; i += 2 ) {
		table.set( T_Test_( i ) );
	}
	const auto sz( table.size( ) );

	T_ObjectTable< uint32_t , T_Test_ > copy( table );

	CPPUNIT_ASSERT_EQUAL( sz , table.size( ) );
	CPPUNIT_ASSERT_EQUAL( sz , copy.size( ) );
	auto const& k( table.keys( ) );
	for ( uint32_t i = 0 ; i < sz ; i ++ ) {
		CPPUNIT_ASSERT( copy.contains( k[ i ] ) );

		T_Test_ const& oValue( table.values( )[ i ] );
		T_Test_ const* const cPtr( copy.get( k[ i ] ) );
		CPPUNIT_ASSERT( cPtr != nullptr );
		CPPUNIT_ASSERT_EQUAL( oValue.key( ) , cPtr->key( ) );
		CPPUNIT_ASSERT_EQUAL( oValue.value( ) , cPtr->value( ) );
	}
}

void ObjectTableTest::testCopyAss( )
{
	T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ );
	for ( uint32_t i = 0 ; i < 124 ; i += 2 ) {
		table.set( T_Test_( i ) );
	}
	for ( uint32_t i = 1 ; i < 124 ; i += 2 ) {
		table.set( T_Test_( i ) );
	}
	const auto sz( table.size( ) );

	T_ObjectTable< uint32_t , T_Test_ > copy( []( T_Test_ const& v ) {
		return v.value( );
	} );
	copy = table;

	CPPUNIT_ASSERT_EQUAL( sz , table.size( ) );
	CPPUNIT_ASSERT_EQUAL( sz , copy.size( ) );
	auto const& k( table.keys( ) );
	for ( uint32_t i = 0 ; i < sz ; i ++ ) {
		CPPUNIT_ASSERT( copy.contains( k[ i ] ) );

		T_Test_ const& oValue( table.values( )[ i ] );
		T_Test_ const* const cPtr( copy.get( k[ i ] ) );
		CPPUNIT_ASSERT( cPtr != nullptr );
		CPPUNIT_ASSERT_EQUAL( oValue.key( ) , cPtr->key( ) );
		CPPUNIT_ASSERT_EQUAL( oValue.value( ) , cPtr->value( ) );
	}

	T_Test_ v( 543 , 210 );
	copy.set( v );
	CPPUNIT_ASSERT( copy.contains( 543 ) );
	CPPUNIT_ASSERT( !table.contains( 543 ) );
}

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

void ObjectTableTest::testMoveCons( )
{
	T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ );
	for ( uint32_t i = 0 ; i < 124 ; i += 2 ) {
		table.set( T_Test_( i ) );
	}
	for ( uint32_t i = 1 ; i < 124 ; i += 2 ) {
		table.set( T_Test_( i ) );
	}
	const auto sz( table.size( ) );
	T_Array< uint32_t > k( table.keys( ) );

	T_ObjectTable< uint32_t , T_Test_ > copy( std::move( table ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.size( ) );
	CPPUNIT_ASSERT_EQUAL( sz , copy.size( ) );
	for ( uint32_t i = 0 ; i < sz ; i ++ ) {
		CPPUNIT_ASSERT( copy.contains( k[ i ] ) );
		CPPUNIT_ASSERT( !table.contains( k[ i ] ) );

		T_Test_ const* const cPtr( copy.get( k[ i ] ) );
		CPPUNIT_ASSERT( cPtr != nullptr );
		CPPUNIT_ASSERT_EQUAL( k[ i ] , cPtr->key( ) );
		CPPUNIT_ASSERT_EQUAL( k[ i ] * 2 , cPtr->value( ) );
	}
}

void ObjectTableTest::testMoveAss( )
{
	T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ );
	for ( uint32_t i = 0 ; i < 124 ; i += 2 ) {
		table.set( T_Test_( i ) );
	}
	for ( uint32_t i = 1 ; i < 124 ; i += 2 ) {
		table.set( T_Test_( i ) );
	}
	const auto sz( table.size( ) );
	T_Array< uint32_t > k( table.keys( ) );

	T_ObjectTable< uint32_t , T_Test_ > copy( []( T_Test_ const& v ) {
		return v.value( );
	} );
	copy = std::move( table );

	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.size( ) );
	CPPUNIT_ASSERT_EQUAL( sz , copy.size( ) );
	for ( uint32_t i = 0 ; i < sz ; i ++ ) {
		CPPUNIT_ASSERT( copy.contains( k[ i ] ) );
		CPPUNIT_ASSERT( !table.contains( k[ i ] ) );

		T_Test_ const* const cPtr( copy.get( k[ i ] ) );
		CPPUNIT_ASSERT( cPtr != nullptr );
		CPPUNIT_ASSERT_EQUAL( k[ i ] , cPtr->key( ) );
		CPPUNIT_ASSERT_EQUAL( k[ i ] * 2 , cPtr->value( ) );
	}

	copy.add( T_Test_( 456 , 789 ) );
	CPPUNIT_ASSERT( copy.contains( 456 ) );
}

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

void ObjectTableTest::testClear( )
{
	T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ );
	for ( uint32_t i = 0 ; i < 124 ; i ++ ) {
		table.set( T_Test_( i ) );
	}
	table.clear( );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.size( ) );
}

void ObjectTableTest::testFree( )
{
	T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ );
	for ( uint32_t i = 0 ; i < 124 ; i ++ ) {
		table.set( T_Test_( i ) );
	}
	table.free( );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.size( ) );
}

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

void ObjectTableTest::testSwap( )
{
	T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ );
	T_ObjectTable< uint32_t , T_Test_ > table2( KeyGetter_ );
	for ( uint32_t i = 0 ; i < 12 ; i ++ ) {
		table.set( T_Test_( i , i * 2 ) );
		table2.set( T_Test_( 20 + i , i ) );
	}
	table.set( T_Test_( 123 , 456 ) );

	swap( table , table2 );

	CPPUNIT_ASSERT_EQUAL( uint32_t( 12 ) , table.size( ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 13 ) , table2.size( ) );
	for ( uint32_t i = 0 ; i < 12 ; i ++ ) {
		CPPUNIT_ASSERT( !table.contains( i ) );
		CPPUNIT_ASSERT( table.contains( 20 + i ) );
		CPPUNIT_ASSERT_EQUAL( i , table.get( 20 + i )->value( ) );

		CPPUNIT_ASSERT( !table2.contains( 20 + i ) );
		CPPUNIT_ASSERT( table2.contains( i ) );
		CPPUNIT_ASSERT_EQUAL( i * 2 , table2.get( i )->value( ) );
	}
	CPPUNIT_ASSERT( table2.contains( 123 ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , table2.get( 123 )->value( ) );
}