#include <catch2/catch.hpp>

#include <exception>
#include <alib/utility>
#include <alib/memory>
#include <alib/type_traits>

namespace {
	int instances = 0; // set to expected number of instances created in parameter pass test including the original

	class FooCloneable {
		int m_data;

		FooCloneable ( const FooCloneable & foo ) : m_data ( foo.m_data ) {
			//std::cout << "Copy" << std::endl;

			instances++;
		}

		FooCloneable ( FooCloneable && foo ) : m_data ( foo.m_data ) {
			//std::cout << "Move" << std::endl;

			instances++;
		}

		public:
		FooCloneable ( int data ) : m_data ( data ) {
			//std::cout << "Constructor" << std::endl;

			instances++;
		}

		~FooCloneable ( ) {
			//std::cout << "Destructor" << std::endl;

			instances--;
		}

		FooCloneable * clone ( ) const {
			return new FooCloneable ( * this );
		}

		int getData ( ) const {
			return m_data;
		}

	};

	class FooCopyable {
		int m_data;

		public:
		FooCopyable ( int data ) : m_data ( data ) {
			//std::cout << "Constructor" << std::endl;

			instances++;
		}

		FooCopyable ( const FooCopyable & foo ) : m_data ( foo.m_data ) {
			//std::cout << "Copy" << std::endl;

			instances++;
		}

		FooCopyable ( FooCopyable && foo ) : m_data ( foo.m_data ) {
			//std::cout << "Move" << std::endl;

			instances++;
		}

		~FooCopyable ( ) {
			//std::cout << "Destructor" << std::endl;

			instances--;
		}

		FooCopyable * clone ( ) const {
			return new FooCopyable ( * this );
		}

		int getData ( ) const {
			return m_data;
		}

	};

	int dest ( FooCloneable && foo ) {
		//std::cout << "Destination called" << std::endl;

		CHECK ( instances == 0 );

		return foo.getData ( );
	}

	int dest ( FooCopyable && foo ) {
		//std::cout << "Destination called" << std::endl;

		CHECK ( instances == 0 );

		return foo.getData ( );
	}

	int test ( FooCloneable && foo ) {
		return dest ( std::move ( foo ) ) + 1;
	}

	int test ( const FooCloneable & foo ) {
		return test ( ext::move_copy ( foo ) ) + 1;
	}

	int test ( FooCopyable && foo ) {
		return dest ( std::move ( foo ) ) + 1;
	}

	int test ( const FooCopyable & foo ) {
		return test ( ext::move_copy ( foo ) ) + 1;
	}
}

TEST_CASE ( "ParamPass", "[unit][std][bits]" ) {
	SECTION ( "Parameter Passing" ) {
		{
			instances = -2;
			const FooCloneable foo ( 1 );
			int res = 0;
			res = test ( foo );
			CHECK ( res == 3 );
		}
		{
			instances = -2;
			const FooCopyable foo ( 1 );
			int res = 0;
			res = test ( foo );
			CHECK ( res == 3 );
		}
		{
			instances = -2;
			ext::ptr_value < FooCopyable > foo ( FooCopyable ( 1 ) );
			CHECK ( instances == -1 );
			int res = 0;
			res = test ( foo );
			CHECK ( res == 3 );
		}
		{
			instances = -1;
			ext::ptr_value < FooCopyable > foo ( FooCopyable ( 1 ) );
			int res = 0;
			res = test ( std::move ( foo ) );
			CHECK ( res == 2 );
		}
	}
}
