/*******************************************************************************
***  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 <pecunia/Codes.h>
using pecunia::currency::Iso4217Codes;
#include <pecunia/Money.h>
using pecunia::currency::Money;
#include <pecunia/MoneyTypes.h>
using pecunia::converter;
using pecunia::FloatingPoint;
#include <QDate>
using ::QDate;
#include <QTest>
using ::QObject;
#include <qt-mocks/Matchers.h>
using qt_mocks::equals;
// using QVERIFY_THAT;

#include <map>
using std::map;
#include <stdexcept>
using std::logic_error;
#include <string>
using namespace std::string_literals;
using std::string;

#include <budgeting/BudgetingErrors.h>
using drn::budgeting::BudgetItemError;
#include <budgeting/BudgetSource.h>
using drn::budgeting::BudgetSource;
#include <budgeting/EventFrequency.h>
using drn::budgeting::EventFrequency;
#include <budgeting/Debt.h>
using drn::budgeting::Debt;
using drn::budgeting::Percentage;
#include <budgeting/DebtMap.h>
using drn::budgeting::DebtMap;


namespace drn
{
namespace budgeting
{
namespace tests
{

struct DebtMapUT : QObject
{
	Q_OBJECT

private slots:
	void initTestCase()
	{
		converter =
				[] (const Iso4217Codes& from, const Iso4217Codes& to) -> FloatingPoint
				{
					if (from == to)
						return static_cast<FloatingPoint>(1.0);
					switch (from)
					{
					case Iso4217Codes::XXX:
						return static_cast<FloatingPoint>(1.0);
					case Iso4217Codes::PLN:
						switch (to)
						{
						case Iso4217Codes::USD:
							return static_cast<FloatingPoint>(0.27);
						case Iso4217Codes::XXX:
							return static_cast<FloatingPoint>(1.0);
						default:
							throw logic_error{"Unhandled to code."};
						};
					case Iso4217Codes::USD:
						switch (to)
						{
						case Iso4217Codes::PLN:
							return static_cast<FloatingPoint>(3.76);
						case Iso4217Codes::XXX:
							return static_cast<FloatingPoint>(1.0);
						default:
							throw logic_error{"Unhandled to code."};
						};
					default:
						throw logic_error{"Unhandled from code."};
					}
				};
	}

	// Constructor Tests

	void constructor_Empty_ShouldBeDefaults()
	{
		const DebtMap debts{};
		QVERIFY_TRUE(debts.empty());
	}
	void constructor_FullInit_ShouldBeSupplied()
	{
		const BudgetSource homeFederalBank{"Home Federal Bank"};
		const BudgetSource visa{"Visa"};
		const map<BudgetSource, Debt> values{
			{
				homeFederalBank,
				Debt{
					homeFederalBank,
					Money{1000, 0u, Iso4217Codes::USD},
					EventFrequency::Monthly,
					{9999, 6, 20},
					Money{120000, 0u, Iso4217Codes::USD},
					Percentage{3.15}
				}
			},
			{
				visa,
				Debt{
					visa,
					Money{500, 0u, Iso4217Codes::USD},
					EventFrequency::Monthly,
					{9999, 6, 20},
					Money{30000, 0u, Iso4217Codes::USD},
					Percentage{3.15}
				}
			}
		};
		const DebtMap debts{values};
		QCOMPARE(debts.at(homeFederalBank), values.at(homeFederalBank));
		QCOMPARE(debts.at(visa), values.at(visa));
	}

	//}

	//{ update Tests

	void update_ValidParams_ShouldSet()
	{
		const BudgetSource visa{"Visa"};
		const Money owed{120000, 0u, Iso4217Codes::USD};
		const Debt debt{
			visa,
			Money{1000, 0u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 6, 20},
			owed,
			Percentage{3.15}
		};
		DebtMap debts;
		debts.add(debt);
		const Debt changed{
			visa,
			Money{50, 0u, Iso4217Codes::USD},
			EventFrequency::BiMonthly,
			{9999, 6, 20},
			owed,
			Percentage{6.25}
		};
		QVERIFY_NO_THROW(debts.update(changed));
		QVERIFY_THAT(debts.at(visa), equals(changed));
	}

	void update_ChangeAmount_ShouldThrow()
	{
		const BudgetSource visa{"Visa"};
		const Money owed{120000, 0u, Iso4217Codes::USD};
		const Debt debt{
			visa,
			Money{1000, 0u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 6, 20},
			owed,
			Percentage{3.15}
		};
		DebtMap debts;
		debts.add(debt);
		const Debt changed{
			visa,
			Money{1000, 0u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 6, 20},
			owed / 2,
			Percentage{3.15}
		};
		QVERIFY_EXCEPTION_THROWN(debts.update(changed), BudgetItemError);
		QVERIFY_THAT(debts.at(visa), equals(debt));
	}
	void update_DifferentCurrencies_ShouldThrow()
	{
		DebtMap debts{};
		const BudgetSource visa{"Visa"};
		const Debt debt{
			visa,
			Money{0, 0u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 6, 20},
			Money{50, 0u, Iso4217Codes::USD},
			Percentage{3.15}
		};
		debts.add(debt);
		Money minimum{Iso4217Codes::PLN};
		minimum = debt.amount();
		QVERIFY_THAT(minimum, equals(debt.amount()));
		Money borrowed{Iso4217Codes::PLN};
		borrowed = debt.borrowed();
		QVERIFY_THAT(borrowed, equals(debt.borrowed()));
		const Debt changed{
			visa,
			minimum,
			EventFrequency::Monthly,
			{2019, 7, 5},
			borrowed,
			Percentage{20.0}
		};
		QVERIFY_EXCEPTION_THROWN(debts.update(changed), BudgetItemError);
		QVERIFY_THAT(debts.at(visa), equals(debt));
	}
	void update_DoesNotExist_ShouldThrow()
	{
		DebtMap debts{};
		const Debt d{
			BudgetSource{"Visa"},
			Money{10, 0u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 6, 20},
			Money{1000, 0u, Iso4217Codes::USD},
			Percentage{3.15}
		};
		QVERIFY_EXCEPTION_THROWN(debts.update(d), BudgetItemError);
	}

	//}
};

}}}

QTEST_MAIN(drn::budgeting::tests::DebtMapUT)
#include "DebtMap-unit-tests.moc"
