/*******************************************************************************
***  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 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/BudgetItemTypes.h>
using drn::budgeting::BudgetItemTypes;
#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 BudgetIteratorUT : QObject
{
	Q_OBJECT

	const BudgetSource burgerKing_{QStringLiteral("Burger King")};
	const BudgetSource mcdonalds_{QStringLiteral("McDonald's")};
	const WageMap wages_{
		{
			{
				this->mcdonalds_,
				Wage{
					this->mcdonalds_,
					Money{5, 34u, Iso4217Codes::USD},
					EventFrequency::Hourly,
					{9999, 6, 20}
				}
			},
			{
				this->burgerKing_,
				Wage{
					this->burgerKing_,
					Money{5, 34u, Iso4217Codes::USD},
					EventFrequency::Hourly,
					{9999, 6, 20}
				}
			}
		}
	};
	const BudgetSource midco_{QStringLiteral("MidCo")};
	const BudgetSource xcel_{QStringLiteral("Xcel")};
	const BillMap bills_{
		{
			{
				this->midco_,
				Bill{
					this->midco_,
					Money{525, 95u, Iso4217Codes::USD},
					EventFrequency::Monthly,
					{9999, 6, 20}
				}
			},
			{
				this->xcel_,
				Bill{
					this->xcel_,
					Money{200, 34u, Iso4217Codes::USD},
					EventFrequency::Weekly,
					{9999, 6, 20}
				}
			}
		}
	};
	const BudgetSource loan_{QStringLiteral("Home Loan")};
	const BudgetSource visa_{QStringLiteral("Visa")};
	const DebtMap debts_{
		{
			{
				this->loan_,
				Debt{
					this->loan_,
					Money{1000, 0u, Iso4217Codes::USD},
					EventFrequency::Monthly,
					{9999, 6, 20},
					Money{120000, 0u, Iso4217Codes::USD},
					Percentage{3.15}
				}
			},
			{
				this->visa_,
				Debt{
					this->visa_,
					Money{500, 0u, Iso4217Codes::USD},
					EventFrequency::Monthly,
					{9999, 6, 20},
					Money{30000, 0u, Iso4217Codes::USD},
					Percentage{3.15}
				}
			}
		}
	};
	const BudgetSource computer_{QStringLiteral("Computer")};
	const BudgetSource vacation_{QStringLiteral("Vacation")};
	const GoalMap goals_{
		{
			{
				this->computer_,
				Goal{
					this->computer_,
					Money{100, 0u, Iso4217Codes::USD},
					EventFrequency::Weekly,
					{9999, 6, 20},
					Money{1000, 0u, Iso4217Codes::USD}
				}
			},
			{
				this->vacation_,
				Goal{
					this->computer_,
					Money{500, 0u, Iso4217Codes::USD},
					EventFrequency::Monthly,
					{9999, 6, 20},
					Money{5000, 0u, Iso4217Codes::USD}
				}
			}
		}
	};
	const BudgetSource myDiscretionary_{"My Discretionary Spending"};
	const BudgetSource wifeDiscretionary_{"Wife's Discretionary Spending"};
	const NontrackMap nontracks_{
		{
			{
				this->myDiscretionary_,
				Nontrack{
					this->myDiscretionary_,
					Money{25, 0u, Iso4217Codes::USD},
					EventFrequency::Weekly,
					{1998, 6, 20}
				}
			},
			{
				this->wifeDiscretionary_,
				Nontrack{
					this->wifeDiscretionary_,
					Money{25, 0u, Iso4217Codes::USD},
					EventFrequency::Weekly,
					{1998, 6, 20}
				}
			}
		}
	};
	const Budget budget_{
		this->wages_,
		this->bills_,
		this->debts_,
		this->goals_,
		this->nontracks_
	};

private slots:
	void constructor_fullInit_shouldPointToEnd()
	{
		Budget::Iterator iter{this->budget_};
		QVERIFY_TRUE(iter == iter.end());
	}

	void postfixOperator_increment_shouldReturnPrevious()
	{
		Budget::Iterator iter{this->budget_};
		iter = iter.begin();
		const auto prev{iter++};
		QVERIFY_THAT(prev->first.source_, equals(this->midco_));
		QVERIFY_THAT(iter->first.source_, equals(this->xcel_));
	}

	void prefixOperator_increment_shouldReturnNext()
	{
		Budget::Iterator iter{this->budget_};
		iter = iter.begin();
		const auto next{++iter};
		QVERIFY_THAT(next->first.source_, equals(this->xcel_));
		QVERIFY_THAT(iter->first.source_, equals(this->xcel_));
	}

	void budgetBegin_interating_shouldMatchExpectedElements()
	{
		auto iter{this->budget_.begin()};

		//{ Bills

		QVERIFY_THAT(
			(*iter).first,
			equals(BudgetItemIdentifier{BudgetItemTypes::Bill, this->midco_})
		);
		QVERIFY_THAT(
			dynamic_cast<const Bill&>(*(*iter).second),
			equals(this->budget_.bills_.at(this->midco_))
		);
		++iter;
		QVERIFY_THAT(
			iter->first,
			equals(BudgetItemIdentifier{BudgetItemTypes::Bill, this->xcel_})
		);
		QVERIFY_THAT(
			dynamic_cast<const Bill&>(*iter->second),
			equals(this->budget_.bills_.at(this->xcel_))
		);

		//}

		//{ Debts

		++iter;
		QVERIFY_THAT(
			iter->first,
			equals(BudgetItemIdentifier{BudgetItemTypes::Debt, this->loan_})
		);
		QVERIFY_THAT(
			dynamic_cast<const Debt&>(*iter->second),
			equals(this->budget_.debts_.at(this->loan_))
		);
		++iter;
		QVERIFY_THAT(
			iter->first,
			equals(BudgetItemIdentifier{BudgetItemTypes::Debt, this->visa_})
		);
		QVERIFY_THAT(
			dynamic_cast<const Debt&>(*iter->second),
			equals(this->budget_.debts_.at(this->visa_))
		);

		//}

		//{ Goals

		++iter;
		QVERIFY_THAT(
			iter->first,
			equals(BudgetItemIdentifier{BudgetItemTypes::Goal, this->computer_})
		);
		QVERIFY_THAT(
			dynamic_cast<const Goal&>(*iter->second),
			equals(this->budget_.goals_.at(this->computer_))
		);
		++iter;
		QVERIFY_THAT(
			iter->first,
			equals(BudgetItemIdentifier{BudgetItemTypes::Goal, this->vacation_})
		);
		QVERIFY_THAT(
			dynamic_cast<const Goal&>(*iter->second),
			equals(this->budget_.goals_.at(this->vacation_))
		);

		//}

		//{ Nontracks

		++iter;
		QVERIFY_THAT(
			iter->first,
			equals(BudgetItemIdentifier{BudgetItemTypes::Nontrack, this->myDiscretionary_})
		);
		QVERIFY_THAT(
			dynamic_cast<const Nontrack&>(*iter->second),
			equals(this->budget_.nontracks_.at(this->myDiscretionary_))
		);
		++iter;
		QVERIFY_THAT(
			iter->first,
			equals(BudgetItemIdentifier{BudgetItemTypes::Nontrack, this->wifeDiscretionary_})
		);
		QVERIFY_THAT(
			dynamic_cast<const Nontrack&>(*iter->second),
			equals(this->budget_.nontracks_.at(this->wifeDiscretionary_))
		);

		//}

		//{ Wages

		++iter;
		QVERIFY_THAT(
			iter->first,
			equals(BudgetItemIdentifier{BudgetItemTypes::Wage, this->burgerKing_})
		);
		QVERIFY_THAT(
			dynamic_cast<const Wage&>(*iter->second),
			equals(this->budget_.wages_.at(this->burgerKing_))
		);
		++iter;
		QVERIFY_THAT(
			iter->first,
			equals(BudgetItemIdentifier{BudgetItemTypes::Wage, this->mcdonalds_})
		);
		QVERIFY_THAT(
			dynamic_cast<const Wage&>(*iter->second),
			equals(this->budget_.wages_.at(this->mcdonalds_))
		);

		//}
	}
};

}}}

QTEST_MAIN(drn::budgeting::tests::BudgetIteratorUT)
#include "BudgetIterator-unit-tests.moc"
