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

template< bool Atomic >
class RefCountTest : public CppUnit::TestFixture
{
    public:
	class T_Test : public T_ReferenceCountedClass< Atomic > \
	{
	    public:
		static uint32_t cNew;
		static uint32_t cDel;
		T_Test( ) noexcept { cNew ++; }
		virtual ~T_Test( ) noexcept { cDel ++; }
	};
	using P_Test = T_RcPtr< T_Test >;

	class T_Child : public T_Test { };
	using P_Child = T_RcPtr< T_Child >;

    private:
	CPPUNIT_TEST_SUITE( RefCountTest );
		CPPUNIT_TEST( testStackInstance );

		CPPUNIT_TEST( testEmpty );
		CPPUNIT_TEST( testConstruct );

		CPPUNIT_TEST( testCopyCons );
		CPPUNIT_TEST( testCopyConsToBase );
		CPPUNIT_TEST( testCopyConsToChildOk );
		CPPUNIT_TEST( testCopyConsToChildBad );

		CPPUNIT_TEST( testCopyAss );
		CPPUNIT_TEST( testCopyAssToBase );
		CPPUNIT_TEST( testCopyAssToChildOk );
		CPPUNIT_TEST( testCopyAssToChildBad );

		CPPUNIT_TEST( testMoveCons );
		CPPUNIT_TEST( testMoveConsToBase );
		CPPUNIT_TEST( testMoveConsToChildOk );
		CPPUNIT_TEST( testMoveConsToChildBad );

		CPPUNIT_TEST( testMoveAss );
		CPPUNIT_TEST( testMoveAssToBase );
		CPPUNIT_TEST( testMoveAssToChildOk );
		CPPUNIT_TEST( testMoveAssToChildBad );

		CPPUNIT_TEST( testSwap );
	CPPUNIT_TEST_SUITE_END( );

    public:
	void setUp( ) override;

	void testStackInstance( );

	void testEmpty( );
	void testConstruct( );

	void testCopyCons( );
	void testCopyConsToBase( );
	void testCopyConsToChildOk( );
	void testCopyConsToChildBad( );

	void testCopyAss( );
	void testCopyAssToBase( );
	void testCopyAssToChildOk( );
	void testCopyAssToChildBad( );

	void testMoveCons( );
	void testMoveConsToBase( );
	void testMoveConsToChildOk( );
	void testMoveConsToChildBad( );

	void testMoveAss( );
	void testMoveAssToBase( );
	void testMoveAssToChildOk( );
	void testMoveAssToChildBad( );

	void testSwap( );
};
CPPUNIT_TEST_SUITE_REGISTRATION( RefCountTest< true > );
CPPUNIT_TEST_SUITE_REGISTRATION( RefCountTest< false > );

template<> uint32_t RefCountTest< true >::T_Test::cNew = 0;
template<> uint32_t RefCountTest< true >::T_Test::cDel = 0;
template<> uint32_t RefCountTest< false >::T_Test::cNew = 0;
template<> uint32_t RefCountTest< false >::T_Test::cDel = 0;

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

template< bool A >
void RefCountTest< A >::setUp( )
{
	T_Test::cNew = T_Test::cDel = 0;
}

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

template< bool A >
void RefCountTest< A >::testStackInstance( )
{
	{
		T_Test t;
		CPPUNIT_ASSERT_EQUAL( 1u , T_Test::cNew );
		CPPUNIT_ASSERT_EQUAL( 0u , t.getReferenceCount( ) );
	}
	CPPUNIT_ASSERT_EQUAL( 1u , T_Test::cDel );
}

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

template< bool A >
void RefCountTest< A >::testEmpty( )
{
	{
		P_Test p1;
		CPPUNIT_ASSERT_EQUAL( 0u , T_Test::cNew );
		CPPUNIT_ASSERT_EQUAL( false , bool{ p1 } );
		CPPUNIT_ASSERT_EQUAL( true , !p1 );
		CPPUNIT_ASSERT_EQUAL( (T_Test*) nullptr , p1.get( ) );
		CPPUNIT_ASSERT_EQUAL( (T_Test*) nullptr , (T_Test*) p1 );
	}
	CPPUNIT_ASSERT_EQUAL( 0u , T_Test::cDel );
}

template< bool A >
void RefCountTest< A >::testConstruct( )
{
	{
		P_Test p1{ P_Test::New( ) };
		CPPUNIT_ASSERT_EQUAL( 1u , T_Test::cNew );
		CPPUNIT_ASSERT_EQUAL( true , bool{ p1 } );
		CPPUNIT_ASSERT_EQUAL( false , !p1 );
		CPPUNIT_ASSERT( (T_Test*) nullptr != p1.get( ) );
		CPPUNIT_ASSERT( (T_Test*) nullptr != (T_Test*) p1 );
		CPPUNIT_ASSERT_EQUAL( 1u , p1->getReferenceCount( ) );
		CPPUNIT_ASSERT_EQUAL( 1u , (*p1).getReferenceCount( ) );
	}
	CPPUNIT_ASSERT_EQUAL( 1u , T_Test::cDel );
}

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

template< bool A >
void RefCountTest< A >::testCopyCons( )
{
	{
		P_Test p1{ P_Test::New( ) };
		{
			P_Test c1{ p1 };
			CPPUNIT_ASSERT_EQUAL( 1u , T_Test::cNew );
			CPPUNIT_ASSERT( c1 );
			CPPUNIT_ASSERT( p1.get( ) == c1.get( ) );
			CPPUNIT_ASSERT_EQUAL( 2u , p1->getReferenceCount( ) );
		}
		CPPUNIT_ASSERT_EQUAL( 0u , T_Test::cDel );
		CPPUNIT_ASSERT_EQUAL( 1u , p1->getReferenceCount( ) );
	}
	CPPUNIT_ASSERT_EQUAL( 1u , T_Test::cDel );
}

template< bool A >
void RefCountTest< A >::testCopyConsToBase( )
{
	P_Child p1{ P_Child::New( ) };
	P_Test p2{ p1 };

	CPPUNIT_ASSERT( bool( p2 ) );
	CPPUNIT_ASSERT( p1.get( ) == p2.get( ) );
	CPPUNIT_ASSERT_EQUAL( 2u , p1->getReferenceCount( ) );
}

template< bool A >
void RefCountTest< A >::testCopyConsToChildOk( )
{
	P_Test p1{ P_Test::FromRaw( new T_Child( ) ) };
	P_Child p2{ p1 };

	CPPUNIT_ASSERT( bool( p2 ) );
	CPPUNIT_ASSERT( p1.get( ) == p2.get( ) );
	CPPUNIT_ASSERT_EQUAL( 2u , p1->getReferenceCount( ) );
}

template< bool A >
void RefCountTest< A >::testCopyConsToChildBad( )
{
	P_Test p1{ P_Test::New( ) };
	CPPUNIT_ASSERT_THROW( P_Child p2{ p1 } , std::bad_cast );
	CPPUNIT_ASSERT_EQUAL( 1u , p1->getReferenceCount( ) );
}

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

template< bool A >
void RefCountTest< A >::testCopyAss( )
{
	{
		P_Test p1{ P_Test::New( ) };
		{
			P_Test c1;
			CPPUNIT_ASSERT( !c1 );
			c1 = p1;
			CPPUNIT_ASSERT_EQUAL( 1u , T_Test::cNew );
			CPPUNIT_ASSERT( c1 );
			CPPUNIT_ASSERT( p1.get( ) == c1.get( ) );
			CPPUNIT_ASSERT_EQUAL( 2u , p1->getReferenceCount( ) );
		}
		CPPUNIT_ASSERT_EQUAL( 0u , T_Test::cDel );
		CPPUNIT_ASSERT_EQUAL( 1u , p1->getReferenceCount( ) );
	}
	CPPUNIT_ASSERT_EQUAL( 1u , T_Test::cDel );
}

template< bool A >
void RefCountTest< A >::testCopyAssToBase( )
{
	P_Child p1{ P_Child::New( ) };
	P_Test p2;
	p2 = p1;

	CPPUNIT_ASSERT( bool( p2 ) );
	CPPUNIT_ASSERT( p1.get( ) == p2.get( ) );
	CPPUNIT_ASSERT_EQUAL( 2u , p1->getReferenceCount( ) );
}

template< bool A >
void RefCountTest< A >::testCopyAssToChildOk( )
{
	P_Test p1{ P_Test::FromRaw( new T_Child( ) ) };
	P_Child p2;
	p2 = p1;

	CPPUNIT_ASSERT( bool( p2 ) );
	CPPUNIT_ASSERT( p1.get( ) == p2.get( ) );
	CPPUNIT_ASSERT_EQUAL( 2u , p1->getReferenceCount( ) );
}

template< bool A >
void RefCountTest< A >::testCopyAssToChildBad( )
{
	P_Test p1{ P_Test::New( ) };
	P_Child p2;
	CPPUNIT_ASSERT_THROW( p2 = p1 , std::bad_cast );
	CPPUNIT_ASSERT_EQUAL( 1u , p1->getReferenceCount( ) );
}

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

template< bool A >
void RefCountTest< A >::testMoveCons( )
{
	P_Test p1{ P_Test::New( ) };
	{
		P_Test c1{ std::move( p1 ) };
		CPPUNIT_ASSERT_EQUAL( 1u , T_Test::cNew );
		CPPUNIT_ASSERT_EQUAL( 0u , T_Test::cDel );
		CPPUNIT_ASSERT( c1 );
		CPPUNIT_ASSERT( !p1 );
		CPPUNIT_ASSERT_EQUAL( 1u , c1->getReferenceCount( ) );
	}
	CPPUNIT_ASSERT_EQUAL( 1u , T_Test::cDel );
}

template< bool A >
void RefCountTest< A >::testMoveConsToBase( )
{
	P_Child p1{ P_Child::New( ) };
	P_Test p2{ std::move( p1 ) };

	CPPUNIT_ASSERT( bool( p2 ) );
	CPPUNIT_ASSERT( !p1 );
	CPPUNIT_ASSERT_EQUAL( 1u , p2->getReferenceCount( ) );
}

template< bool A >
void RefCountTest< A >::testMoveConsToChildOk( )
{
	P_Test p1{ P_Test::FromRaw( new T_Child( ) ) };
	P_Child p2{ std::move( p1 ) };

	CPPUNIT_ASSERT( bool( p2 ) );
	CPPUNIT_ASSERT( !p1 );
	CPPUNIT_ASSERT_EQUAL( 1u , p2->getReferenceCount( ) );
}

template< bool A >
void RefCountTest< A >::testMoveConsToChildBad( )
{
	P_Test p1{ P_Test::New( ) };
	CPPUNIT_ASSERT_THROW( P_Child p2{ std::move( p1 ) } , std::bad_cast );
	CPPUNIT_ASSERT( p1 );
	CPPUNIT_ASSERT_EQUAL( 1u , p1->getReferenceCount( ) );
}

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

template< bool A >
void RefCountTest< A >::testMoveAss( )
{
	P_Test p1{ P_Test::New( ) };
	{
		P_Test c1;
		CPPUNIT_ASSERT( !c1 );
		c1 = std::move( p1 );
		CPPUNIT_ASSERT_EQUAL( 1u , T_Test::cNew );
		CPPUNIT_ASSERT_EQUAL( 0u , T_Test::cDel );
		CPPUNIT_ASSERT( c1 );
		CPPUNIT_ASSERT( !p1 );
		CPPUNIT_ASSERT_EQUAL( 1u , c1->getReferenceCount( ) );
	}
	CPPUNIT_ASSERT_EQUAL( 1u , T_Test::cDel );
}

template< bool A >
void RefCountTest< A >::testMoveAssToBase( )
{
	P_Child p1{ P_Child::New( ) };
	P_Test p2;
	p2 = std::move( p1 );

	CPPUNIT_ASSERT( p2 );
	CPPUNIT_ASSERT( !p1 );
	CPPUNIT_ASSERT_EQUAL( 1u , p2->getReferenceCount( ) );
}

template< bool A >
void RefCountTest< A >::testMoveAssToChildOk( )
{
	P_Test p1{ P_Test::FromRaw( new T_Child( ) ) };
	P_Child p2;
	p2 = std::move( p1 );

	CPPUNIT_ASSERT( p2 );
	CPPUNIT_ASSERT( !p1 );
	CPPUNIT_ASSERT_EQUAL( 1u , p2->getReferenceCount( ) );
}

template< bool A >
void RefCountTest< A >::testMoveAssToChildBad( )
{
	P_Test p1{ P_Test::New( ) };
	P_Child p2;
	CPPUNIT_ASSERT_THROW( p2 = std::move( p1 ) , std::bad_cast );
	CPPUNIT_ASSERT( p1 );
	CPPUNIT_ASSERT_EQUAL( 1u , p1->getReferenceCount( ) );
}

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

template< bool A >
void RefCountTest< A >::testSwap( )
{
	{
		P_Test p1{ P_Test::New( ) };
		P_Test p2{ };
		CPPUNIT_ASSERT_EQUAL( 1u , T_Test::cNew );
		CPPUNIT_ASSERT_EQUAL( 0u , T_Test::cDel );
		CPPUNIT_ASSERT( p1 );
		CPPUNIT_ASSERT( !p2 );
		CPPUNIT_ASSERT_EQUAL( 1u , p1->getReferenceCount( ) );

		p1.swap( p2 );
		CPPUNIT_ASSERT( !p1 );
		CPPUNIT_ASSERT( p2 );
		CPPUNIT_ASSERT_EQUAL( 1u , p2->getReferenceCount( ) );

		p1.swap( p2 );
		CPPUNIT_ASSERT( p1 );
		CPPUNIT_ASSERT( !p2 );
		CPPUNIT_ASSERT_EQUAL( 1u , p1->getReferenceCount( ) );
	}
	CPPUNIT_ASSERT_EQUAL( 1u , T_Test::cDel );
}