#include <ebcl/SRDBinary.hh>
#include <ebcl/MemoryStreams.hh>
#include <cppunit/extensions/HelperMacros.h>
using namespace ebcl;


#define V1PROLOGUE_ \
	0xce , 0xa1 , 0x7c , 0xea , \
	0x00 , 0xee , 0xff , 0xc0

class SRDBinReaderTest : public CppUnit::TestFixture
{
	CPPUNIT_TEST_SUITE( SRDBinReaderTest );
		CPPUNIT_TEST( testEmptyInput );
		CPPUNIT_TEST( testIncompleteMagic );
		CPPUNIT_TEST( testBadMagic );

		CPPUNIT_TEST( testMissingVersion );
		CPPUNIT_TEST( testIncompleteVersion );
		CPPUNIT_TEST( testBadVersion );

		CPPUNIT_TEST( testPrologueOnly );
		CPPUNIT_TEST( testEmpty );

		CPPUNIT_TEST( testList );
		CPPUNIT_TEST( testIncompleteList );

		CPPUNIT_TEST( testString );
		CPPUNIT_TEST( testComment );

		CPPUNIT_TEST( testWordUnknown );
		CPPUNIT_TEST( testWordEmpty );
		CPPUNIT_TEST( testWordInvalid );
		CPPUNIT_TEST( testWordDup );
		CPPUNIT_TEST( testWordKnown );
		CPPUNIT_TEST( testWordBadID );

		CPPUNIT_TEST( testSpuriousVar );
		CPPUNIT_TEST( testVarUnknownWord );
		CPPUNIT_TEST( testVarKnownWord );

		CPPUNIT_TEST( testInt32 );
		CPPUNIT_TEST( testInt64 );
		CPPUNIT_TEST( testFloat );

		CPPUNIT_TEST( testBinaryEmpty );
		CPPUNIT_TEST( testBinarySizeOnly );
		CPPUNIT_TEST( testBinaryData );
	CPPUNIT_TEST_SUITE_END( );

	T_String location = T_String( "test" );

public:
	void testEmptyInput( );
	void testIncompleteMagic( );
	void testBadMagic( );

	void testMissingVersion( );
	void testIncompleteVersion( );
	void testBadVersion( );

	void testPrologueOnly( );
	void testEmpty( );

	void testList( );
	void testIncompleteList( );

	void testString( );
	void testComment( );

	void testWordUnknown( );
	void testWordEmpty( );
	void testWordInvalid( );
	void testWordDup( );
	void testWordKnown( );
	void testWordBadID( );

	void testSpuriousVar( );
	void testVarUnknownWord( );
	void testVarKnownWord( );

	void testInt32( );
	void testInt64( );
	void testFloat( );

	void testBinaryEmpty( );
	void testBinarySizeOnly( );
	void testBinaryData( );
};
CPPUNIT_TEST_SUITE_REGISTRATION( SRDBinReaderTest );

/*----------------------------------------------------------------------------*/
// Helper macros

#define M_CKLS_(SIZE) \
	CPPUNIT_ASSERT_EQUAL( uint32_t( SIZE ) , list.size( ) )

#define M_CKTOK_(ITEM,TYPE,POS) \
	do { \
		CPPUNIT_ASSERT( E_SRDTokenType::TYPE == \
				list[ ITEM ].type( ) ); \
		CPPUNIT_ASSERT( list[ ITEM ].hasLocation( ) ); \
		CPPUNIT_ASSERT( location == \
				list[ ITEM ].location( ).source( ) ); \
		CPPUNIT_ASSERT_EQUAL( size_t( POS ) , \
				list[ ITEM ].location( ).byte( ) ); \
	} while ( 0 )

#define M_CKERRS_( x ) \
	CPPUNIT_ASSERT_EQUAL( uint32_t( x ) , e.errors.size( ) )

#define M_CKERR_(IDX,STR,POS) \
	do { \
		auto const& _e( e.errors[ IDX ] ); \
		CPPUNIT_ASSERT( T_String( STR ) == _e.error( ) ); \
		CPPUNIT_ASSERT( location == _e.location( ).source( ) ); \
		CPPUNIT_ASSERT_EQUAL( size_t( POS ) , \
				_e.location( ).byte( ) ); \
	} while ( 0 )

#define M_PRINTERR_(IDX) \
	do { \
		char err[ e.errors[ IDX ].error( ).size( ) + 1 ]; \
		err[ sizeof( err ) - 1 ] = 0; \
		memcpy( err , e.errors[ IDX ].error( ).data( ) , \
				sizeof( err ) - 1 ); \
		printf( "ERR %s @ %d\n" , err , int( \
				e.errors[ IDX ].location( ).byte( ) ) ); \
	} while ( 0 )

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

void SRDBinReaderTest::testEmptyInput( )
{
	T_MemoryInputStream stream( nullptr , 0 );
	T_SRDList list;
	try {
		list = SRDBinaryReadFrom( location , stream , false );
		CPPUNIT_FAIL( "X_SRDErrors exception expected" );
	} catch ( X_SRDErrors & e ) {
		M_CKERRS_( 1 );
		M_CKERR_( 0 , "missing magic" , 0 );
	}
}

void SRDBinReaderTest::testIncompleteMagic( )
{
	const uint8_t data[] = {
		0xce , 0xa1 , 0x7c
	};
	T_MemoryInputStream stream( data , sizeof( data ) );
	T_SRDList list;
	try {
		list = SRDBinaryReadFrom( location , stream , false );
		CPPUNIT_FAIL( "X_SRDErrors exception expected" );
	} catch ( X_SRDErrors & e ) {
		M_CKERRS_( 1 );
		M_CKERR_( 0 , "missing magic" , 3 );
	}
}

void SRDBinReaderTest::testBadMagic( )
{
	const uint8_t data[] = {
		0xce , 0xa1 , 0x7c , 0xfa
	};
	T_MemoryInputStream stream( data , sizeof( data ) );
	T_SRDList list;
	try {
		list = SRDBinaryReadFrom( location , stream , false );
		CPPUNIT_FAIL( "X_SRDErrors exception expected" );
	} catch ( X_SRDErrors & e ) {
		M_CKERRS_( 1 );
		M_CKERR_( 0 , "invalid magic number" , 0 );
	}
}

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

void SRDBinReaderTest::testMissingVersion( )
{
	const uint8_t data[] = {
		0xce , 0xa1 , 0x7c , 0xea
	};
	T_MemoryInputStream stream( data , sizeof( data ) );
	T_SRDList list;
	try {
		list = SRDBinaryReadFrom( location , stream , false );
		CPPUNIT_FAIL( "X_SRDErrors exception expected" );
	} catch ( X_SRDErrors & e ) {
		M_CKERRS_( 1 );
		M_CKERR_( 0 , "missing version ID" , 4 );
	}
}

void SRDBinReaderTest::testIncompleteVersion( )
{
	const uint8_t data[] = {
		0xce , 0xa1 , 0x7c , 0xea ,
		0x00 , 0xee , 0xff
	};
	T_MemoryInputStream stream( data , sizeof( data ) );
	T_SRDList list;
	try {
		list = SRDBinaryReadFrom( location , stream , false );
		CPPUNIT_FAIL( "X_SRDErrors exception expected" );
	} catch ( X_SRDErrors & e ) {
		M_CKERRS_( 1 );
		M_CKERR_( 0 , "missing version ID" , 7 );
	}
}

void SRDBinReaderTest::testBadVersion( )
{
	const uint8_t data[] = {
		0xce , 0xa1 , 0x7c , 0xea ,
		0xc0 , 0xff , 0xee , 0x00
	};
	T_MemoryInputStream stream( data , sizeof( data ) );
	T_SRDList list;
	try {
		list = SRDBinaryReadFrom( location , stream , false );
		CPPUNIT_FAIL( "X_SRDErrors exception expected" );
	} catch ( X_SRDErrors & e ) {
		M_CKERRS_( 1 );
		M_CKERR_( 0 , "invalid version ID" , 4 );
	}
}

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

void SRDBinReaderTest::testPrologueOnly( )
{
	const uint8_t data[] = { V1PROLOGUE_ };
	T_MemoryInputStream stream( data , sizeof( data ) );
	T_SRDList list;
	try {
		list = SRDBinaryReadFrom( location , stream , false );
		CPPUNIT_FAIL( "X_SRDErrors exception expected" );
	} catch ( X_SRDErrors & e ) {
		M_CKERRS_( 1 );
		M_CKERR_( 0 , "unexpected end of file" , 8 );
	}
}

void SRDBinReaderTest::testEmpty( )
{
	const uint8_t data[] = { V1PROLOGUE_ , 0x00 };
	T_MemoryInputStream stream( data , sizeof( data ) );
	T_SRDList list( SRDBinaryReadFrom( location , stream , false ) );
	M_CKLS_( 0 );
}

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

void SRDBinReaderTest::testList( )
{
	const uint8_t data[] = { V1PROLOGUE_ ,
		0x01 , 0x00 ,
		0x00
	};
	T_MemoryInputStream stream( data , sizeof( data ) );
	T_SRDList list( SRDBinaryReadFrom( location , stream , false ) );
	M_CKLS_( 2 );
	M_CKTOK_( 0 , START , 8 );
	M_CKTOK_( 1 , END , 9 );
}

void SRDBinReaderTest::testIncompleteList( )
{
	const uint8_t data[] = { V1PROLOGUE_ ,
		0x01 , 0x00
	};
	T_MemoryInputStream stream( data , sizeof( data ) );
	try {
		SRDBinaryReadFrom( location , stream , false );
		CPPUNIT_FAIL( "X_SRDErrors exception expected" );
	} catch ( X_SRDErrors & e ) {
		M_CKERRS_( 1 );
		M_CKERR_( 0 , "unexpected end of file" , 10 );
	}
}

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

void SRDBinReaderTest::testString( )
{
	const uint8_t data[] = { V1PROLOGUE_ ,
		0x05 ,
		0x04 , 0x00 , 0x00 , 0x00 ,
		't' , 'e' , 's' , 't' ,
		0x00
	};
	T_MemoryInputStream stream( data , sizeof( data ) );
	T_SRDList list( SRDBinaryReadFrom( location , stream , false ) );
	M_CKLS_( 1 );
	M_CKTOK_( 0 , STRING , 8 );
	CPPUNIT_ASSERT( list[ 0 ].stringValue( ) == T_String( "test" ) );
}

void SRDBinReaderTest::testComment( )
{
	const uint8_t data[] = { V1PROLOGUE_ ,
		0x09 ,
		0x04 , 0x00 , 0x00 , 0x00 ,
		't' , 'e' , 's' , 't' ,
		0x00
	};
	T_MemoryInputStream stream( data , sizeof( data ) );
	T_SRDList list( SRDBinaryReadFrom( location , stream , false ) );
	M_CKLS_( 1 );
	M_CKTOK_( 0 , COMMENT , 8 );
	CPPUNIT_ASSERT( list[ 0 ].stringValue( ) == T_String( "test" ) );
}

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

void SRDBinReaderTest::testWordUnknown( )
{
	const uint8_t data[] = { V1PROLOGUE_ ,
		0x02 ,
		0x04 , 0x00 , 0x00 , 0x00 ,
		't' , 'e' , 's' , 't' ,
		0x00
	};
	T_MemoryInputStream stream( data , sizeof( data ) );
	T_SRDList list( SRDBinaryReadFrom( location , stream , false ) );
	M_CKLS_( 1 );
	M_CKTOK_( 0 , WORD , 8 );
	CPPUNIT_ASSERT( list[ 0 ].stringValue( ) == T_String( "test" ) );
}

void SRDBinReaderTest::testWordEmpty( )
{
	const uint8_t data[] = { V1PROLOGUE_ ,
		0x02 ,
		0x00 , 0x00 , 0x00 , 0x00 ,
		0x00
	};
	T_MemoryInputStream stream( data , sizeof( data ) );
	try {
		SRDBinaryReadFrom( location , stream , false );
		CPPUNIT_FAIL( "X_SRDErrors exception expected" );
	} catch ( X_SRDErrors & e ) {
		M_CKERRS_( 1 );
		M_CKERR_( 0 , "empty word" , 9 );
	}
}

void SRDBinReaderTest::testWordInvalid( )
{
	const uint8_t data[] = { V1PROLOGUE_ ,
		0x02 ,
		0x04 , 0x00 , 0x00 , 0x00 ,
		't' , ' ' , 's' , 't' ,
		0x00
	};
	T_MemoryInputStream stream( data , sizeof( data ) );
	try {
		SRDBinaryReadFrom( location , stream , false );
		CPPUNIT_FAIL( "X_SRDErrors exception expected" );
	} catch ( X_SRDErrors & e ) {
		M_CKERRS_( 1 );
		M_CKERR_( 0 , "invalid word" , 9 );
	}
}

void SRDBinReaderTest::testWordDup( )
{
	const uint8_t data[] = { V1PROLOGUE_ ,
		0x02 ,
		0x04 , 0x00 , 0x00 , 0x00 ,
		't' , 'e' , 's' , 't' ,
		0x02 ,
		0x04 , 0x00 , 0x00 , 0x00 ,
		't' , 'e' , 's' , 't' ,
		0x00
	};
	T_MemoryInputStream stream( data , sizeof( data ) );
	try {
		SRDBinaryReadFrom( location , stream , false );
		CPPUNIT_FAIL( "X_SRDErrors exception expected" );
	} catch ( X_SRDErrors & e ) {
		M_CKERRS_( 1 );
		M_CKERR_( 0 , "duplicate word" , 18 );
	}
}

void SRDBinReaderTest::testWordKnown( )
{
	const uint8_t data[] = { V1PROLOGUE_ ,
		0x02 ,
		0x04 , 0x00 , 0x00 , 0x00 ,
		't' , 'e' , 's' , 't' ,
		0x03 ,
		0x00 , 0x00 , 0x00 , 0x00 ,
		0x00
	};
	T_MemoryInputStream stream( data , sizeof( data ) );
	T_SRDList list( SRDBinaryReadFrom( location , stream , false ) );
	M_CKLS_( 2 );
	M_CKTOK_( 1 , WORD , 17 );
	CPPUNIT_ASSERT( list[ 1 ].stringValue( ) == T_String( "test" ) );
}

void SRDBinReaderTest::testWordBadID( )
{
	const uint8_t data[] = { V1PROLOGUE_ ,
		0x03 ,
		0x00 , 0x00 , 0x00 , 0x00 ,
		0x00
	};
	T_MemoryInputStream stream( data , sizeof( data ) );
	try {
		SRDBinaryReadFrom( location , stream , false );
		CPPUNIT_FAIL( "X_SRDErrors exception expected" );
	} catch ( X_SRDErrors & e ) {
		M_CKERRS_( 1 );
		M_CKERR_( 0 , "unregistered word" , 9 );
	}
}

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

void SRDBinReaderTest::testSpuriousVar( )
{
	const uint8_t data[] = { V1PROLOGUE_ ,
		0x04 ,
		0x00
	};
	T_MemoryInputStream stream( data , sizeof( data ) );
	try {
		SRDBinaryReadFrom( location , stream , false );
		CPPUNIT_FAIL( "X_SRDErrors exception expected" );
	} catch ( X_SRDErrors & e ) {
		M_CKERRS_( 1 );
		M_CKERR_( 0 , "spurious variable tag" , 8 );
	}
}

void SRDBinReaderTest::testVarUnknownWord( )
{
	const uint8_t data[] = { V1PROLOGUE_ ,
		0x04 ,
		0x02 , 0x04 , 0x00 , 0x00 , 0x00 , 't' , 'e' , 's' , 't' ,
		0x00
	};
	T_MemoryInputStream stream( data , sizeof( data ) );
	T_SRDList list( SRDBinaryReadFrom( location , stream , false ) );
	M_CKLS_( 1 );
	M_CKTOK_( 0 , VAR , 8 );
	CPPUNIT_ASSERT( list[ 0 ].stringValue( ) == T_String( "test" ) );
}

void SRDBinReaderTest::testVarKnownWord( )
{
	const uint8_t data[] = { V1PROLOGUE_ ,
		0x02 , 0x04 , 0x00 , 0x00 , 0x00 , 't' , 'e' , 's' , 't' ,
		0x04 ,
		0x03 , 0x00 , 0x00 , 0x00 , 0x00 ,
		0x00
	};
	T_MemoryInputStream stream( data , sizeof( data ) );
	T_SRDList list( SRDBinaryReadFrom( location , stream , false ) );
	M_CKLS_( 2 );
	M_CKTOK_( 1 , VAR , 17 );
	CPPUNIT_ASSERT( list[ 1 ].stringValue( ) == T_String( "test" ) );
}

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

void SRDBinReaderTest::testInt32( )
{
	const uint8_t data[] = { V1PROLOGUE_ ,
		0x06 , 0x04 , 0x03 , 0x02 , 0x01 ,
		0x00
	};
	T_MemoryInputStream stream( data , sizeof( data ) );
	T_SRDList list( SRDBinaryReadFrom( location , stream , false ) );
	M_CKLS_( 1 );
	M_CKTOK_( 0 , INT , 8 );
	CPPUNIT_ASSERT_EQUAL( int64_t( 0x01020304 ) , list[ 0 ].longValue( ) );
}

void SRDBinReaderTest::testInt64( )
{
	const uint8_t data[] = { V1PROLOGUE_ ,
		0x07 , 0x04 , 0x03 , 0x02 , 0x01 , 0x00 , 0x00 , 0x00 , 0x00 ,
		0x00
	};
	T_MemoryInputStream stream( data , sizeof( data ) );
	T_SRDList list( SRDBinaryReadFrom( location , stream , false ) );
	M_CKLS_( 1 );
	M_CKTOK_( 0 , LONG , 8 );
	CPPUNIT_ASSERT_EQUAL( int64_t( 0x01020304 ) , list[ 0 ].longValue( ) );
}

void SRDBinReaderTest::testFloat( )
{
	const uint8_t data[] = { V1PROLOGUE_ ,
		0x08 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0xf0 , 0x3f ,
		0x00
	};
	T_MemoryInputStream stream( data , sizeof( data ) );
	T_SRDList list( SRDBinaryReadFrom( location , stream , false ) );
	M_CKLS_( 1 );
	M_CKTOK_( 0 , FLOAT , 8 );
	CPPUNIT_ASSERT_EQUAL( 1.0 , list[ 0 ].floatValue( ) );
}

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

void SRDBinReaderTest::testBinaryEmpty( )
{
	const uint8_t data[] = { V1PROLOGUE_ ,
		0x0a , 0x00 , 0x00 , 0x00 , 0x00 ,
		0x00
	};
	T_MemoryInputStream stream( data , sizeof( data ) );
	T_SRDList list( SRDBinaryReadFrom( location , stream , false ) );
	M_CKLS_( 1 );
	M_CKTOK_( 0 , BINARY , 8 );
	CPPUNIT_ASSERT_EQUAL( size_t( 0 ) , list[ 0 ].binary( ).size( ) );
}

void SRDBinReaderTest::testBinarySizeOnly( )
{
	const uint8_t data[] = { V1PROLOGUE_ ,
		0x0a , 0x08 , 0x00 , 0x00 , 0x00 ,
	};
	T_MemoryInputStream stream( data , sizeof( data ) );
	T_SRDList list;
	try {
		list = SRDBinaryReadFrom( location , stream , false );
		CPPUNIT_FAIL( "X_SRDErrors exception expected" );
	} catch ( X_SRDErrors & e ) {
		M_CKERRS_( 1 );
		M_CKERR_( 0 , "unexpected end of file" , 13 );
	}
}

void SRDBinReaderTest::testBinaryData( )
{
	const uint8_t data[] = { V1PROLOGUE_ ,
		0x0a , 0x08 , 0x00 , 0x00 , 0x00 ,
			0x01 , 0x02 , 0x03 , 0x04 ,
			0x05 , 0x06 , 0x07 , 0x08 ,
		0x00
	};
	T_MemoryInputStream stream( data , sizeof( data ) );
	T_SRDList list( SRDBinaryReadFrom( location , stream , false ) );
	M_CKLS_( 1 );
	M_CKTOK_( 0 , BINARY , 8 );
	CPPUNIT_ASSERT_EQUAL( size_t( 8 ) , list[ 0 ].binary( ).size( ) );
	for ( uint8_t i = 0 ; i < 8 ; i ++ ) {
		CPPUNIT_ASSERT_EQUAL( uint8_t( i + 1 ) ,
				list[ 0 ].binary( )[ i ] );
	}
}