#include <catch2/catch.hpp>
#include <testing.h>
#include <alib/variant>
#include <alib/set>
#include <alib/algorithm>
#include <alib/map>
#include <sstream>

namespace {
	struct test {
		int * holder;

		test() {
			//std::cout << "test()" << std::endl;
			holder = new int();
		}

		test(test&& old) noexcept : holder(new int()) {
			//std::cout << "test(test&&)" << std::endl;
			std::swap(holder,old.holder);
		}

		test(const test& old) {
			//std::cout << "test(const test&)" << std::endl;
			holder = new int(*old.holder);
		}

		~test() noexcept
		{
			//std::cout << "~test()" << std::endl;
			delete holder;
		}

		test & operator = ( const test & other ) {
			* holder = * other.holder;
			return * this;
		}

		test & operator = ( test && other ) noexcept {
			* holder = * other.holder;
			return * this;
		}

		std::strong_ordering operator<=>(const test& other) const {
			return *(this->holder) <=> *(other.holder);
		}

		bool operator==(const test& other) const {
			return *(this->holder) == *(other.holder);
		}

		friend std::ostream& operator<<(std::ostream& out, const test& other) {
			out << *(other.holder);
			return out;
		}

		operator std::string ( ) const {
			return ext::to_string ( * holder );
		}
	};

	struct test2 {
		int m_i;

		test2(int i) : m_i(i) {}
	};

	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;
		}

		std::strong_ordering operator<=>(const Moveable&) const {
			return std::strong_ordering::equal;
		}

		bool operator==(const Moveable&) const {
			return true;
		}

		friend std::ostream & operator << ( std::ostream & os, const Moveable & ) {
			return os << "Moveable";
		}
	};
}

TEST_CASE ( "Variant", "[unit][std][container]" ) {
	SECTION ( "Variant" ) {
		ext::variant<int, std::string, test> d(10);
		CHECK( d.is<int>() );
		CHECK( d.get<int>() == 10 );
		CHECK( d == 10 );
		CHECK( d < 11 );
		CHECK( 10 == d );
		CHECK_EXCLUSIVE_OR( d < std::string("a"), d > std::string("a") );

		std::string str = "abcde";
		d.set(str);
		CHECK( d.is<std::string>() );
		CHECK( d.get<std::string>() == str );

		d.set(test());
		*(d.get<test>().holder) = 42;

		test tmp;
		*(tmp.holder) = 42;
		CHECK( d.is<test>() );
		CHECK( d.get<test>() == tmp );

		ext::variant<int, std::string, test> e(d);
		CHECK( e.get<test>() == tmp );

		*(e.get<test>().holder) = 43;

		CHECK( (d < e) == true );
		CHECK( (d < e) == true );
		CHECK( (e < d) == false );

		std::swap ( d, e );

		CHECK( (d < e) == false );
		CHECK( (e < d) == true );

		std::swap ( d, e );

		CHECK( (e == d) == false );

		d = ext::variant<int, std::string, test>(0);
		d = e;

		CHECK( *(d.get<test>().holder) == 43 );

		CHECK( (d < e) == false );
		CHECK( (e < d) == false );

		CHECK( (e == d) == true );

		CHECK ( ( ( std::string ) e ) == "43" );

		std::stringstream ss;
		ss << e;
		CHECK ( ss.str ( ) == "43" );
	}

	SECTION ( "set" ) {
		test tmp;
		*(tmp.holder) = 42;

		std::string str = "abcde";

		ext::variant<int, std::string, test> d(test {});
		*(d.get<test>().holder) = 42;
		ext::variant<int, std::string, test> e(d);
		ext::variant<int, std::string, test> f(5);
		ext::variant<int, std::string, test> g(str);

		ext::set<ext::variant<int, std::string, test>> testSet;
		testSet.insert(d);
		testSet.insert(e);
		testSet.insert(f);
		testSet.insert(g);

		CHECK( testSet.size() == 3 );

		for(ext::set<ext::variant<int, std::string, test>>::const_iterator iter = testSet.begin(); iter != testSet.end(); iter++) {
			if(iter->is<int>())
				CHECK( iter->get<int>() == 5);
			if(iter->is<std::string>())
				CHECK( iter->get<std::string>() == str );
			if(iter->is<test>())
				CHECK( iter->get<test>() == tmp );
		}

		ext::variant<test2, std::string, test> h("aa");
		CHECK( h.is<std::string>() );

		ext::map<ext::variant<std::string, int>, int> testMap;
		testMap.insert(ext::variant<std::string, int> {"aa"}, 10);

		CHECK( testMap.size() == 1 );
		CHECK( testMap.find(ext::variant<std::string, int> {"aa"}) != testMap.end() );
		CHECK( testMap.find(ext::variant<std::string, int> {10}) == testMap.end() );

		testMap.insert(ext::variant<std::string, int> {"ab"}, 11);
		testMap.insert(ext::variant<std::string, int> {3}, 13);

		CHECK( testMap.find(ext::variant<std::string, int> {"aa"}) != testMap.end() );
		CHECK( testMap.find(ext::variant<std::string, int> {10}) == testMap.end() );
	}

	SECTION ( "set 2" ) {
		ext::set<ext::variant<std::string, int>> s, t, u;
		s.insert(std::string { "aa" } );
		s.insert(123);

		std::set_union(s.begin(), s.end(), t.begin(), t.end(), std::inserter(u, u.begin()));
		CHECK( s.size() == u.size());

		/*	ext::variant < void, ext::pair < int, int > > var;
			var.get < int > ( );*/ //should not compile -- correct behavior

		//	ext::variant < int, char > var2;// should not compile -- correct behavior
	}

	SECTION ( "Same types" ) {
		ext::variant < int, int, char > v1 ( 1 );
		CHECK ( v1.get < int > ( ) == 1 );

		v1.set ( 'a' );
		CHECK ( v1.get < char > ( ) == 'a' );

		v1.set ( (int) 'a' );
		CHECK ( v1.is < int > ( ) );

		std::cout << v1 << std::endl;
	}

	SECTION ( "size_t" ) {
		ext::variant < size_t, int > a ( ( size_t ) 1 );
		ext::variant < size_t, int > b ( 1 );

		CHECK ( a.is < size_t > ( ) );
		CHECK ( b.is < int > ( ) );
	}

	SECTION ( "Move Semantics" ) {
		CHECK ( ( std::is_nothrow_move_constructible < ext::variant < int, size_t > >::value ) );
		CHECK ( ( std::is_move_constructible < ext::variant < int, size_t > >::value && std::is_move_assignable < ext::variant < int, size_t > >::value ) );
	}

	SECTION ( "Variant" ) {
		std::set < int > a;
		ext::variant < int, std::set < int > > v = a;
		ext::variant < int, std::set < int > > v2 = a;
		v = std::move ( v2 );
		v.get < std::set < int > > ( ).insert ( 1 );
	}

	SECTION ( "Copy vs. Move" ) {
		int moves = 0;
		int copies = 0;

		ext::variant < Moveable, int > inst1 ( Moveable ( moves, copies ) );

		CHECK ( moves == 1 );
		CHECK ( copies == 0 );

		ext::variant < Moveable, int > inst2 ( 1 );

		CHECK ( moves == 1 );
		CHECK ( copies == 0 );

		CHECK ( inst1 != inst2 );

		CHECK ( moves == 1 );
		CHECK ( copies == 0 );

		std::cout << inst1 << std::endl;

		CHECK ( moves == 1 );
		CHECK ( copies == 0 );

		inst1 <=> inst2;

		CHECK ( moves == 1 );
		CHECK ( copies == 0 );
	}
}
