/*******************************************************************************
***  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::MinorUnit;
using pecunia::UnitStorage;
#include <qt-mocks/Matchers.h>
using qt_mocks::equals;
// using QVERIFY_THAT;
#include <QTest>
// using QTEST_MAIN.

#include <chrono>
using namespace std::chrono_literals;
#include <map>
using std::map;
#include <set>
using std::set;
#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/BudgetedMoney.h>
using drn::budgeting::BudgetedMoney;
#include <budgeting/BudgetedMoneyMap.hpp>
using drn::budgeting::BudgetedMoneyMap;
#include <foundation/CalendarTime.hpp>
using drn::foundation::Days;


namespace drn
{
namespace budgeting
{
namespace tests
{

struct BudgetedMoneyMapUT : QObject
{
	Q_OBJECT

private slots:
	// Constructor Tests

	void constructor_Empty_ShouldBeDefaults()
	{
		const BudgetedMoneyMap<BudgetedMoney> monies{};
		QVERIFY2(monies.empty() == true, "By default there should not be any values.");
	}
	void constructor_FullInit_ShouldBeSupplied()
	{
		const map<BudgetSource, BudgetedMoney> values{
			{
				BudgetSource{QStringLiteral("Source Name 0")},
				BudgetedMoney{
					BudgetSource{QStringLiteral("Source Name 0")},
					Money{5, 34u, Iso4217Codes::USD},
					EventFrequency::Hourly,
					{9999, 6, 20}
				}
			},
			{
				BudgetSource{QStringLiteral("Source Name 1")},
				BudgetedMoney{
					BudgetSource{QStringLiteral("Source Name 1")},
					Money{5, 34u, Iso4217Codes::USD},
					EventFrequency::Hourly,
					{9999, 6, 20}
				}
			}
		};
		const BudgetedMoneyMap<BudgetedMoney> monies{values};
		QCOMPARE(
			monies.at(BudgetSource{QStringLiteral("Source Name 0")}),
			values.at(BudgetSource{QStringLiteral("Source Name 0")})
		);
		QCOMPARE(
			monies.at(BudgetSource{QStringLiteral("Source Name 1")}),
			values.at(BudgetSource{QStringLiteral("Source Name 1")})
		);
	}

	//}

	//{ add Tests

	void add_ValidParams_ShouldSet()
	{
		BudgetedMoneyMap<BudgetedMoney> monies{};
		const BudgetedMoney money{
			BudgetSource{QStringLiteral("Source Name 0")},
			Money{5, 34u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{9999, 6, 20}
		};
		monies.add(money);
		QCOMPARE(monies.at(money.source()), money);
	}
	void add_AlreadyExists_ShouldThrow()
	{
		BudgetedMoneyMap<BudgetedMoney> monies{};
		const BudgetedMoney money{
			BudgetSource{QStringLiteral("Source Name 0")},
			Money{5, 34u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{9999, 6, 20}
		};
		monies.add(money);
		QVERIFY_EXCEPTION_THROWN(monies.add(money), BudgetItemError);
	}
	void add_Emptyname_ShouldThrow()
	{
		BudgetedMoneyMap<BudgetedMoney> monies{};
		const BudgetedMoney money{};
		QVERIFY_EXCEPTION_THROWN(monies.add(money), BudgetItemError);
	}

	//}

	//{ has

	void has_ValidParams_ShouldSet()
	{
		BudgetedMoneyMap<BudgetedMoney> monies{};
		const BudgetedMoney money{
			BudgetSource{QStringLiteral("Source Name 0")},
			Money{5, 34u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{9999, 6, 20}
		};
		monies.add(money);
		QCOMPARE(monies.has(money.source()), true);
	}
	void has_AlreadyExists_ShouldThrow()
	{
		BudgetedMoneyMap<BudgetedMoney> monies{};
		QCOMPARE(monies.has(BudgetSource{QStringLiteral("Source Name 0")}), false);
	}

	//}

	//{ rename Tests

	void rename_ValidParams_ShouldSet()
	{
		BudgetedMoneyMap<BudgetedMoney> monies{};
		const BudgetedMoney money{
			BudgetSource{QStringLiteral("Source Name 0")},
			Money{5, 34u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{9999, 6, 20}
		};
		monies.add(money);
		const BudgetSource newName{"Source Name 1"};
		monies.rename(money.source(), newName);
		QCOMPARE(monies.has(money.source()), false);
		QCOMPARE(monies.has(newName), true);
	}
	void rename_DoesNotExist_ShouldThrow()
	{
		BudgetedMoneyMap<BudgetedMoney> monies{};
		QVERIFY_EXCEPTION_THROWN(
			monies.rename(BudgetSource("Test"), BudgetSource("Test 2")),
			BudgetItemError
		);
	}
	void rename_NewSourceExists_ShouldThrow()
	{
		BudgetedMoneyMap<BudgetedMoney> monies{};
		const BudgetedMoney money0{
			BudgetSource{QStringLiteral("Source Name 0")},
			Money{5, 34u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{9999, 6, 20}
		};
		monies.add(money0);
		const BudgetedMoney money1{
			BudgetSource{QStringLiteral("Source Name 1")},
			Money{5, 34u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{9999, 6, 20}
		};
		monies.add(money1);
		QVERIFY_EXCEPTION_THROWN(
			monies.rename(money0.source(), money1.source()),
			BudgetItemError
		);
	}

	//}

	//{ update Tests

	void update_ValidParams_ShouldSet()
	{
		BudgetedMoneyMap<BudgetedMoney> monies{};
		const BudgetedMoney money{
			BudgetSource{QStringLiteral("Source Name 0")},
			Money{5, 34u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{9999, 6, 20}
		};
		monies.add(money);
		const BudgetedMoney changed{
			BudgetSource{QStringLiteral("Source Name 0")},
			Money{15, 65u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{9999, 6, 20}
		};
		monies.update(changed);
		QCOMPARE(monies.at(money.source()), changed);
	}
	void update_DifferentCurrencies_ShouldReplace()
	{
		BudgetedMoneyMap<BudgetedMoney> monies{};
		const BudgetedMoney money{
			BudgetSource{QStringLiteral("Source Name 0")},
			Money{5, 34u, Iso4217Codes::PLN},
			EventFrequency::Hourly,
			{9999, 6, 20}
		};
		monies.add(money);
		const BudgetedMoney changed{
			BudgetSource{QStringLiteral("Source Name 0")},
			Money{15, 65u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{9999, 6, 20}
		};
		monies.update(changed);
		QCOMPARE(monies.at(money.source()).amount().code(), Iso4217Codes::USD);
		QCOMPARE(monies.at(money.source()), changed);
	}
	void update_DoesNotExist_ShouldThrow()
	{
		BudgetedMoneyMap<BudgetedMoney> monies{};
		const BudgetedMoney money{
			BudgetSource{QStringLiteral("Source Name 0")},
			Money{5, 34u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{9999, 6, 20}
		};
		QVERIFY_EXCEPTION_THROWN(monies.update(money), BudgetItemError);
	}

	//}

	//{ remove Tests

	void remove_ValidParams_ShouldRemove()
	{
		BudgetedMoneyMap<BudgetedMoney> monies{};
		const BudgetedMoney money{
			BudgetSource{QStringLiteral("Source Name 0")},
			Money{5, 34u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{9999, 6, 20}
		};
		monies.add(money);
		monies.remove(money.source());
		QCOMPARE(monies.has(money.source()), false);
	}
	void remove_DoesNotExists_ShouldThrow()
	{
		BudgetedMoneyMap<BudgetedMoney> monies{};
		QVERIFY_EXCEPTION_THROWN(monies.remove(BudgetSource{QStringLiteral("Source Name 0")}), BudgetItemError);
	}

	//}

	//{ total

	void total_WithOutValues_ShouldBeSum()
	{
		const BudgetedMoneyMap<BudgetedMoney> monies{};
		QCOMPARE(monies.total(Iso4217Codes::XXX, EventFrequency::Monthly), Money{});
	}
	void total_WithValues_ShouldBeSum()
	{
		const BudgetedMoneyMap<BudgetedMoney> monies{
			{
				{
					BudgetSource{QStringLiteral("Source Name 0")},
					BudgetedMoney{
						BudgetSource{QStringLiteral("Source Name 0")},
						Money{15, 2000u, Iso4217Codes::USD},
						EventFrequency::Hourly,
						{9999, 6, 20}
					}
				},
				{
					BudgetSource{QStringLiteral("Source Name 1")},
					BudgetedMoney{
						BudgetSource{QStringLiteral("Source Name 1")},
						Money{150, 1500u, Iso4217Codes::USD},
						EventFrequency::Weekly,
						{9999, 6, 20}
					}
				}
			}
		};
		const auto total{monies.total(Iso4217Codes::USD, EventFrequency::Monthly, 40h, Days{5})};
		QCOMPARE(total.major(), static_cast<UnitStorage>(3285));
		QCOMPARE(total.minor(), static_cast<MinorUnit>(3165));
	}

	//}

	//{ sources Tests

	void sources_hasSources_ShouldGive()
	{
		const BudgetSource source0{QStringLiteral("Source Name 0")};
		const BudgetSource source1{QStringLiteral("Source Name 1")};
		const BudgetSource source2{QStringLiteral("Source Name 2")};
		const BudgetedMoneyMap<BudgetedMoney> monies{
			{
				{
					source0,
					BudgetedMoney{
						source0,
						Money{15, 2000u, Iso4217Codes::USD},
						EventFrequency::Hourly,
						{9999, 6, 20}
					}
				},
				{
					source1,
					BudgetedMoney{
						source1,
						Money{150, 1500u, Iso4217Codes::USD},
						EventFrequency::Weekly,
						{9999, 6, 20}
					}
				},
				{
					source2,
					BudgetedMoney{
						source2,
						Money{150, 1500u, Iso4217Codes::USD},
						EventFrequency::Weekly,
						{9999, 6, 20}
					}
				}
			}
		};
		const auto actual{monies.sources()};
		const set<BudgetSource> expected{source0, source1, source2};
		QVERIFY_THAT(actual.size(), equals(expected.size()));

		for (
			auto act{actual.cbegin()}, exp{expected.cbegin()} ;
			act != actual.cend() ;
			++act, ++exp
		)
			QVERIFY_THAT(*act, equals(*exp));
	}

	//}
};

}}}

QTEST_MAIN(drn::budgeting::tests::BudgetedMoneyMapUT)
#include "BudgetedMoneyMap-unit-tests.moc"
