#include <catch2/catch.hpp>

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

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

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

	class Moveable2 {
		int& m_moves;
		int& m_copies;

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

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

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

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

	class Base {
		public:
			virtual Base* clone() const = 0;

			virtual ~Base ( ) {
			}
	};

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

			~Derived ( ) override {
			}
	};
}
TEST_CASE ( "Shared Ptr", "[unit][std][bits]" ) {
	SECTION ( "Shared Ptr" ) {
		{
			int moves;
			int copies;

			ext::cow_shared_ptr<Moveable> one(new Moveable(moves, copies));

			CHECK(one.unique());
			CHECK(moves == 0);
			CHECK(copies == 0);

			ext::cow_shared_ptr<Moveable> two(one);

			CHECK(!one.unique());
			CHECK(moves == 0);
			CHECK(copies == 0);

			std::as_const(two).get();

			ext::cow_shared_ptr<Moveable> three(std::move ( two ) );

			std::as_const(three).get();

			CHECK(!one.unique());
			CHECK(moves == 0);
			CHECK(copies == 0);

			two = std::move ( three ) ;

			two.get();

			CHECK(one.unique());
			CHECK(moves == 0);
			CHECK(copies == 1);
		}
		{

			int moves;
			int copies;

			ext::cow_shared_ptr<Moveable2> one(new Moveable2(moves, copies));

			CHECK(one.unique());
			CHECK(moves == 0);
			CHECK(copies == 0);

			ext::cow_shared_ptr<Moveable2> two(one);

			CHECK(!one.unique());
			CHECK(moves == 0);
			CHECK(copies == 0);

			std::as_const(two).get();

			ext::cow_shared_ptr<Moveable2> three(std::move ( two ) );

			std::as_const(three).get();

			CHECK(!one.unique());
			CHECK(moves == 0);
			CHECK(copies == 0);

			two = std::move ( three ) ;

			two.get();

			CHECK(one.unique());
			CHECK(moves == 0);
			CHECK(copies == 1);
		}
		{
			ext::cow_shared_ptr<Base> one(new Derived());
			ext::cow_shared_ptr<Base> two(one);

			CHECK(!one.unique());

			two.get();

			CHECK(one.unique());
			CHECK(two.unique());

			CHECK(one.get() != two.get());
		}
	}
}
