#include <catch2/catch.hpp>

#include <common/SparseBoolVector.hpp>
#include <common/xml/SparseBoolVector.hpp>

#include <factory/XmlDataFactory.hpp>

static ext::vector < bool > createTestVectorBool ( size_t size ) {
	unsigned long long shadow = 0x2A76B147D6521C87ULL;
	ext::vector < bool > ref;
	while ( ref.size ( ) < size ) {
		for ( size_t i = 0; i < sizeof ( unsigned long long ) * 8; ++i ) {
			ref.push_back ( ( bool ) ( shadow & ( 1ULL << i ) ) );
		}
		for ( size_t i = 0; i < 100; ++i ) {
			ref.push_back ( false );
		}
	}
	ref.resize ( size );
	return ref;
}


static void testOffset2 ( size_t size, size_t shift ) {
	ext::vector < bool > ref = createTestVectorBool ( size );

	common::SparseBoolVector data = ref;
	data <<= shift;
	ref <<= shift;

	/*	std::cout << "size = " << size << std::endl;
		std::cout << "shift = " << shift << std::endl;
		std::cout << "data = " << data.data ( ) << std::endl;
		std::cout << "ref = " << common::SparseBoolVector ( ref ).data ( ) << std::endl;*/

	CHECK ( data == common::SparseBoolVector ( ref ) );
}

void testIterator ( size_t size ) {
	ext::vector < bool > ref = createTestVectorBool ( size );
	common::SparseBoolVector data ( ref );

	size_t count = 0;
	for ( bool boolean : ref ) {
		count += boolean;
	}

	for ( size_t onePosition : data ) {
		--count;
		CHECK ( ref [ onePosition ] );
	}

	CHECK ( count == 0 );
}


TEST_CASE ( "Sparse Bool Vector", "[unit][data][string]" ) {
	SECTION ( "Compatibility with Vector" ) {
		ext::vector < bool > orig;
		for ( int i = 0; i < 1000; ++i ) {
			orig.push_back ( false );
		}
		for ( int i = 0; i < 20; ++i ) {
			orig.push_back ( true );
			orig.push_back ( true );
			orig.push_back ( false );
			orig.push_back ( true );
			orig.push_back ( true );
			orig.push_back ( false );
			orig.push_back ( true );
			orig.push_back ( false );
			orig.push_back ( false );
			orig.push_back ( true );
		}
		for ( int i = 0; i < 1000; ++i ) {
			orig.push_back ( false );
		}
		orig.push_back ( true );
		orig.push_back ( true );
		orig.push_back ( false );
		orig.push_back ( true );
		orig.push_back ( true );
		orig.push_back ( false );
		orig.push_back ( true );
		orig.push_back ( false );
		orig.push_back ( false );
		orig.push_back ( true );

		common::SparseBoolVector compressed ( orig );
		ext::vector < bool > copy ( compressed );

		/*	std::cout << orig << std::endl;
			std::cout << copy << std::endl;
			std::cout << compressed.data ( ) << std::endl;*/

		CHECK ( orig == copy );
	}

	SECTION ( "Shifts 4" ) {
		for ( size_t shift = 0 ; shift < 100; ++ shift ) {
			testOffset2 ( 0, shift );
			testOffset2 ( 31, shift );
			testOffset2 ( 32, shift );
			testOffset2 ( 33, shift );
			testOffset2 ( 63, shift );
			testOffset2 ( 64, shift );
			testOffset2 ( 65, shift );
			testOffset2 ( 1023, shift );
			testOffset2 ( 1024, shift );
			testOffset2 ( 1025, shift );
			testOffset2 ( 2000, shift );

		}
	}

	SECTION ( "Shifts 5" ) {
		unsigned long long shadow = 0x2A76B147D6521C87ULL;
		common::SparseBoolVector data;

		for ( unsigned i = 0; i < sizeof ( unsigned long long ) * 8; ++ i ) {
			data.push_back ( ( bool ) ( shadow & ( 1ULL << i ) ) );
		}
		for ( unsigned i = 0; i < sizeof ( unsigned long long ) * 8; ++ i ) {
			data.push_back ( ( bool ) ( shadow & ( 1ULL << i ) ) );
		}
		for ( unsigned i = 0; i < sizeof ( unsigned long long ) * 8; ++ i ) {
			data.push_back ( ( bool ) ( shadow & ( 1ULL << i ) ) );
		}

		common::SparseBoolVector data2 = data;
		data2 <<= 64;

		data <<= 32;
		data <<= 16;
		data <<= 8;
		data <<= 4;
		data <<= 2;
		data <<= 1;
		data <<= 1;

		/*	std::cout << "data  = " << data.data ( )  << std::endl;
			std::cout << "data2 = " << data2.data ( ) << std::endl;*/

		data2 <<= 1;
		data2 <<= 2;
		data2 <<= 4;
		data2 <<= 8;
		data2 <<= 16;
		data2 <<= 32;
		data2 <<= 1;

		data <<= 27;
		data <<= 17;
		data <<= 13;
		data <<= 7;

		/*	std::cout << "data  = " << data.data ( )  << std::endl;
			std::cout << "data2 = " << data2.data ( ) << std::endl;*/

		CHECK ( data2 == data );
	}

	SECTION ( "Ones Iterator" ) {
		for ( size_t i = 0; i < 1000; i++ ) {
			testIterator ( i );
		}

		common::SparseBoolVector empty;
		CHECK ( empty.begin ( ) == empty.end ( ) );
		empty.push_back ( false );
		CHECK ( empty.begin ( ) == empty.end ( ) );
	}

	SECTION ( "Compare" ) {
		common::SparseBoolVector first;
		common::SparseBoolVector second;
		common::SparseBoolVector third;
		common::SparseBoolVector fourth;
		common::SparseBoolVector fifth;
		common::SparseBoolVector sixth;

		first.push_back ( false );

		second.push_back ( true );

		third.push_back ( false );
		third.push_back ( false );

		fourth.push_back ( true );
		fourth.push_back ( false );

		fifth.push_back ( false );
		fifth.push_back ( true );

		sixth.push_back ( true );
		sixth.push_back ( true );

		CHECK ( first == first );
		CHECK ( first < second );

		CHECK ( second > first );
		CHECK ( second == second );

		CHECK ( third == third );
		CHECK ( third < fourth );
		CHECK ( third < fifth );
		CHECK ( third < sixth );

		CHECK ( fourth > third );
		CHECK ( fourth == fourth );
		CHECK ( fourth < fifth );
		CHECK ( fourth < sixth );

		CHECK ( fifth > third );
		CHECK ( fifth > fourth );
		CHECK ( fifth == fifth );
		CHECK ( fifth < sixth );

		CHECK ( sixth > third );
		CHECK ( sixth > fourth );
		CHECK ( sixth > fifth );
		CHECK ( sixth == sixth );
	}

	SECTION ( "Array Subscript" ) {
		{
			ext::vector < bool > ref = createTestVectorBool ( 2000 );
			common::SparseBoolVector data ( ref );

			/*	std::cout << ref << std::endl;
				std::cout << data.data ( ) << std::endl;*/

			for ( size_t i = 0; i < ref.size ( ); ++ i ) {
				INFO ( "Failed on index " << i );
				CHECK ( ( bool ) ref [ i ] == ( bool ) data [ i ] );
			}
		}
		{
			ext::vector < bool > ref;
			common::SparseBoolVector data;

			ref.resize ( 2000 );
			data.resize ( 2000 );

			ref[520] = true;
			data[520] = true;

			CHECK ( ( ext::vector < bool > ) data == ref );

			ref[521] = true;
			data[521] = true;

			CHECK ( ( ext::vector < bool > ) data == ref );

			ref[128] = true;
			data[128] = true;

			CHECK ( ( ext::vector < bool > ) data == ref );

			ref[255] = true;
			data[255] = true;

			CHECK ( ( ext::vector < bool > ) data == ref );

			ref[256] = true;
			data[256] = true;

			CHECK ( ( ext::vector < bool > ) data == ref );
		}

		SECTION ( "Overloaded bitwise and" ) {
			ext::vector < bool > first;
			ext::vector < bool > second;
			ext::vector < bool > ref;

			first.resize ( 2000 );
			second.resize ( 2000 );
			ref.resize ( 2000 );

			first[520] = first[1] = first[3] = first[512] = first[511] = first[128] = first[127] = first[17] = first[53] = first[1999] = true;
			second[80] = second[1] = second[5] = second[51] = second[5] = second[7] = second[127] = second[1] = second[77] = second[999] = true;

			ref[1] = ref[127] = true;

			common::SparseBoolVector data;
			CHECK ( ( first & second ) == ref );
		}

		SECTION ( "Xml Api" ) {
			ext::vector < bool > ref = createTestVectorBool ( 2000 );
			common::SparseBoolVector data ( ref );

			ext::deque<sax::Token> tokens = factory::XmlDataFactory::toTokens( data );
			std::string tmp = sax::SaxComposeInterface::composeMemory ( tokens );
			//	std::cout << tmp << std::endl;

			ext::deque<sax::Token> tokens2 = sax::SaxParseInterface::parseMemory ( tmp );
			common::SparseBoolVector data2 = factory::XmlDataFactory::fromTokens ( std::move ( tokens2 ) );

			/*	std::cout << data.data ( ) << std::endl;
				std::cout << data2.data ( ) << std::endl;*/
			CHECK( data == data2 );
		}
	}
}