/*****************************************************************************
 * $Id: state_machine.cc,v 1.4 2006/07/23 02:09:11 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Test of state-machine.
 *          This is a real test.
 *          The subdir gfx/tests/machine/ has obsolete prototype code (no self-checking).
 *//*
 * LEGAL:	COPYRIGHT (C) 2005 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_machine.hh"

/*****************************************************************************
 * Test support.
 *****************************************************************************/
static int sCnt1, sCnt2, sCnt3, sCnt4;	// counters used to verify test cases
static bool FuncCnt1( Machine* machine ) { ++sCnt1;	return true; }
static bool FuncCnt2( Machine* machine ) { ++sCnt2; return true; }
static bool FuncCnt3( Machine* machine ) { ++sCnt3;	return true; }
static bool FuncCnt4( Machine* machine ) { ++sCnt4; return true; }

static bool FuncTrue(  Machine* machine ) { return true; }
static bool FuncFalse( Machine* machine ) { return false; }

// Prepare for the next test case.
static void
Reset( void )
{
	sCnt1 = sCnt2 = sCnt3 = sCnt4 = 0;
}

/*****************************************************************************
 * Test OPC_FUNC_*.
 *****************************************************************************/
static Machine::Opcode smproc_FUNC[] =
{
DEF_PROC_BEGIN( "FUNC" ),

	DEF_FUNC( FuncTrue ),  DEF_FUNC_T( FuncCnt1 ),	// do
	DEF_FUNC( FuncFalse ), DEF_FUNC_T( FuncCnt2 ),	// don't

	DEF_FUNC( FuncFalse ), DEF_FUNC_F( FuncCnt3 ),	// do
	DEF_FUNC( FuncTrue ),  DEF_FUNC_F( FuncCnt4 ),	// don't

DEF_PROC_END()
};

static bool
Test_FUNC( void )
{
	Reset();
	Machine* machine = new Machine( smproc_FUNC, NULL );
	machine->Tick();
	if (  sCnt1 != 1 || sCnt2 != 0
       || sCnt3 != 1 || sCnt4 != 0 )
		Fail();
	return true;
}

/*****************************************************************************
 * Test ability to suspend.
 *****************************************************************************/
static Machine::Opcode smproc_SUSPEND[] =
{
DEF_PROC_BEGIN( "Suspend" ),

	DEF_FUNC( FuncCnt1 ),
	OPC_SUSPEND,
	DEF_FUNC( FuncCnt1 ),

DEF_PROC_END()
};

static bool
Test_SUSPEND( void )
{
	Reset();
	Machine* machine = new Machine( smproc_SUSPEND, NULL );

	bool ticking = machine->Tick();
	if ( ! ticking || sCnt1 != 1 )
		Fail();

	// suspend should happen

	ticking = machine->Tick();	// should be done by procedure end
	if ( ticking || sCnt1 != 2 )
		Fail();

	return true;
}

/*****************************************************************************
 * Test OPC_QUIT.
 *****************************************************************************/
static Machine::Opcode smproc_QUIT[] =
{
DEF_PROC_BEGIN( "Quit" ),

	DEF_FUNC( FuncCnt1 ),
	DEF_FUNC( FuncCnt1 ),
	DEF_FUNC( FuncCnt1 ),

	OPC_QUIT,

	DEF_FUNC( FuncCnt1 ), // unexpected

DEF_PROC_END()
};

static bool
Test_QUIT( void )
{
	Reset();
	Machine* machine = new Machine( smproc_QUIT, NULL );

	bool ticking = machine->Tick();	// should be done
	if ( ticking || sCnt1 != 3 )
		Fail();

	return true;
}

/*****************************************************************************
 * Tests jumping and compiling of labels.
 *****************************************************************************/
static Machine::Opcode smproc_JUMP[] =
{
DEF_PROC_BEGIN( "Jump" ),

	// ---------------------

	// Test unconditional jump.
	DEF_JUMP( "JumpUncond" ),
DEF_LABEL( "Invalid" ),
	OPC_INVALID,
DEF_LABEL( "JumpUncond" ),

	// ---------------------

	// Test JUMP_T (jump taken).
	DEF_FUNC( FuncTrue ), DEF_JUMP_T( "JUMP_T_taken" ),
	OPC_INVALID,
DEF_LABEL( "JUMP_T_taken" ),

	// Test JUMP_T (jump not taken).
	DEF_FUNC( FuncFalse ), DEF_JUMP_T( "Invalid" ),
	DEF_FUNC( FuncCnt1 ),

	// ---------------------

	// Test JUMP_F (jump taken).
	DEF_FUNC( FuncFalse ), DEF_JUMP_F( "JUMP_F_taken" ),
	OPC_INVALID,
DEF_LABEL( "JUMP_F_taken" ),

	// Test JUMP_F (jump not taken).
	DEF_FUNC( FuncTrue ), DEF_JUMP_F( "Invalid" ),
	DEF_FUNC( FuncCnt2 ),

DEF_PROC_END()
};

static bool
Test_JUMP( void )
{
	Reset();
	Machine* machine = new Machine( smproc_JUMP, NULL );

	bool ticking = machine->Tick();	// should be done
	if ( ticking || sCnt1 != 1 || sCnt2 != 1 )
		Fail();

	return true;
}

/*****************************************************************************
 * Test calling procedures.
 *****************************************************************************/
static Machine::Opcode smproc_call1[] =
{
DEF_PROC_BEGIN( "call1" ),
	DEF_FUNC( FuncCnt1 ),
DEF_PROC_END()
};
static Machine::Opcode smproc_call2[] =
{
DEF_PROC_BEGIN( "call2" ),
	DEF_CALL( smproc_call1 ),
	DEF_FUNC( FuncCnt1 ),
DEF_PROC_END()
};
static bool
Test_CALL( void )
{
	Reset();
	Machine* machine = new Machine( smproc_call2, NULL );

	bool ticking = machine->Tick();	// should be done
	if ( ticking || sCnt1 != 2 )
		Fail();

	return true;
}

/*****************************************************************************
 * Test conditionally calling procedures.
 *****************************************************************************/
static Machine::Opcode smproc_call_tf_cnt1[] =
{
DEF_PROC_BEGIN( "call_tf_cnt1" ),
	DEF_FUNC( FuncCnt1 ),
DEF_PROC_END()
};
static Machine::Opcode smproc_call_tf_cnt2[] =
{
DEF_PROC_BEGIN( "call_tf_cnt2" ),
	DEF_FUNC( FuncCnt2 ),
DEF_PROC_END()
};
static Machine::Opcode smproc_call_tf_cnt3[] =
{
DEF_PROC_BEGIN( "call_tf_cnt3" ),
	DEF_FUNC( FuncCnt3 ),
DEF_PROC_END()
};
static Machine::Opcode smproc_call_tf_cnt4[] =
{
DEF_PROC_BEGIN( "call_tf_cnt4" ),
	DEF_FUNC( FuncCnt4 ),
DEF_PROC_END()
};
static Machine::Opcode smproc_call_tf[] =
{
DEF_PROC_BEGIN( "call_tf" ),

	DEF_FUNC( FuncTrue  ), DEF_CALL_T( smproc_call_tf_cnt1 ),	// do
	DEF_FUNC( FuncFalse ), DEF_CALL_T( smproc_call_tf_cnt2 ),	// don't
	DEF_FUNC( FuncFalse ), DEF_CALL_F( smproc_call_tf_cnt3 ),	// do
	DEF_FUNC( FuncTrue  ), DEF_CALL_F( smproc_call_tf_cnt4 ),	// don't

DEF_PROC_END()
};
static bool
Test_CALL_TF( void )
{
	Reset();
	Machine* machine = new Machine( smproc_call_tf, NULL );

	bool ticking = machine->Tick();	// should be done
	if (    ticking
         || sCnt1 != 1 || sCnt2 != 0
         || sCnt3 != 1 || sCnt4 != 0 )
		Fail();

	return true;
}

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