#include "global.h"
#include "RageLog.h"
#include "RageFile.h"
#include "RageUtil.h"
#include "RageUtil_FileDB.h"
#include "test_misc.h"

bool CreateTestFiles = false;

void CreateBinaryTestFile( RageFile &f, int size )
{
	char c = 0;
	while( size-- )
	{
		f.Write( ssprintf("%c", c) );
		++c;
	}
}

void CreateBinaryTestFile( RString fn, int size )
{
	RageFile f;
	f.Open( fn, RageFile::WRITE );
	CreateBinaryTestFile( f, size );
	f.Close();

	if( !f.Open(fn) )
	{
		LOG->Warn("CreateBinaryTestFile: error reopening %s: %s", fn.c_str(), f.GetError().c_str() );
		return;
	}

	int test = f.GetFileSize();
	if( test != size )
		LOG->Warn("CreateBinaryTestFile: f.GetFileSize ret %i, exp %i", test, size );
}


bool CheckData( const RString &buf, int offset )
{
	char c = offset%256;
	
	for( unsigned i = 0; i < buf.size(); ++i )
	{
		if( buf[i] != c )
			return false;
		++c;
	}
	return true;
}

int TestBinaryRead( RageFile &test, int size, bool AllowEOF, int ExpectedFilePos, int TestDataPos )
{
	char buf[4096];
	ASSERT( size <= (int) sizeof(buf) );
	int ret = test.Read( buf, size );
	if( ret != size )
	{
		if( AllowEOF )
			return 0;
		LOG->Warn("FAIL");
		return -1;
	}

	if( !CheckData( RString(buf, ret), TestDataPos ) )
	{
		LOG->Warn("TestBinaryRead: check failed (size %i, %i,%i)",
				size, ExpectedFilePos, TestDataPos );
		return -1;
	}

	if( test.Tell() != ExpectedFilePos+ret )
	{
		LOG->Warn( "Simple Tell() failed (got %i, expected %i)", test.Tell(), ret );
		return -1;
	}
	return 1;
}

RString MakeTextTestLine( int line, int size )
{
	RString ret = ssprintf( "%02i", line );
	ret.append( RString( size-ret.size(), 'x' ) );
	return ret;
}

bool CheckTextData( const RString &buf, int LineSize, int size )
{
	return buf == MakeTextTestLine( LineSize, size );
}

void TestText( int LineSize, bool DOS )
{
	const unsigned NumLines = 50;
	
	RString filename = "test.text";
	if( DOS )
		filename += ".DOS";
	filename += ssprintf( ".%i", LineSize );
	if( CreateTestFiles )
	{
		RageFile f;
		f.Open( filename, RageFile::WRITE );

		for( unsigned line = 0; line < NumLines; ++line )
		{
			const RString TestLine = MakeTextTestLine( line, LineSize );
			f.Write( TestLine );
			if( DOS )
				f.Write( "\r" );
			f.Write( "\n" );
		}
		
		/* Write a binary test block at the end. */
		CreateBinaryTestFile( f, 4096 );

		/* Write a text line, with no NL. */
		f.Write( "final" );

		return;
	}

	RageFile test;
	if( !test.Open(filename) )
	{
		LOG->Warn( "Open \"%s\" failed: %s", filename.c_str(), test.GetError().c_str() );
		exit(1);
	}
		
	int ExpectedPos = 0;
	for( unsigned line = 0; line < NumLines; ++line )
	{
		RString buf;
		if( test.GetLine( buf ) <= 0 )
		{
			LOG->Warn("Text read failure, line %i, size %i, DOS %i",
					line, LineSize, DOS );
			return;
		}

		/* If we successfully got data, we should not be at EOF. */
		if( test.AtEOF() )
		{
			LOG->Warn("Unexpected EOF in text read, line %i, size %i, DOS %i",
					line, LineSize, DOS );
			return;
		}

		if( !CheckTextData( buf, line, LineSize ) )
		{
			LOG->Warn("Text read check failure, line %i, size %i, DOS %i: '%s'",
					line, LineSize, DOS, buf.c_str() );
			return;
		}
		ExpectedPos += buf.size()+1;
		if( DOS )
			++ExpectedPos;
	}
	if( ExpectedPos != test.Tell() )
	{
		LOG->Warn("Text read pos failure, size %i, DOS %i: exp %i, got %i",
				LineSize, DOS, ExpectedPos, test.Tell() );
		return;
	}

	/* Test the binary block.  Do it in a couple reads, to force it to read from
	 * the buffer and the file more than once. */
	TestBinaryRead( test, 1024, true, test.Tell(), 1024*0 );
	TestBinaryRead( test, 1024, true, test.Tell(), 1024*1 );
	TestBinaryRead( test, 1024, true, test.Tell(), 1024*2 );
	TestBinaryRead( test, 1024, true, test.Tell(), 1024*3 );

	{
		RString buf;
		if( test.GetLine( buf ) <= 0 )
		{
			LOG->Warn( "Unexpected EOF in final line read, DOS %i", DOS );
			return;
		}

		if( buf != "final" )
		{
			LOG->Warn( "Final text read check failure, DOS %i: '%s'",
					DOS, buf.c_str() );
			return;
		}
		if( test.AtEOF() )
		{
			LOG->Warn( "Unexpected EOF (2) in final text read, DOS %i", DOS );
			return;
		}
	}

	{
		RString buf;
		if( test.GetLine( buf ) != 0 )
		{
			LOG->Warn( "Expected EOF in final text read, but didn't get it" );
			return;
		}

		if( !test.AtEOF() )
		{
			LOG->Warn( "Expected EOF (2) in final text read, but didn't get it" );
			return;
		}
	}
}

void TestText()
{
	/* Test with these line sizes.  The internal buffer size is 256, so test line sizes
	 * around there carefully. */
	int sizes[] = {
		4,
		7,
		100,
		200,
		254,
		255,
		256,
		257,
		258,
		500,
		-1
	};

	for( int s = 0; sizes[s] != -1; ++s )
	{
		TestText( sizes[s], false );
		TestText( sizes[s], true );
	}
}

void TestSeek( bool relative )
{
	const RString TestLine = "Hello World\n";
	/* Print a couple kb of text.  Make sure this is big enough that the binary
	 * test data after it won't start in the buffer; if that happens, the seek
	 * will be optimized out. */
	RString junk;
	for( int lines = 0; lines < 128; ++lines )
		junk += "XXXXXXXXX\n";

	if( CreateTestFiles )
	{
		/* Output a line of text, followed by some junk, followed by binary
		 * test data. */
		RageFile f;
		f.Open( "test.seek", RageFile::WRITE );
		f.Write( TestLine );
		f.Write( junk );
		CreateBinaryTestFile( f, 1024 );
		return;
	}
		
	/* Read the line of text.  This will result in some of the junk being
	 * read into the buffer. */
	RageFile test;
	test.Open("test.seek");
	RString line;
	test.GetLine( line );

	/* Run the absolute seek test twice. */
	for( int pass = 0; pass < 2; ++pass )
	{
		/* Seek to the binary test data. */
		if( relative && pass > 0 )
			break;

		int ret;
		if( relative )
			ret = test.Seek( test.Tell() + junk.size() );
		else
			ret = test.Seek( TestLine.size()+junk.size() );

		if( ret != int(TestLine.size()+junk.size()) )
		{
			LOG->Warn( "Seek(%i) failed: got %i", TestLine.size()+junk.size(), ret );
			return;
		}

		/* Check the binary test data (making sure we don't get any junk). */
		TestBinaryRead( test, 256, false, test.Tell(), 256*0 );
		TestBinaryRead( test, 256, false, test.Tell(), 256*1 );
		TestBinaryRead( test, 256, false, test.Tell(), 256*2 );
		TestBinaryRead( test, 256, false, test.Tell(), 256*3 );

		/* Check EOF. */
		char c;
		ret = test.Read( &c, 1 );
		if( ret != 0 )
		{
			LOG->Warn( "Read(1) failed: got %i, expected 0", ret );
			return;
		}
		if( !test.AtEOF() )
		{
			LOG->Warn( "AtEOF returned false, expected true");
			return;
		}
	}

	test.Seek( 5 );
	if( test.AtEOF() )
	{
		LOG->Warn( "AtEOF returned true, expected false");
		return;
	}

	/* Make sure a read after seeking far past EOF returns 0. */
	test.Seek( 9999999 );
	char c;
	int ret = test.Read( &c, 1 );
	if( ret != 0 )
	{
		LOG->Warn( "Read(1) failed: got %i, expected 0", ret );
		return;
	}
	if( !test.AtEOF() )
	{
		LOG->Warn( "AtEOF returned false, expected true");
		return;
	}
}



#include "RageFileDriverMemory.h"
#include "RageFileDriverDeflate.h"
void CheckDeflate( RageFileObjMem &data, int iSize, unsigned iInputCRC, int iBlockSize )
{
	data.Seek(0);

	RString foo2;

	RageFileObjInflate infl( &data, iSize );
	infl.EnableCRC32();
	int iGot = 0;
	while( iGot < iSize )
	{
		int iRet = infl.Read( foo2, iBlockSize );
		if( iRet == 0 )
		{
			LOG->Warn( "Premature EOF (iSize %i, iBlockSize %i)", iSize, iBlockSize );
			return;
		}
			
		iGot += iRet;
	}

	unsigned iOutputCRC;
	if( !infl.GetCRC32(&iOutputCRC) )
	{
		LOG->Warn( "Inflate GetCRC32 failed" );
		return;
	}
	if( iInputCRC != iOutputCRC )
	{
		LOG->Warn( "Expected CRC %08x, got %08x", iInputCRC, iOutputCRC );
		return;
	}

	if( iSize != iGot )
	{
		LOG->Warn( "Expected %i, got %i", iSize, iGot );
		return;
	}
}

void TestDeflate()
{
	RageFileObjMem data;
	RageFileObjDeflate defl( &data );
	defl.EnableCRC32();

	int iSize = 0;
	while( iSize < 1024*512 )
		iSize += defl.Write("test foo bar fah");
	defl.Flush();

	unsigned iOutputCRC;
	if( !defl.GetCRC32(&iOutputCRC) )
	{
		LOG->Warn( "Deflate GetCRC32 failed" );
		return;
	}

	CheckDeflate( data, iSize, iOutputCRC, 1 );
	CheckDeflate( data, iSize, iOutputCRC, 1024 );
	CheckDeflate( data, iSize, iOutputCRC, 1024*4 );
	CheckDeflate( data, iSize, iOutputCRC, iSize-1 );
	CheckDeflate( data, iSize, iOutputCRC, iSize );
}

int main( int argc, char *argv[] )
{
	test_handle_args( argc, argv );

	optind = 0; // force getopt to reset
	while( 1 )
	{
		opterr = 0;
		int opt = getopt( argc, argv, "c" );
		if( opt == -1 )
			break;
		switch( opt )
		{
		case 'c':
			CreateTestFiles = true;
			break;
		}

	}

	test_init();

	if( GetCommandlineArgument("zlib") )
	{
		TestDeflate();
		test_deinit();
		return 0;
	}
			
	if( CreateTestFiles )
		CreateBinaryTestFile( "test.binary", 4096 );
	else
	{
		/* Test binary reading. */
		RageFile test;
		if( !test.Open("test.binary") )
		{
			LOG->Warn( "Open \"test.binary\" failed: %s", test.GetError().c_str() );
			exit(1);
		}

		TestBinaryRead( test, 256, false, 0, 0 );

		/* Test simple re-opening. */
		test.Close();
		test.Open("test.binary");

		/* We should now be back at 0. */
		if( test.Tell() != 0 )
		{
			LOG->Warn( "Simple Tell() after reopen failed (got %i, expected 0)", test.Tell() );
			exit(1);
		}

		/* Test reading further, with various block sizes. */
		while( 1 )
		{
			if( TestBinaryRead( test, 85, true, test.Tell(), test.Tell() ) == 0 )
				break;
		}
	}

	/* Test text reading. */
	TestText();

	/* Test seeking. */
	TestSeek( true );
	TestSeek( false );

	test_deinit();

	exit(0);
}

