/*****************************************************************************
 * $Id: shared_val.cc,v 1.8 2006/07/02 17:05:07 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Test of SharedVal template class.
 *          SharedVal evolved into the singleton SharedValFactory class
 *          so this test has artifacts.
 *//*
 * LEGAL:	COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#define TESTING 1
#include "test.hh"
#include "gfx_shared_val.hh"

/*****************************************************************************
 * Test.
 *****************************************************************************/
struct Counts
{
	int object;				// inc'd by constructor, dec'd by destructor
	int constructor;
	int copyConstructor;
	int destructor;
	int lessThan;
	int num;

	void Clear( void )
	{
		object = constructor = copyConstructor = destructor = lessThan = num = 0;
	}
};

ostream&
operator<<( ostream& strm, const Counts& obj )
{
	strm << "counts: "
         << "object = "		<< obj.object			<< " "
         << "constr = "		<< obj.constructor		<< " "
         << "copyConstr = "	<< obj.copyConstructor	<< " "
         << "destr = "		<< obj.destructor		<< " "
         << "oper<() = "	<< obj.lessThan			<< "\n";
    return strm;
}

static Counts counts;

class Object
{
public:
	Object( void )
	{
		++counts.object;
		++counts.constructor;
		mVal = counts.num++;
		mSig = 0;
	}

	Object( int sig )
	{
		++counts.object;
		++counts.constructor;
		mVal = counts.num++;
		mSig = sig;
	}

	Object( const Object& src )
	{
		++counts.object;
		++counts.copyConstructor;
		mVal = src.mVal;
		mSig = src.mSig;
	}

	~Object()
	{
		--counts.object;
		++counts.destructor;
	}	

	int		mVal;
	int		mSig;
};

bool
operator<( const Object& a, const Object& b )
{
	++counts.lessThan;
	return a.mVal < b.mVal;
}

namespace gfx {
template<> SharedValFactory<Object>* SharedValFactory<Object>::mInstance = NULL;
} // namespace gfx

namespace gfx {

/*****************************************************************************
 * Tests involve ensuring the constructor, destructor, copy constructor, operator<()
 * methods of a user-defined class are called.  These can be called by the STL code
 * and how that is done depends on the STL implementation, so some test cases
 * are flexible or some too closely related to the STL were omitted.
 *
 * When an object is inserted into the map, two more objects
 * should be constructed by SharedVal::Share().  One for the map key,
 * and the other constructed by pointer in the map value pair.
 *
 * Things that are tested:
 * - counts of calling various methods of a shared object, esp. constructors and destructors
 * - operator<() should be called eventually as the map must be sorted.
 * - reference counts
 * - map size
 * - destructor called when reference count reaches zero
 * - SharedVal destructor deletes copies of objects it allocated
 * - neither too many nor too few objects exist after calling Share()/Unshare().
 *****************************************************************************/
bool
TestSharedVal( void )	// friend of SharedVal class
{
	std::map< Object,std::pair<Object*,int> >::iterator itr;

// -------------------------------------------
 {
	counts.Clear();
	Counts prev;

	// ................................
	// First object, first time shared.
	// ................................

	const int sig1 = 0x9988ffee;
	Object* o1 = new Object( sig1 );
	if ( counts.object != 1 ) Fail();			// test Object's counting prior to testing sharing
	if ( counts.constructor != 1 ) Fail();
	if ( counts.num != 1 ) Fail();
	cerr << "Calling Share(), first object... \n";
	cerr << "[pre]  " << counts;
	prev = counts;
	Object* o1shared = ShareVal<Object>( o1 );
	if ( o1->mSig != sig1 ) Fail();
	if ( o1shared->mSig != sig1 ) Fail();
	cerr << "[post] " << counts;
	if ( counts.copyConstructor < 1 ) Fail();	// copy constr should be called at least once
	if ( counts.object < 1 ) Fail();
	if ( counts.constructor != 1 ) Fail();
//	if ( counts.lessThan == 0 ) Fail();			// too STL-specific: g++ STL doesn't call operator<() when map is empty
	// There should be 3 Objects at this point (see above comment for why).
	if ( counts.object != 3 ) Fail();

	SharedValFactory<Object>* shared = SharedValFactory<Object>::GetInstance();

	// Check what was inserted into the map.
	if ( shared->mMap.size() != 1 ) Fail();
	itr = shared->mMap.find( *o1 );
	if ( itr == shared->mMap.end() ) Fail();
	if ( itr->second.second != 1 ) Fail();		// check ref count

	// ................................
	// Share the first object twice.
	// Caching should bypass STL map.
	// ................................

	cerr << "Calling Share(), share first object twice...\n";
	cerr << "[pre]  " << counts;
	prev = counts;
	ShareVal<Object>( o1 );
	cerr << "[post] " << counts;
	if ( counts.object != prev.object ) Fail();						// should be unchanged
	if ( counts.constructor != prev.constructor ) Fail();			// should be unchanged
	if ( counts.destructor != prev.destructor ) Fail();				// should be unchanged
	if ( counts.copyConstructor != prev.copyConstructor ) Fail();	// should be unchanged
	if ( counts.object != 3 ) Fail();
	// Check what was inserted into the map.
	if ( shared->mMap.size() != 1 ) Fail();
	itr = shared->mMap.find( *o1 );
	if ( itr == shared->mMap.end() ) Fail();
	if ( itr->second.second != 2 ) Fail();		// check ref count

	// ................................
	// Create 2nd object, share it.
	// ................................

	cerr << "Calling Share(), 2nd object...\n";
	cerr << "[pre]  " << counts;
	prev = counts;
	const int sig2 = 0xf39c9eda;
	Object* o2 = new Object( sig2 );
	Object* o2shared = ShareVal<Object>( o2 );
	cerr << "[post] " << counts;
	if ( o2->mSig != sig2 ) Fail();
	if ( o2shared->mSig != sig2 ) Fail();
	if ( counts.copyConstructor <= prev.copyConstructor ) Fail();
	if ( counts.object <= prev.object ) Fail();
	if ( counts.constructor <= prev.constructor ) Fail();
	if ( counts.destructor <= prev.destructor ) Fail();
	// There should be 2*3 Objects at this point (see above comment for why).
	if ( counts.object != 2*3 ) Fail();
	// Now operator<() should've been called as there's now more than two items in the map.
	if ( counts.lessThan <= prev.lessThan ) Fail();
	// Check what was inserted into the map.
		// ensure first object wasn't affected:
		itr = shared->mMap.find( *o1 );			// first object should still be there
		if ( itr == shared->mMap.end() ) Fail();
		if ( itr->second.second != 2 ) Fail();	// check ref count (of first object, shouldn't change)
	if ( shared->mMap.size() != 2 ) Fail();
	itr = shared->mMap.find( *o2 );
	if ( itr == shared->mMap.end() ) Fail();
	if ( itr->second.second != 1 ) Fail();		// test ref count

	// ................................
	// Share the first object for the 3rd time.
	// Map should be traversed and the first object found.
	// ................................
	cerr << "Calling Share(), share first object 3rd time...\n";
	cerr << "[pre]  " << counts;
	prev = counts;
	o1shared = ShareVal<Object>( o1 );
	cerr << "[post] " << counts;
	if ( o1shared->mSig != sig1 ) Fail();
	if ( counts.object != prev.object ) Fail();						// should not have changed
	if ( counts.constructor != prev.constructor ) Fail();
	if ( counts.destructor != prev.destructor ) Fail();
	if ( counts.copyConstructor != prev.copyConstructor ) Fail();
	// There should be 2*3 Objects at this point (see above comment for why).
	if ( counts.object != 2*3 ) Fail();
	// Check what was inserted into the map.
	if ( shared->mMap.size() != 2 ) Fail();
	itr = shared->mMap.find( *o1 );
	if ( itr == shared->mMap.end() ) Fail();
	if ( itr->second.second != 3 ) Fail();
		// ensure second object wasn't affected
		itr = shared->mMap.find( *o2 );
		if ( itr == shared->mMap.end() ) Fail();
		if ( itr->second.second != 1 ) Fail();		// test ref count (second object)

	// ................................
	// Unshare the first object.
	// Its reference count should decrement by 1.
	// ................................
	cerr << "Calling Unshare() once on first object...\n";
	itr = shared->mMap.find( *o1 );
	int oldRefCnt = itr->second.second;
	UnshareVal<Object>( o1 );
	int newRefCnt = itr->second.second;
	if ( oldRefCnt != 3 ) Fail();
	if ( newRefCnt != 2 ) Fail();

	// ................................
	// Totally unshare the first object.
	// It should be removed out of the map
	// and copies of it deleted.
	// ................................
	cerr << "Totally unsharing the first object...\n";
	prev = counts;
	UnshareVal<Object>( o1 );	// ref cnt 2 --> 1
	if ( counts.destructor != prev.destructor ) Fail();	// destructor shouldn't b called yet
		itr = shared->mMap.find( *o1 );
		if ( itr->second.second != 1 ) Fail();		// check ref count
		if ( shared->mMap.size() != 2 ) Fail();		// map size shouldn't have changed
	shared->Unshare( o1 );	// ref cnt 1 --> 0
		if ( counts.destructor <= prev.destructor ) Fail();	// destructor now should've been called
		if ( shared->mMap.size() != 1 ) Fail();				// map size should be one less
		itr = shared->mMap.find( *o2 );
		if ( itr == shared->mMap.end() ) Fail();			// second object should still be there

	// Almost done.
	delete o1;		// although unshared and deleted from map, the original survives
	delete o2;

	// Test destroying instance.
	shared->DeleteInstance();
	cerr << "Destroyed SharedVal instance, all of its Objects should be destroyed...\n";
	cerr << counts;
	if ( counts.object != 0 ) Fail();
 }

#if 0
// Obsolete test case before SharedVal became a singleton class.
// -------------------------------------------
 {

    // Test vector<SharedVal>.resize()
	counts.Clear();
	cerr << "Testing vector<SharedPtr>.resize()...\n";

	vector<SharedVal<Object> > vec;
	const int cnt = 50;
	vec.resize( cnt );		// resize() is what really allocates elements
	vec.reserve( cnt );		// test this along the way
	if ( vec.size() != (uint)cnt ) Fail();

	const int sig3 = 8139134;
	Object* orig = new Object( sig3 );

    for ( int i = 0; i < cnt; ++i )
	{
		Object* shared = vec[i].Share( orig );
		if ( shared->mSig != sig3 ) Fail();
	}
 }
#endif

	return true;
}

} // namespace gfx

/*****************************************************************************
 * main.
 *****************************************************************************/
int
main( int argc, char** argv )
{
	GFX_INIT( argv[0] );
	int res = 1;
	if ( TestSharedVal() )
	{
		cerr << "Test PASSED. \n";
		res = 0;
	}
	else
	{
		cerr << "Test FAILED! \n";
		res = 1;
	}
	GFX_CLEANUP();
	exit( res );
}
