#include <catch2/catch.hpp>
#include <alib/vector>
#include <alib/type_traits>
#include <alib/algorithm>

namespace {
	class Moveable {
		int& m_moves;
		int& m_copies;

		public:
		Moveable(int& moves, int& copies) : m_moves(moves), m_copies(copies) {
			m_moves = 0;
			m_copies = 0;
		}

		Moveable(const Moveable& src) : m_moves(src.m_moves), m_copies(src.m_copies) {
			m_copies++;
		}

		Moveable(Moveable&& src) : m_moves(src.m_moves), m_copies(src.m_copies) {
			m_moves++;
		}

		Moveable & operator = ( const Moveable & ) {
			m_copies ++;
			return * this;
		}

		Moveable & operator = ( Moveable && ) {
			m_moves ++;
			return * this;
		}

		bool operator<(const Moveable&) const {
			return false;
		}
	};
}

TEST_CASE ( "Vector", "[unit][std][container]" ) {
	SECTION ( "Properties test" ) {
		ext::vector<int> data = {1, 2, 3, 4};
		ext::vector<int> dataRef = {1, 2, 3, 4};

		const ext::vector<int>& constData = data;

		auto iter1 = data.erase ( data.begin ( ) );
		CHECK ( iter1 == data.begin ( ) );
		auto iter2 = data.erase ( data.rbegin ( ) );
		CHECK ( iter2 == data.rbegin ( ) );

		CHECK ( data.size ( ) == 2 );

		auto iter3 = data.insert ( data.begin ( ), 1 );
		CHECK ( iter3 == data.begin ( ) );
		auto iter4 = data.insert ( data.rbegin ( ), 4 );
		CHECK ( iter4 == data.rbegin ( ) );

		CHECK ( data.size ( ) == 4 );

		CHECK((std::is_same<decltype(constData[0]), const int&>::value));

		CHECK ( data == dataRef );

		int moves;
		int copies;

		ext::vector < Moveable > vec;
		vec.push_back ( Moveable ( moves, copies ) );
		vec.push_back ( Moveable ( moves, copies ) );
		vec.push_back ( Moveable ( moves, copies ) );
		vec.push_back ( Moveable ( moves, copies ) );

		vec.erase ( ext::unique ( vec.begin ( ), vec.end ( ), [ ] ( const Moveable &, const Moveable & ) { return true; } ), vec.end ( ) );

		CHECK ( vec.size ( ) == 1 );

		CHECK(copies == 0);
	}

	SECTION ( "Vector<bool>" ) {
		ext::vector < bool > A;
		A.push_back ( true );
		A.push_back ( false );
		A.push_back ( true );
		A.push_back ( false );

		ext::vector < bool > B;
		B.push_back ( true );
		B.push_back ( true );
		B.push_back ( false );
		B.push_back ( false );

		{
			ext::vector < bool > C = A | B;
			ext::vector < bool > D = A;
			D |= B;

			ext::vector < bool > X;
			X.push_back ( true );
			X.push_back ( true );
			X.push_back ( true );
			X.push_back ( false );

			CAPTURE ( X, C, D );

			CHECK ( C == X );
			CHECK ( D == X );
		}

		{
			ext::vector < bool > C = A & B;
			ext::vector < bool > D = A;
			D &= B;

			ext::vector < bool > X;
			X.push_back ( true );
			X.push_back ( false );
			X.push_back ( false );
			X.push_back ( false );

			CHECK ( C == X );
			CHECK ( D == X );
		}

		{
			ext::vector < bool > C = A ^ B;
			ext::vector < bool > D = A;
			D ^= B;

			ext::vector < bool > X;
			X.push_back ( false );
			X.push_back ( true );
			X.push_back ( true );
			X.push_back ( false );

			CHECK ( C == X );
			CHECK ( D == X );
		}
	}


	SECTION ( "vector<bool> 2" ) {
		auto testOffset = [] ( long offset, long shift ) {
			unsigned long long shadow = 0x2A76B147D6521C87ULL;
			ext::vector < bool > data;

			for ( unsigned i = 0; i < sizeof ( unsigned long long ) * 16 + offset; ++ i ) {
				data.push_back ( true );
			}

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

			data >>= sizeof ( unsigned long long ) * 6 + shift;

			ext::vector < bool > ref;
			for ( unsigned i = 0; i < sizeof ( unsigned long long ) * 10 + offset - shift; ++ i ) {
				ref.push_back ( true );
			}

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

			ref.resize ( 192 + offset );

			/*	std::cout << "offset = " << offset << std::endl;
				std::cout << "shift = " << shift << std::endl;
				std::cout << "data = " << data << std::endl;
				std::cout << "ref  = " << ref << std::endl;*/

			CHECK ( data == ref );
		};

		unsigned long long shadow = 0x2A76B147D6521C87ULL;
		ext::vector < bool > data;

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

		shadow >>= 10;
		data >>= 10;

		ext::vector < bool > ref;
		for ( unsigned i = 0; i < sizeof ( unsigned long long ) * 8; ++ i ) {
			ref.push_back ( ( bool ) ( shadow & ( 1ULL << i ) ) );
		}

		CAPTURE ( data ,ref );

		CHECK ( data == ref );

		for ( long i = - ( long ) sizeof ( unsigned long long ) / 2 ; i < ( long ) sizeof ( unsigned long long ) * 8; ++ i )
			for ( long j = - ( long ) sizeof ( unsigned long long ) / 2 ; j < ( long ) sizeof ( unsigned long long ) * 8; ++ j )
				testOffset ( i, j );
	}

	SECTION ( "vector<bool> 3" ) {
		unsigned long long shadow = 0x2A76B147D6521C87ULL;
		ext::vector < bool > 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 ) ) );
		}

		ext::vector < bool > data2 = data;
		data2 >>= 64;

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

		std::cout << "data  = " << data  << std::endl;
		std::cout << "data2 = " << data2 << 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  << std::endl;
		std::cout << "data2 = " << data2 << std::endl;

		CHECK ( data2 == data );
	}


	SECTION ( "vector<bool> 4" ) {
		auto testOffset = [] ( long offset, long shift ) {
			unsigned long long shadow = 0x2A76B147D6521C87ULL;
			ext::vector < bool > data;

			for ( unsigned i = 0; i < sizeof ( unsigned long long ) * 8 + offset; ++ i ) {
				data.push_back ( true );
			}

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

			data <<= sizeof ( unsigned long long ) * 2 + shift;

			ext::vector < bool > ref;
			for ( unsigned i = 0; i < sizeof ( unsigned long long ) * 2 + shift; ++ i ) {
				ref.push_back ( false );
			}

			for ( unsigned i = 0; i < sizeof ( unsigned long long ) * 8 + offset; ++ i ) {
				ref.push_back ( true );
			}

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

			ref.resize ( 128 + offset );

			/*	std::cout << "offset = " << offset << std::endl;
				std::cout << "shift = " << shift << std::endl;
				std::cout << "data = " << data << std::endl;
				std::cout << "ref  = " << ref << std::endl;*/

			CHECK ( data == ref );
		};

		unsigned long long shadow = 0x2A76B147D6521C87ULL;
		ext::vector < bool > data;

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

		shadow <<= 10;
		data <<= 10;

		ext::vector < bool > ref;
		for ( unsigned i = 0; i < sizeof ( unsigned long long ) * 8; ++ i ) {
			ref.push_back ( ( bool ) ( shadow & ( 1ULL << i ) ) );
		}

		std::cout << "data = " << data << std::endl;
		std::cout << "ref  = " << ref << std::endl;

		CHECK ( data == ref );

		for ( long i = - ( long ) sizeof ( unsigned long long ) / 2 ; i < ( long ) sizeof ( unsigned long long ) * 8; ++ i )
			for ( long j = - ( long ) sizeof ( unsigned long long ) / 2 ; j < ( long ) sizeof ( unsigned long long ) * 8; ++ j )
				testOffset ( i, j );
	}

	SECTION ( "vector<bool> 5" ) {
		unsigned long long shadow = 0x2A76B147D6521C87ULL;
		ext::vector < bool > 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 ) ) );
		}

		ext::vector < bool > data2 = data;
		data2 <<= 64;

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

		std::cout << "data  = " << data  << std::endl;
		std::cout << "data2 = " << data2 << 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  << std::endl;
		std::cout << "data2 = " << data2 << std::endl;

		CHECK ( data2 == data );
	}

	SECTION ( "vector<bool> 6" ) {
		for ( size_t i = 1; i < 1000; i += 31 ) {
			for ( size_t j = 1; j < 1000; j += 31 ) {
				ext::vector < bool > first;
				ext::vector < bool > second;

				first.resize ( i );
				second.resize ( j );

				ext::fill ( first );
				ext::clear ( second );

				ext::vector < bool > res = first & second;

				CHECK ( ext::any ( res ) == false );
			}
		}
	}

	SECTION ( "Test Range" ) {
		ext::vector < int > data { 1, 2, 3, 4 };
		ext::vector < int > data2 ( data.range ( ) );

		CHECK ( data == data2 );

		int moves;
		int copies;

		ext::vector < Moveable > vec;
		vec.push_back ( Moveable ( moves, copies ) );
		vec.push_back ( Moveable ( moves, copies ) );
		vec.push_back ( Moveable ( moves, copies ) );
		vec.push_back ( Moveable ( moves, copies ) );

		ext::vector < Moveable > vec2 ( ext::range ( std::move ( vec ) ) );

		CHECK ( copies == 0 );
	}
}
