/*****************************************************************************
 * $Id: dlist.cc,v 1.3 2006/11/25 23:16:31 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Test of Dlist template class.
 *//*
 * 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_list.hh"

static int sInstances = 0;

class Class
{
public:
            Class( void )     : mVal(0)  { ++sInstances; }
			Class( int val ) : mVal(val) { ++sInstances; }
            ~Class() { --sInstances; }
	int		mVal;
};

/*****************************************************************************
 * Print list.
 *****************************************************************************/
bool
PrintList( Dlist<Class>* dlist )
{
	int cnt = 0;
	for ( const Dlink<Class>* dlink = dlist->ForStart();
          dlist->ForTest(dlink);
          dlist->ForNext(dlink) )
	{
		if ( dlink == NULL )
	        { cerr << "error ! dlink      cnt=" << cnt << "dlink=" << dlink << endl; return false; }
		if ( dlink->mObj == NULL )
	        { cerr << "error ! dlink->obj cnt=" << cnt << "dlink=" << dlink << endl; return false; }

		cerr << "item #" << cnt << " mVal=" << dlink->mObj->mVal << endl;
		++cnt;
	}
	if ( !cnt ) cerr << "(empty list) \n";
	return true;
}

/*****************************************************************************
 * Test
 *****************************************************************************/
namespace gfx {
bool
TestList( void )
{
 //-----------------------------------------------------------
 {
	Dlist<Class>* dlist = new Dlist<Class>;  // implies SCALAR/FREE
	Class* p;
	Dlink<Class>* dlink;
	Dlink<Class>* dlink2;
	Dlink<Class>* dlink3;
	Dlink<Class>* dlink4;

	cerr << "-- should print empty list -- \n";
	if ( ! PrintList( dlist ) ) return false;
	cerr << endl;

	cerr << "-- testing Append() once (should print 101) -- \n";
	p = new Class( 101 );
	dlink = dlist->Append( p );
	if ( ! PrintList( dlist ) ) return false;
	cerr << endl;

	cerr << "-- testing Unlink() once (should print empty list) -- \n";
	dlist->Unlink( dlink );
	if ( ! PrintList( dlist ) ) return false;
	cerr << endl;

	cerr << "-- testing Append() 3x (should print 102,103,104) -- \n";
	dlink2 = dlist->Append( new Class( 102 ) );
	dlink3 = dlist->Append( new Class( 103 ) );
	dlink4 = dlist->Append( new Class( 104 ) );
	if ( ! PrintList( dlist ) ) return false;
	cerr << endl;

	cerr << "-- testing Unlink(103) (should print 102,104) -- \n";
	dlist->Unlink( dlink3 );
	if ( ! PrintList( dlist ) ) return false;
	cerr << endl;

	cerr << "-- testing Unlink(104) (should print 102) -- \n";
	dlist->Unlink( dlink4 );
	if ( ! PrintList( dlist ) ) return false;
	cerr << endl;

	cerr << "-- testing Unlink(102) (should print empty) -- \n";
	dlist->Unlink( dlink2 );
	if ( ! PrintList( dlist ) ) return false;
	cerr << endl;

	///////////////////////////////////////////////////////////////
	// prepend test
	///////////////////////////////////////////////////////////////

	Dlist<Class>* dlist2 = new Dlist<Class>;  // implies SCALAR/FREE

	if (    ! dlist2->IfEmpty()
         || dlist2->First() != NULL
         || dlist2->Last()  != NULL )
		Fail();

	cerr << "-- testing Prepend() once (should print 1) -- \n";
	p = new Class( 1 );
	dlink = dlist2->Prepend( p );
	if ( ! PrintList( dlist2 ) ) return false;
	cerr << endl;
	if (    dlist2->IfEmpty()
         || dlist2->First() == NULL
         || dlist2->Last()  == NULL
         || dlist2->First()->mObj->mVal != 1
         || dlist2->Last()->mObj->mVal  != 1 )
		Fail();

	cerr << "-- testing Append() once (should print 1,2) -- \n";
	p = new Class( 2 );
	dlink = dlist2->Append( p );
	if ( ! PrintList( dlist2 ) ) return false;
	cerr << endl;
	if (    dlist2->IfEmpty()
         || dlist2->First() == NULL
         || dlist2->Last()  == NULL
         || dlist2->First()->mObj->mVal != 1
         || dlist2->Last()->mObj->mVal  != 2 )
		Fail();

	cerr << "-- testing Prepend() again (should print 3,1,2) -- \n";
	p = new Class( 3 );
	dlink = dlist2->Prepend( p );
	if ( ! PrintList( dlist2 ) ) return false;
	cerr << endl;
	if (    dlist2->IfEmpty()
         || dlist2->First() == NULL
         || dlist2->Last()  == NULL
         || dlist2->First()->mObj->mVal != 3
         || dlist2->Last()->mObj->mVal  != 2 )
		Fail();

	cerr << "-- testing Dlist::Next() -- \n";
		// 1st
	dlink = dlist2->First();
	if ( dlink == NULL || dlink->mObj->mVal != 3 ) Fail();
		// 2nd
	dlink = dlist2->Next(dlink);
	if ( dlink == NULL || dlink->mObj->mVal != 1 ) Fail();
		// 3rd
	dlink = dlist2->Next(dlink);
	if ( dlink == NULL || dlink->mObj->mVal != 2 ) Fail();
		// non-existent
	dlink = dlist2->Next(dlink);
	if ( dlink != NULL ) Fail(); // should be NULL
 }
 //-----------------------------------------------------------

	////////////////////////////////////////////////////////////////////////////
	cerr << "-- testing For*() helpers with loop that unlinks current link -- \n";
  {
	Dlist<Class>* dlist = new Dlist<Class>;  // implies SCALAR/FREE
	dlist->Append( new Class( 1001 ) );
	dlist->Append( new Class( 1002 ) );
	dlist->Append( new Class( 1003 ) );
	dlist->Append( new Class( 1004 ) );

	// List has 4 elements.
	int count = 0;
	for ( const Dlink<Class>* dlink = dlist->ForStart(); dlist->ForTest(dlink); dlist->ForNext(dlink) )
	{
		++count;
		switch ( count )
		{
			case 1:
				if ( dlink->mObj->mVal != 1001 ) Fail();
				break;
			case 2:
				if ( dlink->mObj->mVal != 1002 ) Fail();
				delete dlink->mObj;
				dlist->Unlink( const_cast< Dlink<Class>* >(dlink) );
				break;
			case 3:
				if ( dlink->mObj->mVal != 1003 ) Fail();
				break;
			case 4:
				if ( dlink->mObj->mVal != 1004 ) Fail();
				break;
		}
	}
	if ( count != 4 ) Fail();
  }

	////////////////////////////////////////////////////////////////////////////
	cerr << "-- testing deleting empty list - \n";
  {
	Dlist<Class>* dlist = new Dlist<Class>( Dlist<Class>::SCALAR );
	delete dlist;

	Dlist<Class>* dlist2 = new Dlist<Class>( Dlist<Class>::ARRAY );
	delete dlist2;
  }

	////////////////////////////////////////////////////////////////////////////
	cerr << "-- testing Dlist( SCALAR,DESTROY ) - \n";
  {
    sInstances = 0;
	Dlist<Class> dlist( Dlist<Class>::SCALAR, Dlist<Class>::DESTROY );
    dlist.Append( new Class( 101 ) );
    dlist.Append( new Class( 102 ) );
	dlist.Append( new Class( 103 ) );
    if ( sInstances != 3 ) Fail();
  }
    if ( sInstances != 0 ) Fail();  // ~Dlist() should have destroyed linked objects

	////////////////////////////////////////////////////////////////////////////
	cerr << "-- testing Dlist( ARRAY,DESTROY ) - \n";
  {
    sInstances = 0;
	Dlist<Class> dlist( Dlist<Class>::ARRAY, Dlist<Class>::DESTROY );  // array case
    dlist.Append( new Class[100] );
	dlist.Append( new Class[100] );
    if ( sInstances != 200 ) Fail();
  }
    if ( sInstances != 0 ) Fail();  // ~Dlist() should have destroyed linked objects

	////////////////////////////////////////////////////////////////////////////
	cerr << "-- testing Dlist( SCALAR,KEEP ) - \n";
  {
    sInstances = 0;
	Dlist<Class> dlist( Dlist<Class>::KEEP, Dlist<Class>::SCALAR );  // swapped args should be supported
    dlist.Append( new Class( 101 ) );
    dlist.Append( new Class( 102 ) );
	dlist.Append( new Class( 103 ) );
    if ( sInstances != 3 ) Fail();
  }
    if ( sInstances != 3 ) Fail();  // should keep objects

	////////////////////////////////////////////////////////////////////////////
	cerr << "-- testing Dlist( ARRAY,KEEP ) - \n";
  {
    sInstances = 0;
	Dlist<Class> dlist( Dlist<Class>::ARRAY, Dlist<Class>::KEEP );  // array case
    dlist.Append( new Class[70] );
	dlist.Append( new Class[70] );
    if ( sInstances != 140 ) Fail();
  }
    if ( sInstances != 140 ) Fail();  // should keep

	////////////////////////////////////////////////////////////////////////////
	cerr << "-- testing Dlist.Clear() - \n";
  {
    sInstances = 0;
	Dlist<Class> dlist( Dlist<Class>::DESTROY, Dlist<Class>::SCALAR );
	dlist.Append( new Class( 101 ) );
	dlist.Append( new Class( 102 ) );
	dlist.Append( new Class( 103 ) );
    if ( sInstances != 3 ) Fail();
    dlist.Clear();
    if ( sInstances != 0 ) Fail();
  }
  {
    sInstances = 0;
	Dlist<Class> dlist( Dlist<Class>::KEEP, Dlist<Class>::SCALAR );
	dlist.Append( new Class( 111 ) );
	dlist.Append( new Class( 222 ) );
	dlist.Append( new Class( 333 ) );
    if ( sInstances != 3 ) Fail();
	if ( dlist.First()->mObj->mVal != 111 ) Fail();
	if ( dlist.Last()->mObj->mVal != 333 ) Fail();
    dlist.Clear();
	if ( dlist.First() != NULL ) Fail();
	if ( dlist.Last() != NULL ) Fail();
    if ( sInstances != 3 ) Fail();  // KEEP was specified
  }
  {
	// Test clearing one element.
    sInstances = 0;
	Dlist<Class> dlist( Dlist<Class>::DESTROY, Dlist<Class>::SCALAR );
	dlist.Append( new Class( 101 ) );
    if ( sInstances != 1 ) Fail();
    dlist.Clear();
    if ( sInstances != 0 ) Fail();

	dlist.Append( new Class( 101 ) );  // hopefully catch if Clear() malfunctioned
	dlist.Append( new Class( 102 ) );
    if ( sInstances != 2 ) Fail();
  }
  {
	// Test clearing zero elements.
    sInstances = 0;
	Dlist<Class> dlist( Dlist<Class>::DESTROY, Dlist<Class>::SCALAR );
	dlist.Append( new Class( 101 ) );
    if ( sInstances != 1 ) Fail();
    dlist.Clear();
    dlist.Clear(); // again to test clearing empty list
    if ( sInstances != 0 ) Fail();

	dlist.Append( new Class( 399 ) );  // hopefully catch if Clear() malfunctioned
    if ( sInstances != 1 ) Fail();
  }

	////////////////////////////////////////////////////////////////////////////
	cerr << "-- testing Unlink() - \n";
    // Unlink() NEVER deletes objects, regardlesss of DESTROY ctor arg.
    // This design decision was for flexibility, the caller can link and unlink
    // while keeping an object in a list that otherwise auto-destroys its objects.
  {
    sInstances = 0;
	Dlist<Class> dlist( Dlist<Class>::SCALAR, Dlist<Class>::DESTROY );
	Dlink<Class>* dlink = dlist.Append( new Class( 101 ) );
                          dlist.Append( new Class( 102 ) );
    dlist.Unlink( dlink );
    if ( sInstances != 2 ) Fail();
  }
  if ( sInstances != 1 ) Fail();  // unlinked object should not be deleted

  {
    sInstances = 0;
	Dlist<Class> dlist( Dlist<Class>::SCALAR, Dlist<Class>::KEEP );
	Dlink<Class>* dlink = dlist.Append( new Class( 101 ) );
                          dlist.Append( new Class( 102 ) );
                          dlist.Append( new Class( 103 ) );
    dlist.Unlink( dlink );
    if ( sInstances != 3 ) Fail();
  }
  if ( sInstances != 3 ) Fail();

  {
    sInstances = 0;
	Dlist<Class> dlist( Dlist<Class>::ARRAY, Dlist<Class>::KEEP );
	Dlink<Class>* dlink = dlist.Append( new Class[50] );
	                      dlist.Append( new Class[50] );
	                      dlist.Append( new Class[50] );
    dlist.Unlink( dlink );
    if ( sInstances != 150 ) Fail();
  }
  if ( sInstances != 150 ) Fail();

  {
    sInstances = 0;
	Dlist<Class> dlist( Dlist<Class>::ARRAY, Dlist<Class>::DESTROY );
	Dlink<Class>* dlink = dlist.Append( new Class[30] );
	                      dlist.Append( new Class[30] );
	                      dlist.Append( new Class[30] );
    dlist.Unlink( dlink );
    if ( sInstances != 90 ) Fail();
  }
  if ( sInstances != 30 ) Fail();  // unlinked object should not be deleted

	////////////////////////////////////////////////////////////////////////////
	cerr << "-- testing Dlist.IfLinked() - \n";
  {
    sInstances = 0;
	Dlist<Class> dlist( Dlist<Class>::KEEP, Dlist<Class>::SCALAR );

	// Not linked yet.
	Class* o1 = new Class( 101 );
	if ( dlist.IfLinked( o1 ) ) Fail();

	// o1 linked.
	dlist.Append( o1 );
	if ( ! dlist.IfLinked( o1 ) ) Fail();

	// o1 linked twice.
	dlist.Append( o1 );
	if ( ! dlist.IfLinked( o1 ) ) Fail();

	// o1 unlinked.
	dlist.Clear();
	if ( dlist.IfLinked( o1 ) ) Fail();

	// o1 not linked but a link has the same value (different objects/ptrs).
	dlist.Clear();
	dlist.Append( new Class( 101 ) );  // not o1 though it has the same value
	if ( dlist.IfLinked( o1 ) ) Fail();

	// o1 last link
	dlist.Clear();
	dlist.Append( new Class(200) );
	dlist.Append( new Class(201) );
	dlist.Append( new Class(202) );
	dlist.Append( o1 );
	if ( ! dlist.IfLinked( o1 ) ) Fail();
  }

  // -- DONE --
	return true; // doesn't really mean test passed
}
} // namespace gfx

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