/*******************************************************************************
***  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 <QDate>
using ::QDate;
#include <QTest>
// using GTEST_MAIN
#include <qt-mocks/Matchers.h>
using qt_mocks::equals;
using qt_mocks::isTrue;
using qt_mocks::memberFunction;
// using QVERIFY_THAT;

#include <cstddef>
using std::size_t;
#include <map>
using std::map;
#include <set>
using std::set;
#include <string>
using namespace std::string_literals;
using std::string;

#include <budgeting/Budget.h>
using drn::budgeting::Budget;
using drn::budgeting::containedCurrencies;
#include <budgeting/BudgetSource.h>
using drn::budgeting::BudgetSource;
#include <budgeting/Nontrack.h>
using drn::budgeting::Nontrack;
#include <budgeting/EventFrequency.h>
using drn::budgeting::EventFrequency;
#include <budgeting/Bill.h>
using drn::budgeting::Bill;
#include <budgeting/BillMap.hpp>
using drn::budgeting::BillMap;
#include <budgeting/Goal.h>
using drn::budgeting::Goal;
#include <budgeting/GoalMap.h>
using drn::budgeting::GoalMap;
#include <budgeting/Wage.h>
using drn::budgeting::Wage;
#include <budgeting/WageMap.hpp>
using drn::budgeting::WageMap;
#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 BudgetUT : QObject
{
	Q_OBJECT

	const WageMap wages_;
	const BillMap bills_;
	const DebtMap debts_;
	const GoalMap goals_;
	const NontrackMap nontracks_;

private slots:
	//{ Constructor Tests

	void constructor_Empty_ShouldBeDefaults()
	{
		const Budget b{};
		QVERIFY2(b.wages_.empty(), "By default there should not be any wages.");
		QVERIFY2(b.bills_.empty(), "By default there should not be any bills.");
		QVERIFY2(b.debts_.empty(), "By default there should not be any debts.");
		QVERIFY2(b.goals_.empty(), "By default there should not be any debts.");
		QVERIFY2(b.nontracks_.empty(), "By default there should not be any nontrack items.");
	}
	void constructor_FullInit_ShouldBeSupplied()
	{
		const Budget b{
			this->wages_,
			this->bills_,
			this->debts_,
			this->goals_,
			this->nontracks_
		};
		QCOMPARE(b.wages_, this->wages_);
		QCOMPARE(b.bills_, this->bills_);
		QCOMPARE(b.debts_, this->debts_);
		QCOMPARE(b.goals_, this->goals_);
		QCOMPARE(b.nontracks_, this->nontracks_);
	}

	//}

	//{ isEmpty Tests

	void isEmpty_NoValues_ShouldBeTrue()
	{
		const Budget b{};
		QVERIFY2(b.isEmpty(), "An empty budget should be empty.");
	}
	void isEmpty_HasWageValue_ShouldBeFalse()
	{
		const Budget b{this->wages_};
		QVERIFY2( ! b.isEmpty(), "A non-empty budget should not be empty.");
	}
	void isEmpty_HasBillValue_ShouldBeFalse()
	{
		const Budget b{{}, this->bills_};
		QVERIFY2( ! b.isEmpty(), "A non-empty budget should not be empty.");
	}
	void isEmpty_HasDebtValue_ShouldBeFalse()
	{
		const Budget b{{}, {}, this->debts_};
		QVERIFY2( ! b.isEmpty(), "A non-empty budget should not be empty.");
	}
	void isEmpty_HasGoalValue_ShouldBeFalse()
	{
		const Budget b{{}, {}, {}, this->goals_};
		QVERIFY2( ! b.isEmpty(), "A non-empty budget should not be empty.");
	}
	void isEmpty_HasDiscretionaryValue_ShouldBeFalse()
	{
		const Budget b{{}, {}, {}, {}, this->nontracks_};
		QVERIFY2( ! b.isEmpty(), "A non-empty budget should not be empty.");
	}
	void isEmpty_HasAllValues_ShouldBeFalse()
	{
		const Budget b{
			this->wages_,
			this->bills_,
			this->debts_,
			this->goals_,
			this->nontracks_
		};
		QVERIFY2( ! b.isEmpty(), "A non-empty budget should not be empty.");
	}

	//}

	//{ containedCurrencies Tests

	void containedCurrencies_NoCurrencies_ShouldBeEmpty()
	{
		const Budget b{};
		const set<Iso4217Codes> contained{containedCurrencies(b)};
		QVERIFY_THAT(
			contained,
			memberFunction(&set<Iso4217Codes>::empty, isTrue())
		);
	}
	void containedCurrencies_HasCurrencies_ShouldBeExpected()
	{
		const Budget b{
			this->wages_,
			this->bills_,
			this->debts_,
			this->goals_,
			this->nontracks_
		};
		const set<Iso4217Codes> contained{containedCurrencies(b)};
		const size_t expectedSize{1};
		QVERIFY_THAT(contained, memberFunction(&set<Iso4217Codes>::size, equals(expectedSize)));
		QVERIFY_THAT(*contained.find(Iso4217Codes::USD), equals(Iso4217Codes::USD));
	}

	//}

	//{ size Tests

	void size_Empty_ShouldBeZero()
	{
		const Budget b{};
		QVERIFY_THAT(b.size(), equals(static_cast<size_t>(0)));
	}

	void size_Filled_ShouldBeSum()
	{
		const Budget b{
			this->wages_,
			this->bills_,
			this->debts_,
			this->goals_,
			this->nontracks_
		};
		QVERIFY_THAT(
			b.size(),
			equals(
				static_cast<size_t>(
					this->wages_.size()
						+ this->bills_.size()
						+ this->debts_.size()
						+ this->goals_.size()
						+ this->nontracks_.size()
				)
			)
		);
	}

	//}

public:
	BudgetUT() :
		QObject{},
		wages_{
			{
				{
					BudgetSource{QStringLiteral("Burger King")},
					Wage{
						BudgetSource{QStringLiteral("Burger King")},
						Money{5, 34u, Iso4217Codes::USD},
						EventFrequency::Hourly,
						{9999, 6, 20}
					}
				},
				{
					BudgetSource{QStringLiteral("McDonald's")},
					Wage{
						BudgetSource{QStringLiteral("McDonald's")},
						Money{5, 34u, Iso4217Codes::USD},
						EventFrequency::Hourly,
						{9999, 6, 20}
					}
				}
			}
		},
		bills_{
			{
				{
					BudgetSource{QStringLiteral("MidCo")},
					Bill{
						BudgetSource{QStringLiteral("MidCo")},
						Money{525, 95u, Iso4217Codes::USD},
						EventFrequency::Monthly,
						{9999, 6, 20}
					}
				},
				{
					BudgetSource{QStringLiteral("Xcel")},
					Bill{
						BudgetSource{QStringLiteral("Xcel")},
						Money{200, 34u, Iso4217Codes::USD},
						EventFrequency::Weekly,
						{9999, 6, 20}
					}
				}
			}
		},
		debts_{
			{
				{
					BudgetSource{QStringLiteral("Home Federal Bank")},
					Debt{
						BudgetSource{QStringLiteral("Home Federal Bank")},
						Money{1000, 0u, Iso4217Codes::USD},
						EventFrequency::Monthly,
						{9999, 6, 20},
						Money{120000, 0u, Iso4217Codes::USD},
						Percentage{3.15}
					}
				},
				{
					BudgetSource{QStringLiteral("Visa")},
					Debt{
						BudgetSource{QStringLiteral("Visa")},
						Money{500, 0u, Iso4217Codes::USD},
						EventFrequency::Monthly,
						{9999, 6, 20},
						Money{30000, 0u, Iso4217Codes::USD},
						Percentage{3.15}
					}
				}
			}
		},
		goals_{
			{
				{
					BudgetSource{QStringLiteral("Computer")},
					Goal{
						BudgetSource{QStringLiteral("Computer")},
						Money{100, 0u, Iso4217Codes::USD},
						EventFrequency::Weekly,
						{9999, 6, 20},
						Money{1000, 0u, Iso4217Codes::USD}
					}
				},
				{
					BudgetSource{QStringLiteral("Vacation")},
					Goal{
						BudgetSource{QStringLiteral("Computer")},
						Money{500, 0u, Iso4217Codes::USD},
						EventFrequency::Monthly,
						{9999, 6, 20},
						Money{5000, 0u, Iso4217Codes::USD}
					}
				}
			}
		},
		nontracks_{
			{
				{
					BudgetSource{"My Discretionary Spending"},
					Nontrack{
						BudgetSource{"My Discretionary Spending"},
						Money{25, 0u, Iso4217Codes::USD},
						EventFrequency::Weekly,
						{1998, 6, 20}
					}
				},
				{
					BudgetSource{"Wife's Discretionary Spending"},
					Nontrack{
						BudgetSource{"Wife's Discretionary Spending"},
						Money{25, 0u, Iso4217Codes::USD},
						EventFrequency::Weekly,
						{1998, 6, 20}
					}
				}
			}
		}
	{}
};

}}}

QTEST_MAIN(drn::budgeting::tests::BudgetUT)
#include "Budget-unit-tests.moc"
