#include <lw/lib/VFS.hh>
#include <cppunit/extensions/HelperMacros.h>
using namespace lw;


/*= VFSPathTest ===============================================================*/

class VFSPathTest : public CppUnit::TestFixture
{
	CPPUNIT_TEST_SUITE( VFSPathTest );
		CPPUNIT_TEST( testUnknown );
		CPPUNIT_TEST( testRoot );
		CPPUNIT_TEST( testAbsolute );
		CPPUNIT_TEST( testRelative );

		CPPUNIT_TEST( testValid );
		CPPUNIT_TEST( testInvalid );

		CPPUNIT_TEST( testDotDirs );
		CPPUNIT_TEST( testDotInNames );
		CPPUNIT_TEST( testLeadingDots );

		CPPUNIT_TEST( testTrailingSlash );
		CPPUNIT_TEST( testExtraSlashes );

		CPPUNIT_TEST( testEquals );
		CPPUNIT_TEST( testToString );
		CPPUNIT_TEST( testAppend );
		CPPUNIT_TEST( testNormalize );
	CPPUNIT_TEST_SUITE_END( );

public:
	void testUnknown( );
	void testRoot( );
	void testAbsolute( );
	void testRelative( );

	void testValid( );
	void testInvalid( );

	void testDotDirs( );
	void testDotInNames( );
	void testLeadingDots( );

	void testTrailingSlash( );
	void testExtraSlashes( );

	void testEquals( );
	void testToString( );
	void testAppend( );
	void testNormalize( );
};

CPPUNIT_TEST_SUITE_REGISTRATION( VFSPathTest );

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

void VFSPathTest::testUnknown( )
{
	{
		T_VFSPath path;
		CPPUNIT_ASSERT( path.type( ) == E_VFSPathType::UNKNOWN );
		CPPUNIT_ASSERT( path.elements( ) == 0 );
	}
	{
		T_VFSPath path( "" );
		CPPUNIT_ASSERT( path.type( ) == E_VFSPathType::UNKNOWN );
		CPPUNIT_ASSERT( path.elements( ) == 0 );
	}
}

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

void VFSPathTest::testRoot( )
{
	T_VFSPath path( "/" );
	CPPUNIT_ASSERT( path.type( ) == E_VFSPathType::ABSOLUTE );
	CPPUNIT_ASSERT( path.elements( ) == 0 );
}

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

void VFSPathTest::testAbsolute( )
{
	T_VFSPath path( "/this/is/a/test" );
	CPPUNIT_ASSERT( path.type( ) == E_VFSPathType::ABSOLUTE );
	CPPUNIT_ASSERT( path.elements( ) == 4 );
	CPPUNIT_ASSERT( path[ 0 ] == "this" );
	CPPUNIT_ASSERT( path[ 1 ] == "is" );
	CPPUNIT_ASSERT( path[ 2 ] == "a" );
	CPPUNIT_ASSERT( path[ 3 ] == "test" );
}

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

void VFSPathTest::testRelative( )
{
	T_VFSPath path( "this/is/a/test" );
	CPPUNIT_ASSERT( path.type( ) == E_VFSPathType::RELATIVE );
	CPPUNIT_ASSERT( path.elements( ) == 4 );
	CPPUNIT_ASSERT( path[ 0 ] == "this" );
	CPPUNIT_ASSERT( path[ 1 ] == "is" );
	CPPUNIT_ASSERT( path[ 2 ] == "a" );
	CPPUNIT_ASSERT( path[ 3 ] == "test" );
}

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

void VFSPathTest::testValid( )
{
	char const* const NAMES[] = {
		"test" , "123" , "test-123" , "-test-123-" , "test_123" , "_" , "-"
	};

	for ( size_t i = 0 ; i < sizeof( NAMES ) / sizeof( NAMES[ 0 ] ) ; i ++ ) {
		T_VFSPath path( NAMES[ i ] );
		CPPUNIT_ASSERT( path.type( ) == E_VFSPathType::RELATIVE );
	}
}

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

void VFSPathTest::testInvalid( )
{
	char const* const NAMES[] = {
		"bad name" , "bad\\name" , "bad\u00e9name" , "bad\x09name" ,
		"BadName"
	};

	for ( size_t i = 0 ; i < sizeof( NAMES ) / sizeof( NAMES[ 0 ] ) ; i ++ ) {
		T_VFSPath path( NAMES[ i ] );
		CPPUNIT_ASSERT( path.type( ) == E_VFSPathType::INVALID );
	}
}

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

void VFSPathTest::testDotDirs( )
{
	{
		T_VFSPath path( "./.././test" );
		CPPUNIT_ASSERT( path.type( ) == E_VFSPathType::RELATIVE );
		CPPUNIT_ASSERT( path.elements( ) == 4 );
		CPPUNIT_ASSERT( path[ 0 ] == "." );
		CPPUNIT_ASSERT( path[ 1 ] == ".." );
		CPPUNIT_ASSERT( path[ 2 ] == "." );
		CPPUNIT_ASSERT( path[ 3 ] == "test" );
	}
}

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

void VFSPathTest::testDotInNames( )
{
	{
		T_VFSPath path( "test.test/test...test/test." );
		CPPUNIT_ASSERT( path.type( ) == E_VFSPathType::RELATIVE );
		CPPUNIT_ASSERT( path.elements( ) == 3 );
		CPPUNIT_ASSERT( path[ 0 ] == "test.test" );
		CPPUNIT_ASSERT( path[ 1 ] == "test...test" );
		CPPUNIT_ASSERT( path[ 2 ] == "test." );
	}
}

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

void VFSPathTest::testLeadingDots( )
{
	{
		T_VFSPath path( ".test" );
		CPPUNIT_ASSERT( path.type( ) == E_VFSPathType::INVALID );
		CPPUNIT_ASSERT( path.elements( ) == 1 );
		CPPUNIT_ASSERT( path[ 0 ] == ".test" );
	}
}

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

void VFSPathTest::testTrailingSlash( )
{
	T_VFSPath path( "/this/is/a/test/" );
	CPPUNIT_ASSERT( path.type( ) == E_VFSPathType::ABSOLUTE );
	CPPUNIT_ASSERT( path.elements( ) == 4 );
	CPPUNIT_ASSERT( path[ 0 ] == "this" );
	CPPUNIT_ASSERT( path[ 1 ] == "is" );
	CPPUNIT_ASSERT( path[ 2 ] == "a" );
	CPPUNIT_ASSERT( path[ 3 ] == "test" );
}

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

void VFSPathTest::testExtraSlashes( )
{
	T_VFSPath path( "///this////is//a////test///" );
	CPPUNIT_ASSERT( path.type( ) == E_VFSPathType::ABSOLUTE );
	CPPUNIT_ASSERT( path.elements( ) == 4 );
	CPPUNIT_ASSERT( path[ 0 ] == "this" );
	CPPUNIT_ASSERT( path[ 1 ] == "is" );
	CPPUNIT_ASSERT( path[ 2 ] == "a" );
	CPPUNIT_ASSERT( path[ 3 ] == "test" );
}

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

void VFSPathTest::testEquals( )
{
	const T_VFSPath PATHS[] = {
		{} , ".bad" , "test" , "/test" , "this/is/a/test"
	};
	const auto n( sizeof( PATHS ) / sizeof( PATHS[ 0 ] ) );

	T_VFSPath copies[ n ];
	for ( size_t i = 0 ; i < n ; i ++ ) {
		copies[ i ] = PATHS[ i ];
	}

	for ( size_t i = 0 ; i < n ; i ++ ) {
		for ( size_t j = 0 ; j < n ; j ++ ) {
			if ( i == j ) {
				CPPUNIT_ASSERT( PATHS[ i ] == PATHS[ j ] );
				CPPUNIT_ASSERT( PATHS[ i ] == copies[ j ] );
			} else {
				CPPUNIT_ASSERT( PATHS[ i ] != PATHS[ j ] );
				CPPUNIT_ASSERT( PATHS[ i ] != copies[ j ] );
			}
		}
	}
}

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

void VFSPathTest::testToString( )
{
	char const* const PATHS[] = {
		"/test" , "test" , "test/test" , "/test/test" , "" , ".bad"
	};
	for ( size_t i = 0 ; i < sizeof( PATHS ) / sizeof( PATHS[ 0 ] ) ; i ++ ) {
		T_VFSPath path( PATHS[ i ] );
		CPPUNIT_ASSERT( T_String( path ) == PATHS[ i ] );
	}
}

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

void VFSPathTest::testAppend( )
{
	const T_VFSPath paths[] = {
		"/test" , "test" , ".bad" , {}
	};
	const E_VFSPathType combos[] = {
		// Parent is absolute
		E_VFSPathType::ABSOLUTE , E_VFSPathType::ABSOLUTE ,
		E_VFSPathType::INVALID , E_VFSPathType::UNKNOWN ,
		// Parent is relative
		E_VFSPathType::RELATIVE , E_VFSPathType::RELATIVE ,
		E_VFSPathType::INVALID , E_VFSPathType::UNKNOWN ,
		// Parent is invalid
		E_VFSPathType::INVALID , E_VFSPathType::INVALID ,
		E_VFSPathType::INVALID , E_VFSPathType::UNKNOWN ,
		// Parent is unknown
		E_VFSPathType::UNKNOWN , E_VFSPathType::UNKNOWN ,
		E_VFSPathType::UNKNOWN , E_VFSPathType::UNKNOWN
	};

	int cid = 0;
	for ( int i = 0 ; i < 4 ; i ++ ) {
		for ( int j = 0 ; j < 4 ; j ++ , cid ++ ) {
			T_VFSPath parent( paths[ i ] ) , child( paths[ j ] );
			T_VFSPath path( parent , child );
			//printf( "i = %d j = %d t = %d\n" , i , j , int( path.type( ) ) );
			CPPUNIT_ASSERT( path.type( ) == combos[ cid ] );
			if ( combos[ cid ] == E_VFSPathType::UNKNOWN ) {
				CPPUNIT_ASSERT( path.elements( ) == 0 );
			} else {
				CPPUNIT_ASSERT( path.elements( ) == parent.elements( ) + child.elements( ) );
				CPPUNIT_ASSERT( path[ 0 ] == parent[ 0 ] );
				CPPUNIT_ASSERT( path[ 1 ] == child[ 0 ] );
			}
		}
	}
}

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

void VFSPathTest::testNormalize( )
{
	const T_VFSPath INPUTS[] = {
		{} , ".bad" , "/test" , "test" ,
		"./test" ,
		"/." ,
		"/.." ,
		"." ,
		".." ,
		"wat/../wet/../wut" ,
		"wat/../.." ,
		"wat/.." ,
		"/wat/../.."
	};
	const T_VFSPath EXPECTED[] = {
		{} , {} , "/test" , "test" ,
		"test" ,
		"/" ,
		"/" ,
		"." ,
		".." ,
		"wut" ,
		".." ,
		"." ,
		"/"
	};
	static_assert( sizeof( INPUTS ) / sizeof( INPUTS[ 0 ] )
				== sizeof( EXPECTED ) / sizeof( EXPECTED[ 0 ] ) ,
		       "yo, your test sucks!" );

	for ( size_t i = 0 ; i < sizeof( INPUTS ) / sizeof( INPUTS[ 0 ] ) ; i ++ ) {
		CPPUNIT_ASSERT( T_String( INPUTS[ i ].normalize( ) ) == T_String( EXPECTED[ i ] ) );
	}
}