#include <catch2/catch.hpp>

#include <alib/list>
#include <alib/type_traits>
#include <alib/set>

#include <object/Object.h>
#include <object/Void.h>

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

		operator std::string ( ) const {
			return "Moveable";
		}

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

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

	};

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

}
TEST_CASE ( "Objects", "[unit][object]" ) {

	SECTION ( "Test Properties" ) {
		REQUIRE ( std::is_nothrow_move_constructible < object::Object >::value );
		REQUIRE ( ( std::is_move_constructible < object::Object >::value && std::is_move_assignable < object::Object >::value ) );

		object::Object tmp1 { object::AnyObject < unsigned > ( 1 ) };
		object::Object tmp2 { object::AnyObject < unsigned > ( 2 ) };

		std::swap ( tmp1, tmp2 );

		CHECK ( tmp1 == object::Object ( object::AnyObject < unsigned > ( 2 ) ) );
		CHECK ( tmp2 == object::Object ( object::AnyObject < unsigned > ( 1 ) ) );
	}

	SECTION ( "Test Construction" ) {
		object::Object tmp1 ( 1 );
		object::Object tmp2 ( ext::variant < int, std::string > ( 1 ) );
		object::Object tmp3 ( ext::variant < int, ext::variant < std::string, int > > ( ext::variant < std::string, int > ( 1 ) ) );
		CHECK ( tmp1 == tmp2 );
		CHECK ( tmp1 == tmp3 );

		object::Object tmp4 = object::Object ( object::AnyObject < ext::set < int > > ( ext::set < int > { } ) );
		object::Object tmp5 ( ext::set < int > { } );
		object::Object tmp6 ( object::AnyObject < ext::set < int > > ( ext::set < int > { } ) );
		CHECK ( tmp4 == tmp5 );
		CHECK ( tmp4 == tmp6 );

		tmp6 ++;
		object::Object tmp7 ( object::AnyObject < ext::set < int > > ( ext::set < int > { }, 1 ) );
		CHECK ( tmp6 == tmp7 );

		object::Object test = object::Object ( object::AnyObject < object::Object > ( object::Object ( object::AnyObject < ext::set < int > > ( ext::set < int > { } ) ) ) );

		CAPTURE ( test, tmp4 );
		CHECK ( test == tmp4 );

		{
			int moves = 0, copies = 0;

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

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

			object::Object object1 ( std::move ( variant1 ) );

			CHECK ( moves >= 2 );
			CHECK ( copies == 0 );
		}

		{
			int moves = 0, copies = 0;

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

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

			object::Object object2 ( variant2 );

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

	SECTION ( "Compare" ) {
		CHECK ( object::Void { } == object::Void { } );
	}
}
