/*******************************************************************************
***  This file is part of Dux Rei Nummariae.                                 ***
***                                                                          ***
***  Copyright (C) 2017, 2018, 2019, 2020, 2021                              ***
***  John Schneiderman <Licensing _AT_ Schneiderman _DOT_ me>                ***
***                                                                          ***
***  This program is free software: you can redistribute it and/or modify it ***
***  under the terms of the GNU Affero General Public License as published   ***
***  by the Free Software Foundation, either version 3 of the License, or    ***
***  (at your option) any later version.                                     ***
***                                                                          ***
***  This program is distributed in the hope that it will be useful, but     ***
***  WITHOUT ANY WARRANTY; without even the implied warranty of              ***
***  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.                    ***
***  See the GNU Affero General Public License for more details.             ***
***                                                                          ***
***  You should have received a copy of the GNU Affero General Public License***
***  along with this program. If not, see <http://www.gnu.org/licenses/>.    ***
*******************************************************************************/
#include <QTest>
// using QVERIFY2

#include <map>
using std::map;
#include <memory>
using std::make_unique;
#include <ostream>
using std::boolalpha;
using std::ostream;
#include <vector>
using std::vector;

#include <foundation/ObserverPtr.hpp>
using drn::foundation::ObserverPtr;
#include <qt-mocks/Matchers.h>
using qt_mocks::equals;
using qt_mocks::isFalse;
using qt_mocks::isTrue;
using qt_mocks::lessThan;
using qt_mocks::notEquals;
using qt_mocks::not_;
// using QVERIFY_THAT;
// using QVERIFY_TRUE;


namespace drn
{
namespace foundation
{
namespace tests
{

struct ObserverPtrUT : QObject
{
	Q_OBJECT

public:
	struct Tester
	{
		// cppcheck-suppress uninitvar
		// Justification: Delegating constructor.
		Tester() :
			Tester{0}
		{}
		explicit Tester(const qint32 value) :
			value_{value},
			lessThanCalled_{false}
		{}
		qint32 constMember() const
		{
			return this->value_ + 5;
		}
		qint32 nonConstMember()
		{
			this->value_ += 10;
			return this->value_;
		}
		bool operator<(const Tester& other) const
		{
			this->lessThanCalled_ = true;
			return this->value_ < other.value_;
		}


		qint32 value_;
		mutable bool lessThanCalled_;
	};

private slots:
	//{ Constructor Tests

	void constructor_Empty_ShouldBeNull()
	{
		const ObserverPtr<Tester> ptr{};
		QVERIFY2(ptr == nullptr, "An empty observer pointer should be null.");
	}
	void constructor_FullInit_ShouldBeValue()
	{
		const Tester test{42};
		const ObserverPtr<const Tester> ptr{&test};
		const Tester* testRaw{&test};
		QVERIFY2(ptr == &test, "An initalised observer pointer should point to the same object.");
		QVERIFY2(
			static_cast<const Tester*>(ptr) == testRaw,
			"A casted observer pointer should point to the same object."
		);
		QVERIFY2(
			testRaw == static_cast<const Tester*>(ptr),
			"A casted observer pointer should point to the same object."
		);
		QVERIFY2(
			ptr == testRaw,
			"A noncasted observer pointer should point to the same object."
		);
		QVERIFY2(
			testRaw == ptr,
			"A noncasted observer pointer should point to the same object."
		);
	}

	//}

	//{ Dereference Operator Tests

	void dereferenceConstOperator_ObservedValues_ShouldBeSame()
	{
		const Tester test{42};
		const ObserverPtr<const Tester> ptr{&test};
		QCOMPARE((*ptr).value_, test.value_);
	}
	void dereferenceNonConstOperator_ObservedValues_ShouldBeSame()
	{
		Tester test{42};
		ObserverPtr<Tester> ptr{};
		ptr = &test;
		QCOMPARE((*ptr).value_, test.value_);
	}

	//}

	//{ Arrow Operator Tests

	void arrowConstOperator_ObservedValues_ShouldBeSame()
	{
		const Tester test{42};
		const ObserverPtr<const Tester> ptr{&test};
		QCOMPARE(ptr->value_, test.value_);
		QVERIFY_THAT(ptr->constMember(), equals(47));
	}
	void arrowNonConstOperator_ObservedValues_ShouldBeSame()
	{
		Tester test{42};
		ObserverPtr<Tester> ptr{};
		ptr = &test;
		QCOMPARE(ptr->value_, test.value_);
		const auto val{ptr->nonConstMember()};
		QVERIFY_THAT(val, equals(52));
	}

	//}

	//{ Conversion Operators

	void conversionOperatorBool_ObservedValueSet_ShouldBeTrue()
	{
		const Tester test{42};
		const ObserverPtr<const Tester> ptr{&test};
		QVERIFY2(ptr, "A set observer pointer should be true.");
	}
	void conversionOperatorBool_ObservedValueNotSet_ShouldBeFalse()
	{
		const ObserverPtr<const Tester> ptr{};
		QVERIFY2( ! ptr, "An empty observer pointer should be false.");
	}
	void conversionOperatorObservedType_ObservedValueSet_ShouldBeConverted()
	{
		const Tester test{42};
		const ObserverPtr<const Tester> ptr{&test};
		const auto verifyConversion{
			[&test] (const Tester* tstPtr)
			{
				QCOMPARE(tstPtr->value_, test.value_);
			}
		};
		verifyConversion(static_cast<const Tester*>(ptr));
	}

	//}

	//{ assignment Operator Tests

	void assignmentOperator_Assigned_ShouldBeSame()
	{
		Tester test{1};
		ObserverPtr<Tester> ptr;
		ptr = &test;
		QVERIFY2(ptr == &test, "Once assigned they should be the same.");
	}
	void assignmentOperator_NullAssigned_ShouldBeNull()
	{
		Tester test{1};
		ObserverPtr<Tester> ptr{&test};
		ptr = nullptr;
		QVERIFY2(ptr != &test, "Once unassigned they should not be the same.");
		QVERIFY2(ptr == nullptr, "Once unassigned the observer should be null.");
	}

	//}

	//{ Other Tests

	void observer_InConstContainer_ShouldBeUsable()
	{
		const Tester test0{1};
		const Tester test1{2};
		const Tester test2{3};
		const vector<ObserverPtr<const Tester>> ptr{&test0, &test1, &test2};
		QCOMPARE(ptr[0]->value_, test0.value_);
		QCOMPARE(ptr[1]->value_, test1.value_);
		QCOMPARE(ptr[2]->value_, test2.value_);
	}
	void observer_InNonConstContainer_ShouldBeUsable()
	{
		Tester test0{1};
		Tester test1{2};
		Tester test2{3};
		const vector<ObserverPtr<Tester>> ptr{&test0, &test1, &test2};
		ptr[0]->value_ = 32;
		ptr[1]->value_ = 26;
		ptr[2]->value_ = 38;
		QCOMPARE(ptr[0]->value_, test0.value_);
		QCOMPARE(ptr[1]->value_, test1.value_);
		QCOMPARE(ptr[2]->value_, test2.value_);
	}

	//}

	//{ Equality Operator

	void equalityOperator_SameStackObject_ShouldBeTrue()
	{
		const Tester t{1234};
		const ObserverPtr<const Tester> o0{&t};
		const ObserverPtr<const Tester> o1{&t};
		QVERIFY_THAT(&t, equals(o0));
		QVERIFY_THAT(&t, equals(o1));
		QVERIFY_THAT(o0, equals(o1));
		QCOMPARE(o0.observed(), o1.observed());
	}

	void equalityOperator_SameHeapObject_ShouldBeTrue()
	{
		const auto t{make_unique<const Tester>(1234)};
		const ObserverPtr<const Tester> o0{t};
		const ObserverPtr<const Tester> o1{t};
		ObserverPtr<const Tester> o2{};
		{
			const ObserverPtr<const Tester> dummy{t};
			o2 = dummy;
		}
		QVERIFY_THAT(t.get(), equals(o0));
		QVERIFY_THAT(t.get(), equals(o1));
		QVERIFY_THAT(o0, equals(o1));
		QCOMPARE(o0.observed(), o1.observed());
		QVERIFY_THAT(o2, equals(o0));
	}

	void equalityOperator_InContainer_ShouldBeTrue()
	{
		const Tester t0{0};
		const Tester t1{1};
		const Tester t2{2};
		const Tester t3{3};
		map<qint32, ObserverPtr<const Tester>> ts{
			{
				{0, &t0},
				{1, &t1},
				{2, &t2}
			}
		};
		const ObserverPtr<const Tester> o0{&t0};
		const ObserverPtr<const Tester> o1{&t1};
		const ObserverPtr<const Tester> o2{&t2};
		{
			const ObserverPtr<const Tester> o3{&t3};
			QVERIFY_TRUE(ts.emplace(3, o3).second);
		}

		QVERIFY_THAT(&t0, equals(ts.at(0)));
		QCOMPARE(ts.at(0).observed(), o0.observed());

		QVERIFY_THAT(&t1, equals(ts.at(1)));
		QCOMPARE(ts.at(1).observed(), o1.observed());

		QVERIFY_THAT(&t2, equals(ts.at(2)));
		QCOMPARE(ts.at(2).observed(), o2.observed());

		QVERIFY_THAT(&t3, equals(ts.at(3)));
	}

	//}

};

ostream& operator<<(ostream& o, const ObserverPtrUT::Tester& test)
{
	return o << boolalpha << "(Value: " << test.value_ << ", lessThanCalled: "
		<< test.lessThanCalled_ << ')';
}

}}}

QTEST_MAIN(drn::foundation::tests::ObserverPtrUT)
#include "ObserverPtr-unit-tests.moc"

