#include <catch2/catch.hpp>
#include <alib/ptr_vector>
#include <alib/algorithm>

namespace {
	enum class Type {
		CHILD1,
		CHILD2
	};

	class Base {
		public:
			virtual ~Base ( ) {
			}

			virtual Base * clone ( ) const = 0;
			virtual Base * plunder ( ) && = 0;

			virtual Type type ( ) const = 0;

			bool operator == ( const Base & other ) const {
				return this->type ( ) == other.type ( );
			}

	};

	class Child1 : public Base {
		public:
			virtual Base * clone ( ) const override {
				return new Child1 ( * this );
			}

			virtual Base * plunder ( ) && override {
				return new Child1 ( * this );
			}

			virtual Type type ( ) const override {
				return Type::CHILD1;
			}
	};

	class Child2 : public Base {
		public:
			virtual Base * clone ( ) const override {
				return new Child2 ( * this );
			}

			virtual Base * plunder ( ) && override {
				return new Child2 ( * this );
			}

			virtual Type type ( ) const override {
				return Type::CHILD2;
			}
	};

	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 ( "PtrVector", "[unit][std][container]" ) {
	SECTION ( "Test Properties" ) {
		ext::ptr_vector < int > data = {1, 2, 3, 4};

		CHECK ( data [ 0 ] == 1 );
		CHECK ( data.size ( ) == 4 );
		CHECK ( data [ 3 ] == 4 );

		int i = 1;
		for ( int & content : data ) {
			CHECK ( content == i ++ );
		}
		const ext::ptr_vector < int > & cdata = data;

		i = 1;
		for ( const int & content : cdata ) {
			CHECK ( content == i ++ );
		}

		data.push_back ( 1 );
		CHECK ( data.size ( ) == 5 );
		CHECK ( data [ 4 ] == 1 );

		data.insert ( data.cbegin ( ) + 1, 2 );
		CHECK ( data.size ( ) == 6 );
		CHECK ( data [ 0 ] == 1 );
		CHECK ( data [ 1 ] == 2 );
		CHECK ( data [ 2 ] == 2 );
		CHECK ( data [ 3 ] == 3 );

		auto iter1 = data.erase ( data.rbegin ( ) );
		CHECK ( iter1 == data.rbegin ( ) );
		auto iter2 = data.insert ( data.rbegin ( ), 2 );
		CHECK ( iter2 == data.rbegin ( ) );
	}

	SECTION ( "Test Polymorphism" ) {
		ext::ptr_vector < Base > data = { Child2 ( ) };

		CHECK ( data [ 0 ].type ( ) == Type::CHILD2 );
		CHECK ( data.size ( ) == 1 );

		data.push_back ( Child1 ( ) );
		CHECK ( data.size ( ) == 2 );
		CHECK ( data [ 1 ].type ( ) == Type::CHILD1 );

		ext::ptr_vector < Base >::const_iterator iter = data.cbegin ( );
		CHECK ( iter->type ( ) == Type::CHILD2 );
		++ iter;
		CHECK ( iter->type ( ) == Type::CHILD1 );
		++ iter;
		CHECK ( iter == data.cend ( ) );

		data.insert ( data.cbegin ( ) + 1, Child1 ( ) );
		CHECK ( data.size ( ) == 3 );
		CHECK ( data [ 0 ].type ( ) == Type::CHILD2 );
		CHECK ( data [ 1 ].type ( ) == Type::CHILD1 );
		CHECK ( data [ 2 ].type ( ) == Type::CHILD1 );

		data.resize < Child2 > ( 4 );
		CHECK ( data.at ( 3 ) == Child2 ( ) );
		data.shrink ( 3 );

		ext::ptr_vector < Base > data2 = data;

		CHECK ( data2 [ 0 ].type ( ) == Type::CHILD2 );
		CHECK ( data2 [ 1 ].type ( ) == Type::CHILD1 );
		CHECK ( data2 [ 2 ].type ( ) == Type::CHILD1 );

		auto iter2 = data2.insert ( data2.cbegin ( ) + 1, data.begin ( ), data.end ( ) );

		CHECK ( iter2 == data2.begin ( ) + 1 );

		CHECK ( data2 [ 0 ].type ( ) == Type::CHILD2 );
		CHECK ( data2 [ 1 ].type ( ) == Type::CHILD2 );
		CHECK ( data2 [ 2 ].type ( ) == Type::CHILD1 );
		CHECK ( data2 [ 3 ].type ( ) == Type::CHILD1 );
		CHECK ( data2 [ 4 ].type ( ) == Type::CHILD1 );
		CHECK ( data2 [ 5 ].type ( ) == Type::CHILD1 );

		data2.erase ( ext::dereferencer ( ext::unique ( data2.begin ( ).base ( ), data2.end ( ).base ( ), [ ] ( const Base * a, const Base * b ) { return * a == * b; } ) ), data2.end ( ) );
	}

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

		CHECK ( data == data2 );

		int moves;
		int copies;

		ext::ptr_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::ptr_vector < Moveable > vec2 ( ext::range ( std::move ( vec ) ) );

		CHECK ( copies == 0 );
	}
}
