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


class SRDParserTest : public CppUnit::TestFixture
{
	CPPUNIT_TEST_SUITE( SRDParserTest );
		CPPUNIT_TEST( testTokenTypes );
		CPPUNIT_TEST( testParse );

		CPPUNIT_TEST( testExecStartRuleFinish );
		CPPUNIT_TEST( testExecContext );
		CPPUNIT_TEST( testExecContextAndRule );
		CPPUNIT_TEST( testExecInterruptTop );
		CPPUNIT_TEST( testExecInterruptContext );
		CPPUNIT_TEST( testExecLoneEnter );
		CPPUNIT_TEST( testExecLoneExit );
		CPPUNIT_TEST( testExecNoContextHandler );

		CPPUNIT_TEST( testParseOLBCList );
		CPPUNIT_TEST( testParseOLBCContext );
		CPPUNIT_TEST( testParseOLBCListThenContext );
		CPPUNIT_TEST( testParseOLBCListOverrides );
		CPPUNIT_TEST( testParseOLBCContextOverrides );
		CPPUNIT_TEST( testParseOLBCFailure );

		CPPUNIT_TEST( testErrorTopLevelJunk );
		CPPUNIT_TEST( testErrorMismatchInRule );
		CPPUNIT_TEST( testErrorContextJunk );
		CPPUNIT_TEST( testErrorUnterminatedLists );

		CPPUNIT_TEST( testErrorNoExec );
		CPPUNIT_TEST( testErrorInHandlerTop );
		CPPUNIT_TEST( testErrorInHandlerContext );
	CPPUNIT_TEST_SUITE_END( );

	static T_SRDParserDefs makeParseOnlyDefs( );
	static T_SRDParserDefs makeExecDefs( );
	static T_SRDParserDefs makeOLBCDefs( );

public:
	void testTokenTypes( );
	void testParse( );

	void testExecStartRuleFinish( );
	void testExecContext( );
	void testExecContextAndRule( );
	void testExecInterruptTop( );
	void testExecInterruptContext( );
	void testExecLoneEnter( );
	void testExecLoneExit( );
	void testExecNoContextHandler( );

	// Parse with Optional List Before Context
	// * Clear choice towards the list
	void testParseOLBCList( );
	// * Clear choice towards the context
	void testParseOLBCContext( );
	// * List then context, both unambiguous
	void testParseOLBCListThenContext( );
	// * Ambiguity between list and context resolved by using the list
	void testParseOLBCListOverrides( );
	// * Initial ambiguity between list and context resolved by using the context
	void testParseOLBCContextOverrides( );
	// * Neither the list nor the context match the input
	void testParseOLBCFailure( );

	void testErrorTopLevelJunk( );
	void testErrorMismatchInRule( );
	void testErrorContextJunk( );
	void testErrorUnterminatedLists( );

	void testErrorNoExec( );
	void testErrorInHandlerTop( );
	void testErrorInHandlerContext( );
};
CPPUNIT_TEST_SUITE_REGISTRATION( SRDParserTest );

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

namespace {

// parse( input , IN/OUT parser , OUT errors )
void parse( char const* input , T_SRDParser & parser , T_SRDErrors & errors )
{
	T_SRDLexer lexer( T_String( "test" ) , errors , parser );
	parser.start( errors );
	char const* ptr = input;
	while ( *ptr != 0 ) {
		lexer.processCharacter( *ptr ++ );
	}
	lexer.processEnd( );
}

// parse( input , defs , OUT errors ) - Helper function for the tests
void parse( char const* input , T_SRDParserDefs const& defs , T_SRDErrors & errors )
{
	T_SRDParserConfig cfg( defs );
	T_SRDParser parser( cfg );
	parse( input , parser , errors );
}

} // namespace

// M_PRINTERR_( index ) - Print an error message
#define M_PRINTERR_(IDX) \
	do { \
		auto const& _e( errors[ IDX ] ); \
		char err[ _e.error( ).size( ) + 1 ]; \
		err[ sizeof( err ) - 1 ] = 0; \
		memcpy( err , _e.error( ).data( ) , \
				sizeof( err ) - 1 ); \
		printf( "ERR %s l. %u c. %lu\n" , err , \
				_e.location( ).line( ) , \
				_e.location( ).character( ) ); \
	} while ( 0 )

// M_CKERR_( index , string , line , character ) - Check an error
#define M_CKERR_(IDX,STR,L,C) \
	do { \
		auto const& _e( errors[ IDX ] ); \
		CPPUNIT_ASSERT( T_String( STR ) == _e.error( ) ); \
		CPPUNIT_ASSERT_EQUAL( uint32_t( L ) , \
				_e.location( ).line( ) ); \
		CPPUNIT_ASSERT_EQUAL( size_t( C ) , \
				_e.location( ).character( ) ); \
	} while ( 0 )

// M_PARSE_( defs , input ) - Run the parser using definitions provided by
// SRDParserTest::make{defs}Defs() on the specified input.
#define M_PARSE_( DEFS , INPUT ) \
	T_SRDErrors errors; \
	T_SRDParserConfig cfg( make ##DEFS## Defs( ) ); \
	T_SRDParser parser( cfg ); \
	parse( INPUT , parser , errors )


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

T_SRDParserDefs SRDParserTest::makeParseOnlyDefs( )
{
	using namespace SRD;
	const T_String name( T_String::Pooled( "base" ) );
	const T_String sub( T_String::Pooled( "sub" ) );

	T_SRDParserDefs d( name );
	d.enumeration( T_String( "enum" ) )
		<< "this" << "that";
	d.context( name )
		<< ( Rule( ) << "simple" << "test" )
		<< ( Rule( ) << Enum( "enum" ) << "works" << ( Opt( ) << "too" )
				<< EnterContext( sub ) )
		<< ( Rule( ) << Int32( ) << "little" << "piggies" )
		<< ( Rule( ) << "too" << "sexy" << "for" << "the"
					<< ( AtLeast( 0 ) << "wabbit"  << "too" << "sexy" << "for" << "the" )
					<< "wabbiiiiit" )
	;
	d.context( sub )
		<< ( Rule( ) << "sometimes" );
	return d;
}

T_SRDParserDefs SRDParserTest::makeExecDefs( )
{
	using namespace SRD;
	const T_String name( T_String::Pooled( "base" ) );
	const T_String sub( T_String::Pooled( "sub" ) );
	static const F_SRDHandler start(
		[]( T_SRDParserData const& d ) -> bool {
			*( d.currentData ) = T_StringBuilder( );
			d.currentData->value< T_StringBuilder >( ) << "START";
			return true;
		} );
	static const F_SRDHandler finish(
		[]( T_SRDParserData const& d ) -> bool {
			d.currentData->value< T_StringBuilder >( ) << " FINISH";
			return true;
		} );
	static const F_SRDHandler op(
		[]( T_SRDParserData const& d ) -> bool {
			d.currentData->value< T_StringBuilder >( )
				<< " OP("
				<< (* d.input)[ 1 ].longValue( )
				<< ')';
			return true;
		} );
	static const F_SRDHandler enter(
		[]( T_SRDParserData const& d ) -> bool {
			*( d.targetData ) = T_StringBuilder( );
			d.targetData->value< T_StringBuilder >( )
				<< " ENTER(" << d.targetContext << ')';
			return true;
		} );
	static const F_SRDHandler exit(
		[]( T_SRDParserData const& d ) -> bool {
			d.targetData->value< T_StringBuilder >( )
				<< d.currentData->value< T_StringBuilder >( )
				<< " EXIT(" << d.currentContext << ')';
			return true;
		} );
	static const F_SRDHandler ctxExec(
		[]( T_SRDParserData const& d ) -> bool {
			d.currentData->value< T_StringBuilder >( )
				<< " CTXEXEC("
				<< (* d.input)[ 2 ].longValue( )
				<< ')';
			return true;
		} );
	static const F_SRDHandler failError(
		[]( T_SRDParserData const& d ) -> bool {
			d.errors.add( "Failure on demand!" , (* d.input)[ 0 ] );
			return true;
		} );
	static const F_SRDHandler failHandler(
		[]( T_SRDParserData const& d ) -> bool {
			d.currentData->value< T_StringBuilder >( )
				<< " FAIL";
			return false;
		} );
	static const F_SRDHandler exitAlone(
		[]( T_SRDParserData const& d ) -> bool {
			d.currentData->value< T_StringBuilder >( )
				<< " LONE-EXIT(" << d.currentContext << ')';
			return true;
		} );
	static const F_SRDHandler enterAlone(
		[]( T_SRDParserData const& d ) -> bool {
			d.currentData->value< T_StringBuilder >( )
				<< " LONE-ENTER(" << d.targetContext << ')';
			return true;
		} );

	T_SRDParserDefs d( sub );
	d << OnStart( start ) << OnFinish( finish );

	d.context( sub )
		<< ( Rule( ) << "do-op" << Int32( ) << op )
		<< ( Rule( ) << "fail" << "error" << failError )
		<< ( Rule( ) << "fail" << "handler" << failHandler );

	d.context( name , sub )
		<< ( Rule( ) << "enter" << EnterContext( sub )
				<< OnEnter( enter ) << OnExit( exit ) )
		<< ( Rule( ) << "enter" << "exec" << Int32( )
				<< ctxExec << EnterContext( sub )
				<< OnEnter( enter ) << OnExit( exit ) )
		<< ( Rule( ) << "handle" << "nothing" << EnterContext( sub ) )
		<< ( Rule( ) << "handle" << "only" << "exit" << EnterContext( sub )
				<< OnExit( exitAlone ) )
		<< ( Rule( ) << "handle" << "only" << "entrance" << EnterContext( sub )
				<< OnEnter( enterAlone ) );

	d.defaultContext( name );
	return d;
}

T_SRDParserDefs SRDParserTest::makeOLBCDefs( )
{
	using namespace SRD;
	T_SRDParserDefs d( "base" );
	d << OnStart(
			[]( T_SRDParserData const& d ) -> bool {
				*( d.currentData ) = T_StringBuilder( );
				d.currentData->value< T_StringBuilder >( ) << "START";
				return true;
			} )
		<< OnFinish(
			[]( T_SRDParserData const& d ) -> bool {
				d.currentData->value< T_StringBuilder >( ) << " FINISH";
				return true;
			} );

	d.context( "base" )
		<< ( Rule( ) << "olbc"
				<< ( AtLeast( 0 ) << ( List( ) << ( Alt( )
					<< "non-context"
					<< "context" ) ) )
				<< EnterContext( "sub" )
				<< OnEnter( []( T_SRDParserData const& d ) -> bool {
					d.currentData->value< T_StringBuilder >( ) << " SUB";
					return true;
				} )
				<< []( T_SRDParserData const& d ) -> bool {
					d.currentData->value< T_StringBuilder >( )
						<< " EXEC("
						<< d.input->size( )
						<< ')';
					return true;
				} )
	;
	d.context( "sub" )
		<< ( Rule( ) << "context" << Opt( Word( "too" ) ) )
		<< ( Rule( ) << "in" << "context" )
	;
	return d;
}

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

void SRDParserTest::testTokenTypes( )
{
	using F_InitRule = std::function< void( T_SRDContext& ) >;
	using namespace ebcl::SRD;
	const F_InitRule rules[] = {
		[]( T_SRDContext& c ) { c << ( Rule() << Word( ) ); } ,
		[]( T_SRDContext& c ) { c << ( Rule() << String( ) ); } ,
		[]( T_SRDContext& c ) { c << ( Rule() << Integer( ) ); } ,
		[]( T_SRDContext& c ) { c << ( Rule() << Float( ) ); } ,
		[]( T_SRDContext& c ) { c << ( Rule() << Binary( ) ); } ,
	};
	char const* const inputs[] = {
		"(word)" , "(\"string\")" , "(123)" , "(123.0)" , "([])"
	};
	const uint32_t nTests( sizeof( inputs ) / sizeof( inputs[ 0 ] ) );
	static_assert( nTests == sizeof( rules ) / sizeof( rules[ 0 ] ) ,
			"bad tests" );

	for ( uint32_t i = 0 ; i < nTests ; i ++ ) {
		T_SRDParserDefs defs( "default" );
		rules[ i ]( defs.context( "default" ) );
		for ( uint32_t j = 0 ; j < nTests ; j ++ ) {
			T_SRDErrors errors;
			parse( inputs[ j ] , defs , errors );
			if ( i == j ) {
				CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
			} else {
				CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) );
			}
		}
	}
}

void SRDParserTest::testParse( )
{
	static const char input[] =
		"( simple test )\n"
		"( this works )\n"
		"( that works too )\n"
		"( this works ( sometimes ) )\n"
		"( 3 little piggies )\n"
		"( too sexy for the wabbit too sexy for the wabbit too sexy for the wabbiiiiit )"
	;

	T_SRDErrors errors;
	parse( input , makeParseOnlyDefs( ) , errors );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) );
}

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

void SRDParserTest::testExecStartRuleFinish( )
{
	M_PARSE_( Exec , "( do-op 2 )" );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) );
	T_StringBuilder sb( parser.getData< T_StringBuilder >( ) );
	CPPUNIT_ASSERT( sb == "START OP(2) FINISH" );
}

void SRDParserTest::testExecContext( )
{
	M_PARSE_( Exec , "( enter ( do-op 2 ) ( do-op 3 ) )" );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) );
	T_StringBuilder sb( parser.getData< T_StringBuilder >( ) );
	CPPUNIT_ASSERT( T_String( sb ) == T_String(
			"START "
			"ENTER(sub) "
				"OP(2) "
				"OP(3) "
			"EXIT(sub) "
			"FINISH" ) );
}

void SRDParserTest::testExecContextAndRule( )
{
	M_PARSE_( Exec , "( enter exec 4 ( do-op 2 ) ( do-op 3 ) )" );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) );
	T_StringBuilder sb( parser.getData< T_StringBuilder >( ) );
	CPPUNIT_ASSERT( T_String( sb ) == T_String(
			"START "
			"ENTER(sub) "
				"OP(2) "
				"OP(3) "
			"EXIT(sub) "
			"CTXEXEC(4) "
			"FINISH" ) );
}

void SRDParserTest::testExecInterruptTop( )
{
	M_PARSE_( Exec , "( do-op 2 ) ( fail handler )" );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) );
	T_StringBuilder sb( parser.getData< T_StringBuilder >( ) );
	CPPUNIT_ASSERT( T_String( sb ) == T_String(
			"START "
			"OP(2) "
			"FAIL" ) );
}

void SRDParserTest::testExecInterruptContext( )
{
	M_PARSE_( Exec , "( enter ( do-op 2 ) ( fail handler ) )" );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) );
	T_StringBuilder sb( parser.getData< T_StringBuilder >( ) );
	CPPUNIT_ASSERT( T_String( sb ) == T_String( "START" ) );
}

void SRDParserTest::testExecLoneEnter( )
{
	M_PARSE_( Exec , "( handle only entrance ( do-op 2 ) )" );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) );
	T_StringBuilder sb( parser.getData< T_StringBuilder >( ) );
	CPPUNIT_ASSERT( T_String( sb ) == T_String(
				"START "
				"LONE-ENTER(sub) "
				"OP(2) "
				"FINISH" ) );
}

void SRDParserTest::testExecLoneExit( )
{
	M_PARSE_( Exec , "( handle only exit ( do-op 2 ) )" );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) );
	T_StringBuilder sb( parser.getData< T_StringBuilder >( ) );
	CPPUNIT_ASSERT( T_String( sb ) == T_String(
				"START "
				"OP(2) "
				"LONE-EXIT(sub) "
				"FINISH" ) );
}

void SRDParserTest::testExecNoContextHandler( )
{
	M_PARSE_( Exec , "( handle nothing ( do-op 2 ) )" );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) );
	T_StringBuilder sb( parser.getData< T_StringBuilder >( ) );
	CPPUNIT_ASSERT( sb == "START OP(2) FINISH" );
}

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

void SRDParserTest::testParseOLBCList( )
{
	M_PARSE_( OLBC , "( olbc ( non-context ) ( non-context ) )" );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) );
	T_StringBuilder sb( parser.getData< T_StringBuilder >( ) );
	CPPUNIT_ASSERT( sb == "START EXEC(3) FINISH" );
}

void SRDParserTest::testParseOLBCContext( )
{
	M_PARSE_( OLBC , "( olbc ( in context ) ( in context ) )" );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) );
	T_StringBuilder sb( parser.getData< T_StringBuilder >( ) );
	CPPUNIT_ASSERT( sb == "START SUB EXEC(1) FINISH" );
}

void SRDParserTest::testParseOLBCListThenContext( )
{
	M_PARSE_( OLBC , "( olbc ( non-context ) ( in context ) )" );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) );
	T_StringBuilder sb( parser.getData< T_StringBuilder >( ) );
	CPPUNIT_ASSERT( sb == "START SUB EXEC(2) FINISH" );
}

void SRDParserTest::testParseOLBCListOverrides( )
{
	M_PARSE_( OLBC , "( olbc ( context ) )" );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) );
	T_StringBuilder sb( parser.getData< T_StringBuilder >( ) );
	CPPUNIT_ASSERT( sb == "START EXEC(2) FINISH" );
}

void SRDParserTest::testParseOLBCContextOverrides( )
{
	M_PARSE_( OLBC , "( olbc ( context too ) )" );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) );
	T_StringBuilder sb( parser.getData< T_StringBuilder >( ) );
	CPPUNIT_ASSERT( sb == "START SUB EXEC(1) FINISH" );
}

void SRDParserTest::testParseOLBCFailure( )
{
	M_PARSE_( OLBC , "( olbc ( incorrect ) )" );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , errors.size( ) );
	M_CKERR_( 0 , "'context' or 'in' expected, 'incorrect' found instead." , 1 , 10 );
}

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

void SRDParserTest::testErrorTopLevelJunk( )
{
	static const char input[] =
		")\n"
		"( simple test )\n"
		"junk\n"
		"( this works )";
	;

	T_SRDErrors errors;
	parse( input , makeParseOnlyDefs( ) , errors );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , errors.size( ) );
	M_CKERR_( 0 , "'(' expected, ')' found instead." , 1 , 1 );
	M_CKERR_( 1 , "'(' expected, 'junk' found instead." , 3 , 1 );
}

void SRDParserTest::testErrorMismatchInRule( )
{
	static const char input[] =
		"( simple piggies )\n"
		"( tests suck )\n"
		"( 3 little piggies ate a farmer )\n"
		"( this works when you punch it in the balls )\n"
		"( this works ( sort of ) )\n"
	;

	T_SRDErrors errors;
	parse( input , makeParseOnlyDefs( ) , errors );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 5 ) , errors.size( ) );
	M_CKERR_( 0 , "'test' expected, 'piggies' found instead." , 1 , 10 );
	M_CKERR_( 1 , "'simple', 'too', 'enum' enum value or INT token expected, 'tests' found instead." , 2 , 3 );
	M_CKERR_( 2 , "')' expected, 'ate' found instead." , 3 , 20 );
	M_CKERR_( 3 , "'(', ')' or 'too' expected, 'when' found instead." , 4 , 14 );
	M_CKERR_( 4 , "'sometimes' expected, 'sort' found instead." , 5 , 16 );
}

void SRDParserTest::testErrorContextJunk( )
{
	static const char input[] =
		"( this works (sometimes) when you punch it (sometimes) forcefully )";
	;

	T_SRDErrors errors;
	parse( input , makeParseOnlyDefs( ) , errors );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , errors.size( ) );
	M_CKERR_( 0 , "'(' or ')' expected, 'when' found instead." , 1 , 26 );
	// Second error because finding a valid rule resets error recovery
	M_CKERR_( 1 , "'(' or ')' expected, 'forcefully' found instead." , 1 , 56 );
}

void SRDParserTest::testErrorUnterminatedLists( )
{
	static const char input[] =
		"( this works\n"
			"( sometimes"
	;

	T_SRDErrors errors;
	parse( input , makeParseOnlyDefs( ) , errors );
	CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , errors.size( ) );
	M_CKERR_( 0 , "Unterminated list." , 1 , 1 );
	M_CKERR_( 1 , "Unterminated list." , 2 , 1 );
}

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

void SRDParserTest::testErrorNoExec( )
{
	static const char input[] =
		"( enter ( do-op 2 ) ( invalid ) )"
	;

	T_SRDErrors errors;
	T_SRDParserConfig cfg( makeExecDefs( ) );
	T_SRDParser parser( cfg );
	parse( input , parser , errors );

	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , errors.size( ) );
	CPPUNIT_ASSERT_THROW( parser.getData< T_StringBuilder >( ) ,
			std::bad_cast );
}

void SRDParserTest::testErrorInHandlerTop( )
{
	static const char input[] =
		"( fail error )"
	;

	T_SRDErrors errors;
	T_SRDParserConfig cfg( makeExecDefs( ) );
	T_SRDParser parser( cfg );
	parse( input , parser , errors );

	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , errors.size( ) );
	M_CKERR_( 0 , "Failure on demand!" , 1 , 3 );
	T_StringBuilder sb( parser.getData< T_StringBuilder >( ) );
	CPPUNIT_ASSERT( T_String( sb ) == T_String( "START" ) );
}

void SRDParserTest::testErrorInHandlerContext( )
{
	static const char input[] =
		"( enter\n( fail error ) )"
	;

	T_SRDErrors errors;
	T_SRDParserConfig cfg( makeExecDefs( ) );
	T_SRDParser parser( cfg );
	parse( input , parser , errors );

	CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , errors.size( ) );
	M_CKERR_( 0 , "Failure on demand!" , 2 , 3 );
	T_StringBuilder sb( parser.getData< T_StringBuilder >( ) );
	CPPUNIT_ASSERT( T_String( sb ) == T_String( "START" ) );
}