#include "srd-preproc-cmd-common.hh"

class SRDPreprocCmdFunctionsTest : public CppUnit::TestFixture
{
	CPPUNIT_TEST_SUITE( SRDPreprocCmdFunctionsTest );

	CPPUNIT_TEST( testBlessNoArgs );
	CPPUNIT_TEST( testBlessMissing );
	CPPUNIT_TEST( testBlessEmpty );
	CPPUNIT_TEST( testBlessInvalid1 );
	CPPUNIT_TEST( testBlessInvalid2 );
	CPPUNIT_TEST( testBlessInvalid3 );
	CPPUNIT_TEST( testBlessInvalid4 );
	CPPUNIT_TEST( testBlessEmptyFunction );
	CPPUNIT_TEST( testBlessFnWithArgs );
	CPPUNIT_TEST( testBlessFnWithDupArgs );
	CPPUNIT_TEST( testBlessFnWithOptArgs );
	CPPUNIT_TEST( testBlessFnWithDupOptArgs );
	CPPUNIT_TEST( testBlessFnWithOptArgsOnly );
	CPPUNIT_TEST( testBlessFnWithDupArgsAll );

	CPPUNIT_TEST( testCallNoArg );
	CPPUNIT_TEST( testCallBlessedVar );
	CPPUNIT_TEST( testCallBadArg );
	CPPUNIT_TEST( testCallInvalidFunction );
	CPPUNIT_TEST( testCallValidFunction );
	CPPUNIT_TEST( testCallTooManyArgs );
	CPPUNIT_TEST( testCallNotEnoughArgs );
	CPPUNIT_TEST( testCallNotEnoughArgsOpt );

	CPPUNIT_TEST( testBodyInnerList );
	CPPUNIT_TEST( testDashFunction );

	CPPUNIT_TEST( testBug00 );
	CPPUNIT_TEST( testBug01 );
	CPPUNIT_TEST( testBug02 );
	CPPUNIT_TEST( testBug03 );

	CPPUNIT_TEST_SUITE_END( );

   public:
	void testBlessNoArgs( );
	void testBlessMissing( );
	void testBlessEmpty( );
	void testBlessInvalid1( );
	void testBlessInvalid2( );
	void testBlessInvalid3( );
	void testBlessInvalid4( );
	void testBlessEmptyFunction( );
	void testBlessFnWithArgs( );
	void testBlessFnWithDupArgs( );
	void testBlessFnWithOptArgs( );
	void testBlessFnWithDupOptArgs( );
	void testBlessFnWithOptArgsOnly( );
	void testBlessFnWithDupArgsAll( );

	void testCallNoArg( );
	void testCallBlessedVar( );
	void testCallBadArg( );
	void testCallInvalidFunction( );
	void testCallValidFunction( );
	void testCallTooManyArgs( );
	void testCallNotEnoughArgs( );
	void testCallNotEnoughArgsOpt( );

	void testBodyInnerList( );
	void testDashFunction( );

	void testBug00( );
	void testBug01( );
	void testBug02( );
	void testBug03( );

};
CPPUNIT_TEST_SUITE_REGISTRATION( SRDPreprocCmdFunctionsTest );

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

void SRDPreprocCmdFunctionsTest::testBlessNoArgs( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -bless )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "not enough arguments" , 1 , 3 );
	M_CKERR_( 1 , "previous error cause" , 1 , 10 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdFunctionsTest::testBlessMissing( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -bless a ) " , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "unknown variable" , 1 , 3 );
	M_CKERR_( 1 , "previous error cause" , 1 , 10 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdFunctionsTest::testBlessEmpty( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				  "( -set a )\n"
				  "( -bless a )\n"
				  "( $a ) " ,
				  errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "invalid function" , 2 , 3 );
	M_CKERR_( 1 , "previous error cause" , 2 , 10 );
	CPPUNIT_ASSERT( check( "( )" , output ) );
}

void SRDPreprocCmdFunctionsTest::testBlessInvalid1( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
		"(-set x\n"
			"nope\n"
		")\n"
		"( -bless x )\n"
		"($x)" ,
		errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "invalid function" , 4 , 3 );
	M_CKERR_( 1 , "previous error cause" , 4 , 10 );
	CPPUNIT_ASSERT( check( "(nope)" , output ) );
}

void SRDPreprocCmdFunctionsTest::testBlessInvalid2( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
		"(-set x\n"
			"()\n"
		")\n"
		"( -bless x )\n"
		"($x)" ,
		errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "invalid function" , 4 , 3 );
	M_CKERR_( 1 , "previous error cause" , 4 , 10 );
	CPPUNIT_ASSERT( check( "(())" , output ) );
}

void SRDPreprocCmdFunctionsTest::testBlessInvalid3( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
		"(-set x\n"
			"(nope)\n"
		")\n"
		"( -bless x )\n"
		"($x)" ,
		errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "invalid function" , 4 , 3 );
	M_CKERR_( 1 , "previous error cause" , 4 , 10 );
	CPPUNIT_ASSERT( check( "((nope))" , output ) );
}

void SRDPreprocCmdFunctionsTest::testBlessInvalid4( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
		"(-set x\n"
			"(()) nope\n"
		")\n"
		"( -bless x )\n"
		"($x)" ,
		errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "invalid function" , 4 , 3 );
	M_CKERR_( 1 , "previous error cause" , 4 , 10 );
	CPPUNIT_ASSERT( check( "((()) nope)" , output ) );
}

void SRDPreprocCmdFunctionsTest::testBlessEmptyFunction( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				  "( -set a ( ( ) x ) )\n"
				  "( -bless a )\n"
				  "( $a ) " ,
				  errors ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) );
	CPPUNIT_ASSERT( check( "x" , output ) );
}

void SRDPreprocCmdFunctionsTest::testBlessFnWithArgs( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				  "( -set a ( ( u v w ) ( -raw $u $v $w ) ) )\n"
				  "( -bless a )\n"
				  "( $a b c d ) " ,
				  errors ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) );
	CPPUNIT_ASSERT( check( "b c d" , output ) );
}

void SRDPreprocCmdFunctionsTest::testBlessFnWithDupArgs( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				  "( -set a ( ( u v u ) ( -raw $u $v ) ) )\n"
				  "( -bless a )\n"
				  "( $a b c d ) " ,
				  errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "invalid function" , 2 , 3 );
	M_CKERR_( 1 , "previous error cause" , 2 , 10 );
	CPPUNIT_ASSERT( check( "( ( ( u v u ) $u $v ) b c d )" , output ) );
}

void SRDPreprocCmdFunctionsTest::testBlessFnWithOptArgs( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				  "( -set a ( ( u ( v ) ) ( -raw ( $u ) ( $v ) ) ) )\n"
				  "( -bless a )\n"
				  "($a b c d)\n"
				  "($a e)\n"
				  , errors ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) );
	CPPUNIT_ASSERT( check( "( b ) ( c d ) ( e ) ( )" , output ) );
}

void SRDPreprocCmdFunctionsTest::testBlessFnWithDupOptArgs( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				  "( -set a ( ( u ( v v ) ) ( -raw ( $u ) ( $v ) ) ) )\n"
				  "( -bless a )\n"
				  "( $a b )" ,
				  errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "invalid function" , 2 , 3 );
	M_CKERR_( 1 , "previous error cause" , 2 , 10 );
	CPPUNIT_ASSERT( check( "( ( ( u ( v v ) ) ( $u ) ( $v ) ) b )" , output ) );
}

void SRDPreprocCmdFunctionsTest::testBlessFnWithOptArgsOnly( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				  "( -set a ( ( ( v ) ) ( -raw ( $v ) ) ) )\n"
				  "( -bless a )\n"
				  "( $a ) ( $a b ) ( $a c d )" ,
				  errors ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) );
	CPPUNIT_ASSERT( check( "( ) ( b ) ( c d )" , output ) );
}

void SRDPreprocCmdFunctionsTest::testBlessFnWithDupArgsAll( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				  "( -set a ( ( u ( v u ) ) ( -raw ( $u ) ( $v ) ) ) )\n"
				  "( -bless a )\n"
				  "( $a b )" ,
				  errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "invalid function" , 2 , 3 );
	M_CKERR_( 1 , "previous error cause" , 2 , 10 );
	CPPUNIT_ASSERT( check( "( ( ( u ( v u ) ) ( $u ) ( $v ) ) b )" , output ) );
}

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

void SRDPreprocCmdFunctionsTest::testCallNoArg( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -call )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "not enough arguments" , 1 , 3 );
	M_CKERR_( 1 , "previous error cause" , 1 , 9 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdFunctionsTest::testCallBlessedVar( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-set f (() x))\n"
				"(-bless f)\n"
				"( -call $f )"
				, errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "x" , output ) );
}

void SRDPreprocCmdFunctionsTest::testCallBadArg( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -call x )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "list expected" , 1 , 3 );
	M_CKERR_( 1 , "previous error cause" , 1 , 9 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdFunctionsTest::testCallInvalidFunction( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -call ( nope ) )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "list expected" , 1 , 3 );
	M_CKERR_( 1 , "previous error cause" , 1 , 11 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdFunctionsTest::testCallValidFunction( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -call ( -raw ( ( x ( y ) ) $y $x ) ) a b c )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) );
	CPPUNIT_ASSERT( check( "b c a" , output ) );
}

void SRDPreprocCmdFunctionsTest::testCallTooManyArgs( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -call ( -raw ( ( x ) $x ) ) a b )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "too many arguments" , 1 , 3 );
	M_CKERR_( 1 , "previous error cause" , 1 , 33 );
	CPPUNIT_ASSERT( check( "a" , output ) );
}

void SRDPreprocCmdFunctionsTest::testCallNotEnoughArgs( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -call ( -raw ( ( x ) $x ) ) )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "not enough arguments" , 1 , 3 );
	M_CKERR_( 1 , "previous error cause" , 1 , 31 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdFunctionsTest::testCallNotEnoughArgsOpt( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -call ( -raw ( ( x y ( z ) ) $x ) ) a )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "not enough arguments" , 1 , 3 );
	M_CKERR_( 1 , "previous error cause" , 1 , 41 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

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

void SRDPreprocCmdFunctionsTest::testBodyInnerList( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"( -set x (-raw ( () ) ) )\n"
				"( -bless x )\n"
				"( -call ( -raw ( () -not-a-command ) ) )\n"
				"( -call ( -raw ( () $x ) ) )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) );
	CPPUNIT_ASSERT( check( "-not-a-command (())" , output ) );
}

void SRDPreprocCmdFunctionsTest::testDashFunction( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-set -f ((x) (-raw yo $x)))\n"
				"(-bless -f)\n"
				"(-f bob)\n"
				, errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "yo bob" , output ) );
}

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

void SRDPreprocCmdFunctionsTest::testBug00( )
{
	/* A bug with tail calls - the code below would output "y x" */
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-set f1 ((-raw (a) $a)))\n"
				"(-set f2 ((-raw (a) ($f1 $a))))\n"
				"(-bless f1 f2)\n"
				"($f2 x) y\n"
				, errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "x y" , output ) );
}

void SRDPreprocCmdFunctionsTest::testBug01( )
{
	/* Similar to bug 00, except with an ending parens. */
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-set f1 ((-raw (a) $a)))\n"
				"(-set f2 ((-raw (a) ($f1 $a))))\n"
				"(-bless f1 f2)\n"
				"(($f2 x))\n"
				, errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "(x)" , output ) );
}

void SRDPreprocCmdFunctionsTest::testBug02( )
{
	/* Causes a crash due to a missing location */
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-set fn (-raw (((list))\n"
					"(-set (first rest) $list)\n"
					"(-if (-length ($first)) (\n"
						"($first ($fn $rest))\n"
					"))\n"
				")))\n"
				"(-bless fn)\n"
				"($fn a b)\n"
				, errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "(a (b))" , output ) );
}

void SRDPreprocCmdFunctionsTest::testBug03( )
{
	/* More tail-call problems (interaction between the "unswallow"
	 * part of the evaluator/function feeders and stuff that stands
	 * between them).
	 */
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-set (f1 f2) (-raw\n"
				   "(() (-if 1 ()))\n"
				   "(() ($f1))\n"
				"))\n"
				"(-bless f1 f2)\n"
				"(-eval ((-raw -eval) ((-raw $f2))))\n"
				, errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "(a (b))" , output ) );
}