/*******************************************************************************
***  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 "BudgetBankLedgers.h"
// using assert
using std::chrono::hours;
using std::make_unique;
using std::map;
using std::move;
using std::pair;
using std::unique_ptr;
using drn::accounting::AccountCode;
using drn::accounting::AccountNumber;
using drn::accounting::GeneralLedger;
using drn::accounting::presentationText;
using drn::banking::BankMap;
using drn::banking::ReconciledBankAccount;
using drn::banking::ReconciledBankAccounts;
using drn::banking::toAccountTypes;
using drn::budgeting::Budget;
using drn::foundation::Days;
using drn::foundation::TypeIndexMap;
using drn::navigation::internal::BankLedgers;
using drn::navigation::internal::BudgetBankLedgers;
using drn::navigation::internal::BudgetLedgers;

#include <pecunia/Codes.h>
using pecunia::currency::Iso4217Codes;
#include <pecunia/Money.h>
using pecunia::currency::Money;
#include <QDate>
using ::QDate;
#include <QObject>
using ::QObject;
#include <QString>
using ::QString;

#include <algorithm>
using std::find_if;
#include <cassert>
//using assert
#include <exception>
using std::exception;
#include <ostream>
using std::ostream;
#include <set>
using std::set;
#include <stdexcept>
using std::logic_error;

#include <accounting/Account.h>
using drn::accounting::Account;
#include <accounting/AccountCode.h>
using drn::accounting::AccountCode;
#include <accounting/AccountingErrors.h>
using drn::accounting::AccountError;
#include <accounting/Transaction.h>
using drn::accounting::Transaction;
using drn::accounting::TransactionNumber;
using drn::accounting::TransactionStatuses;
#include <accounting/AccountNumberGenerator.h>
using drn::accounting::AccountNumberGenerator;
using drn::accounting::SequentialAccountNumberGenerator;
#include <banking/BankName.h>
using drn::banking::Bank;
using drn::banking::BankName;
#include <banking/BankAccount.h>
using drn::banking::BankAccount;
#include <banking/BankAccountType.h>
using drn::banking::BankAccountType;
using drn::banking::SupportedAccountTypes;
#include <banking/BankingErrors.h>
using drn::banking::BankError;
#include <budgeting/Bill.h>
using drn::budgeting::Bill;
#include <budgeting/Algorithm.h>
using drn::budgeting::adjustTo;
#include <budgeting/BudgetingErrors.h>
using drn::budgeting::BudgetItemError;
#include <budgeting/BudgetItemDue.h>
using drn::budgeting::BudgetItemDue;
#include <budgeting/BudgetItemIdentifier.h>
using drn::budgeting::BudgetItemIdentifier;
#include <budgeting/BudgetItemTypes.h>
using drn::budgeting::BudgetItemTypes;
#include <budgeting/BudgetSource.h>
using drn::budgeting::BudgetSource;
#include <budgeting/Debt.h>
using drn::budgeting::Debt;
#include <budgeting/EventFrequency.h>
using drn::budgeting::EventFrequency;
#include <budgeting/Goal.h>
using drn::budgeting::Goal;
#include <budgeting/Nontrack.h>
using drn::budgeting::Nontrack;
#include <budgeting/Wage.h>
using drn::budgeting::Wage;
#include <foundation/CalendarTime.hpp>
using drn::foundation::daysInYear;
using drn::foundation::monthsInYear;
#include <foundation/Error.h>
using drn::foundation::Error;
#include <foundation/Optional.hpp>
using drn::foundation::Optional;
#include <navigation/BankAccountSurvey.h>
using drn::navigation::BankAccountSurvey;
#include <navigation/BudgetBankAccount.h>
using drn::navigation::BudgetBankAccount;


void BudgetBankLedgers::verifyBudgetSourceName(const BudgetSource& bs)
{
	for (const auto& accountCode : this->ledgers_.accountCodes())
		if (accountCode.name() == bs.name())
			throw BudgetItemError{
				bs,
				QObject::tr("The budget item has the same name as an existing ledger account.")
			};
}

void BudgetBankLedgers::verifyAccountCodeName(const AccountCode& ac)
{
	for (const auto& typeSource : this->budget_.sources())
		for (const auto& source : typeSource.second)
			if (source.name() == ac.name())
				throw AccountError{
					ac,
					QObject::tr("The code name is the same name as an existing budget item.")
				};
}

BudgetBankLedgers::BudgetBankLedgers() :
	BudgetBankLedgers{{}, {}, {}, {}, {}, make_unique<SequentialAccountNumberGenerator>()}
{}

BudgetBankLedgers::BudgetBankLedgers(
	GeneralLedger ledgers,
	Budget budget,
	TypeIndexMap<BudgetSource, AccountCode> budgetCodes,
	BankMap banks,
	map<BankName, map<AccountNumber, SupportedAccountTypes>> bankAccountTypes,
	unique_ptr<AccountNumberGenerator> accountCodeNumberGenerator
) :
	ledgers_{move(ledgers)},
	budget_{move(budget)},
	banks_{move(banks)},
	accountCodeNumberGenerator_{move(accountCodeNumberGenerator)},
	budgetLedgers_{&this->budget_, &this->ledgers_, move(budgetCodes)},
	bankLedgers_{&this->banks_, &this->ledgers_, move(bankAccountTypes)},
	reconciliations_{} // TODO: populate from constructor
{
	assert(
		this->accountCodeNumberGenerator_ != nullptr
		&& "The account code number generator must be set"
	);
	const auto accountCodes{this->ledgers_.accountCodes()};

	for (const auto& typeSource : this->budget_.sources())
		for (const auto& source : typeSource.second)
			for (const auto& accountCode : accountCodes)
				if (source.name() == accountCode.name())
					throw AccountError{
						accountCode,
						QObject::tr(
							"Found an account code name that is the same name as a budget item."
						)
					};
}

BudgetBankLedgers::BudgetBankLedgers(const BudgetBankLedgers& other) :
	ledgers_{other.ledgers_},
	budget_{other.budget_},
	banks_{other.banks_},
	accountCodeNumberGenerator_{other.accountCodeNumberGenerator_->clone()},
	budgetLedgers_{&this->budget_, &this->ledgers_, other.budgetLedgers_.budgetAccountCodes()},
	bankLedgers_{&this->banks_, &this->ledgers_, other.bankLedgers_.bankAccountTypes()}
{}

BudgetBankLedgers::BudgetBankLedgers(BudgetBankLedgers&& other) noexcept :
	ledgers_{move(other.ledgers_)},
	budget_{move(other.budget_)},
	banks_{move(other.banks_)},
	accountCodeNumberGenerator_{move(other.accountCodeNumberGenerator_)},
	budgetLedgers_{&this->budget_, &this->ledgers_, other.budgetLedgers_.budgetAccountCodes()},
	bankLedgers_{&this->banks_, &this->ledgers_, other.bankLedgers_.bankAccountTypes()}
{}

BudgetBankLedgers::~BudgetBankLedgers() = default;

BudgetBankLedgers& BudgetBankLedgers::operator=(BudgetBankLedgers&& other) noexcept
{
	if (this != &other)
	{
		this->ledgers_ = move(other.ledgers_);
		this->budget_ = move(other.budget_);
		this->banks_ = move(other.banks_);
		this->bankLedgers_ = BankLedgers{
			&this->banks_,
			&this->ledgers_,
			other.bankLedgers_.bankAccountTypes()
		};
		this->budgetLedgers_ = BudgetLedgers{
			&this->budget_,
			&this->ledgers_,
			other.budgetLedgers_.budgetAccountCodes()
		};
		this->accountCodeNumberGenerator_ = move(other.accountCodeNumberGenerator_);
	}
	return *this;
}

const BankLedgers& BudgetBankLedgers::bankLedgers() const
{
	return this->bankLedgers_;
}

const BudgetLedgers& BudgetBankLedgers::budgetLedgers() const
{
	return this->budgetLedgers_;
}

void BudgetBankLedgers::setManaged(
	GeneralLedger ledgers,
	Budget budget,
	TypeIndexMap<BudgetSource, AccountCode> budgetCodes,
	BankMap banks,
	map<BankName, map<AccountNumber, SupportedAccountTypes>> bankAccountTypes
)
{
	auto copy{*this};

	try
	{
		this->ledgers_ = move(ledgers);
		this->budget_ = move(budget);
		this->banks_ = move(banks);
		this->accountCodeNumberGenerator_ = make_unique<SequentialAccountNumberGenerator>(
			this->accountCodeNumberGenerator_->current(this->ledgers_.accounts())
		);
		this->budgetLedgers_ = BudgetLedgers
		{
			&this->budget_,
			&this->ledgers_,
			move(budgetCodes)
		};
		this->bankLedgers_ = BankLedgers{&this->banks_, &this->ledgers_, move(bankAccountTypes)};
	}
	catch (const exception&)
	{
		*this = move(copy);
		throw;
	}
}

bool BudgetBankLedgers::isEmpty() const
{
	return this->budget_.isEmpty() && this->ledgers_.isEmpty() && this->banks_.empty();
}

void BudgetBankLedgers::reset()
{
	this->setManaged({}, {}, {}, {}, {});
}

const Budget& BudgetBankLedgers::budget() const
{
	return this->budget_;
}

const GeneralLedger& BudgetBankLedgers::ledgers() const
{
	return this->ledgers_;
}

const BankMap& BudgetBankLedgers::banks() const
{
	return this->banks_;
}

set<BankAccountType> BudgetBankLedgers::bankAccountTypes() const
{
	const auto bankAccountTypes{this->bankLedgers_.bankAccountTypes()};
	set<BankAccountType> accountBankTypes;

	for (const auto& bankAccountType : bankAccountTypes)
		for (const auto& accountTypes : bankAccountType.second)
		{
			BankAccountType accountType{AccountCode{accountTypes.first}, accountTypes.second};
			assert(
				accountBankTypes.find(accountType) == accountBankTypes.cend()
				&& "Only one bank should ever be associated with an account code."
			);
			accountBankTypes.emplace(move(accountType));
		}
	return accountBankTypes;
}

void BudgetBankLedgers::setAccountNumberGenerator(
	unique_ptr<AccountNumberGenerator> accountCodeNumberGenerator
)
{
	assert(
		accountCodeNumberGenerator != nullptr
		&& "The account code number generator cannot be null."
	);
	this->accountCodeNumberGenerator_ = move(accountCodeNumberGenerator);
}

const AccountNumberGenerator& BudgetBankLedgers::accountCodeNumberGenerator() const
{
	assert(
		this->accountCodeNumberGenerator_ != nullptr
		&& "The account code number generator should never be null."
	);
	return *this->accountCodeNumberGenerator_;
}

set<BankName> BudgetBankLedgers::bankNames() const
{
	return this->banks_.names();
}

//{ Budgeting member functions

void BudgetBankLedgers::removeWage(const BudgetSource& source)
{
	this->budgetLedgers_.removeWage(source);
}

void BudgetBankLedgers::removeBill(const BudgetSource& source)
{
	this->budgetLedgers_.removeBill(source);
}

void BudgetBankLedgers::removeDebt(const BudgetSource& source)
{
	this->budgetLedgers_.removeDebt(source);
}

void BudgetBankLedgers::removeGoal(const BudgetSource& source)
{
	this->budgetLedgers_.removeGoal(source);
}

void BudgetBankLedgers::removeNontrack(const BudgetSource& source)
{
	this->budgetLedgers_.removeNontrack(source);
}

void BudgetBankLedgers::renameWage(const BudgetSource& original, const BudgetSource& changed)
{
	this->verifyBudgetSourceName(changed);
	this->budgetLedgers_.renameWage(original, changed, {});
}

void BudgetBankLedgers::renameBill(const BudgetSource& original, const BudgetSource& changed)
{
	this->verifyBudgetSourceName(changed);
	this->budgetLedgers_.renameBill(original, changed, {});
}

void BudgetBankLedgers::renameDebt(const BudgetSource& original, const BudgetSource& changed)
{
	this->verifyBudgetSourceName(changed);
	this->budgetLedgers_.renameDebt(original, changed, {});
}

void BudgetBankLedgers::renameGoal(const BudgetSource& original, const BudgetSource& changed)
{
	this->verifyBudgetSourceName(changed);
	this->budgetLedgers_.renameGoal(original, changed, {});
}

void BudgetBankLedgers::renameNontrack(const BudgetSource& original, const BudgetSource& changed)
{
	this->verifyBudgetSourceName(changed);
	this->budgetLedgers_.renameNontrack(original, changed, {});
}

//}

//{ Accounting member functions

void BudgetBankLedgers::add(Account account)
{
	this->verifyAccountCodeName(account.code());
	this->ledgers_.add(move(account), *this->accountCodeNumberGenerator_);
}

void BudgetBankLedgers::close(const AccountCode& code)
{
	this->ledgers_.close(code);
}

void BudgetBankLedgers::open(const AccountCode& code)
{
	this->ledgers_.open(code);
}

void BudgetBankLedgers::remove(const AccountCode& code)
{
	const auto& budgetAccountCodes{this->budgetLedgers_.budgetAccountCodes()};
	const BudgetSource source{code.name()};

	if (budgetAccountCodes.has<Bill>(source))
		this->budgetLedgers_.removeBill(source);
	else if (budgetAccountCodes.has<Debt>(source))
		this->budgetLedgers_.removeDebt(source);
	else if (budgetAccountCodes.has<Goal>(source))
		this->budgetLedgers_.removeGoal(source);
	else if (budgetAccountCodes.has<Nontrack>(source))
		this->budgetLedgers_.removeNontrack(source);
	else if (budgetAccountCodes.has<Wage>(source))
		this->budgetLedgers_.removeWage(source);
	else
		this->ledgers_.remove(code);
	const auto accountBankName{this->bankLedgers_.findBankName(code.number())};

	if (accountBankName.hasValue())
		this->bankLedgers_.removeAssociation(*accountBankName, code.number());
}

void BudgetBankLedgers::recode(const AccountCode& original, const AccountCode& changed)
{
	this->verifyAccountCodeName(changed);
	const auto& budgetAccountCodes{this->budgetLedgers_.budgetAccountCodes()};
	const BudgetSource originalSource{original.name()};
	const BudgetSource changedSource{changed.name()};

	if (budgetAccountCodes.has<Bill>(originalSource))
		this->budgetLedgers_.renameBill(originalSource, changedSource, changed.number());
	else if (budgetAccountCodes.has<Debt>(originalSource))
		this->budgetLedgers_.renameDebt(originalSource, changedSource, changed.number());
	else if (budgetAccountCodes.has<Goal>(originalSource))
		this->budgetLedgers_.renameGoal(originalSource, changedSource, changed.number());
	else if (budgetAccountCodes.has<Nontrack>(originalSource))
		this->budgetLedgers_.renameNontrack(originalSource, changedSource, changed.number());
	else if (budgetAccountCodes.has<Wage>(originalSource))
		this->budgetLedgers_.renameWage(originalSource, changedSource, changed.number());
	else
		this->ledgers_.recode(original, changed);
	const auto accountBankName{this->bankLedgers_.findBankName(original.number())};

	if (accountBankName.hasValue())
		this->bankLedgers_.recode(original.number(), changed.number());
}

void BudgetBankLedgers::clear(const TransactionNumber& number)
{
	this->ledgers_.clear(number);
}

void BudgetBankLedgers::reconcile(const TransactionNumber& number)
{
	this->ledgers_.reconcile(number);
}

TransactionNumber BudgetBankLedgers::post(const Transaction& txn)
{
	return this->ledgers_.post(txn);
}

void BudgetBankLedgers::repost(const TransactionNumber& number, const Transaction& txn)
{
	return this->ledgers_.repost(number, txn);
}

void BudgetBankLedgers::remove(const TransactionNumber& number)
{
	this->ledgers_.remove(number);
}

//}

//{ Bank Member Functions

void BudgetBankLedgers::add(
	Bank b,
	map<AccountNumber, SupportedAccountTypes> accountNumbersBankTypes
)
{
	this->bankLedgers_.add(move(b), move(accountNumbersBankTypes));
}

void BudgetBankLedgers::addAssociation(
	const BankName& name,
	const AccountNumber& number,
	const SupportedAccountTypes& type
)
{
	this->bankLedgers_.addAssociation(name, number, type);
}

AccountCode BudgetBankLedgers::addAccount(
	QString name,
	const SupportedAccountTypes& type,
	const Money& openingBalance
)
try
{
	const AccountCode ac{++*this->accountCodeNumberGenerator_, move(name)};
	Account a{toAccountTypes(type), ac, openingBalance};
	this->add(move(a));
	return ac;
}
catch (const exception&)
{
	--*this->accountCodeNumberGenerator_;
	throw;
}

void BudgetBankLedgers::update(
	Bank b,
	map<AccountNumber, SupportedAccountTypes> accountNumbersBankTypes
)
{
	this->bankLedgers_.update(move(b), move(accountNumbersBankTypes));
}

void BudgetBankLedgers::close(const BankName& name)
{
	this->bankLedgers_.close(name);
}

void BudgetBankLedgers::open(const BankName& name)
{
	this->bankLedgers_.open(name);
}

void BudgetBankLedgers::rename(const BankName& original, const BankName& changed)
{
	this->bankLedgers_.rename(original, changed);
}

void BudgetBankLedgers::remove(const BankName& bank)
{
	this->bankLedgers_.remove(bank);
}

//}

TransactionNumber BudgetBankLedgers::post(
	const BudgetItemDue& bid,
	const AccountCode& ac,
	const QDate& postOn
)
{
	const auto& budgetAccountCodes{this->budgetLedgers_.budgetAccountCodes()};

	switch(bid.id_.type_)
	{
	case BudgetItemTypes::Bill:
		if (budgetAccountCodes.has<Bill>(bid.id_.source_))
			return this->budgetLedgers_.postBill(bid.id_.source_, ac, bid.amount_, postOn);
		break;
	case BudgetItemTypes::Debt:
		if (budgetAccountCodes.has<Debt>(bid.id_.source_))
			return this->budgetLedgers_.postDebt(bid.id_.source_, ac, bid.amount_, postOn);
		break;
	case BudgetItemTypes::Goal:
		if (budgetAccountCodes.has<Goal>(bid.id_.source_))
			return this->budgetLedgers_.postGoal(bid.id_.source_, ac, bid.amount_, postOn);
		break;
	case BudgetItemTypes::Nontrack:
		if (budgetAccountCodes.has<Nontrack>(bid.id_.source_))
			return this->budgetLedgers_.postNontrack(bid.id_.source_, ac, bid.amount_, postOn);
		break;
	case BudgetItemTypes::Wage:
		if (budgetAccountCodes.has<Wage>(bid.id_.source_))
			return this->budgetLedgers_.postWage(bid.id_.source_, ac, bid.amount_, postOn);
		break;
	case BudgetItemTypes::Unknown:
		throw BudgetItemError{
			bid.id_.source_,
			QObject::tr("Cannot post budget item due that has an unknown type.")
		};
		break;
	default:
		throw logic_error{"The Budget Item Type is not implemented for posting budget items."};
	};
	throw BudgetItemError{
		bid.id_.source_,
		QObject::tr("Failed to locate budget item source to post.")
	};
}

TransactionNumber BudgetBankLedgers::post(
	const BudgetItemIdentifier& bii,
	const Money& amount,
	const AccountCode& ac,
	const QDate& postOn
)
{
	return this->budgetLedgers_.postUnexpected(bii, amount, ac, postOn);
}

void BudgetBankLedgers::skip(const BudgetItemIdentifier& bii)
{
	const auto& budgetAccountCodes{this->budgetLedgers_.budgetAccountCodes()};

	switch (bii.type_)
	{
	case BudgetItemTypes::Bill:
		if (budgetAccountCodes.has<Bill>(bii.source_))
			return this->budgetLedgers_.skipBill(bii.source_);
		break;
	case BudgetItemTypes::Debt:
		if (budgetAccountCodes.has<Debt>(bii.source_))
			return this->budgetLedgers_.skipDebt(bii.source_);
		break;
	case BudgetItemTypes::Goal:
		if (budgetAccountCodes.has<Goal>(bii.source_))
			return this->budgetLedgers_.skipGoal(bii.source_);
		break;
	case BudgetItemTypes::Nontrack:
		if (budgetAccountCodes.has<Nontrack>(bii.source_))
			return this->budgetLedgers_.skipNontrack(bii.source_);
		break;
	case BudgetItemTypes::Wage:
		if (budgetAccountCodes.has<Wage>(bii.source_))
			return this->budgetLedgers_.skipWage(bii.source_);
		break;
	case BudgetItemTypes::Unknown:
		throw BudgetItemError{
			bii.source_,
			QObject::tr("Cannot skip a budget item when its type is not known.")
		};
	default:
		throw logic_error{
			"An unknown budget item type was supplied when attempting to skip a budget item."
		};
	};
	throw BudgetItemError{bii.source_, QObject::tr("Failed to locate the budget item to skip.")};
}

set<BudgetBankAccount> BudgetBankLedgers::budgetBankAccounts(
	const BudgetItemTypes& type
) const
{
	const map<BudgetSource, AccountCode> sourceAccountCodes{
		[&type, this] ()
		{
			map<BudgetSource, AccountCode> codes{};

			switch (type)
			{
			case BudgetItemTypes::Bill:
				for (const auto& bill : this->budget_.bills_)
					codes.emplace(
						bill.first,
						this->budgetLedgers_.budgetAccountCode<Bill>(bill.first)
					);
				break;
			case BudgetItemTypes::Debt:
				for (const auto& debt : this->budget_.debts_)
					codes.emplace(
						debt.first,
						this->budgetLedgers_.budgetAccountCode<Debt>(debt.first)
					);
				break;
			case BudgetItemTypes::Goal:
				for (const auto& goal : this->budget_.goals_)
					codes.emplace(
						goal.first,
						this->budgetLedgers_.budgetAccountCode<Goal>(goal.first)
					);
				break;
			case BudgetItemTypes::Nontrack:
				for (const auto& nontrack : this->budget_.nontracks_)
					codes.emplace(
						nontrack.first,
						this->budgetLedgers_.budgetAccountCode<Nontrack>(nontrack.first)
					);
				break;
			case BudgetItemTypes::Wage:
				for (const auto& wage : this->budget_.wages_)
					codes.emplace(
						wage.first,
						this->budgetLedgers_.budgetAccountCode<Wage>(wage.first)
					);
				break;
			case BudgetItemTypes::Unknown:
				break;
			default:
				throw logic_error{
					"An unknown budget item type was supplied when attempting to gather the "
						"budget item bank accounts."
				};
			};
			return codes;
		}()
	};
	set<BudgetBankAccount> accountCodes{};
	auto bankAccountTypes{this->bankLedgers_.bankAccountTypes()};

	for (const auto& sourceAccountCode : sourceAccountCodes)
	{
		bool foundBankAccount{false};

		for (const auto& bankAccounts : bankAccountTypes)
		{
			for (const auto& accountType : bankAccounts.second)
			{
				if (sourceAccountCode.second.number() == accountType.first)
				{
					if (accountType.second == SupportedAccountTypes::Goal) // TODO: add unit tests
					{
						const auto parent{
							this->bankLedgers_.ledgers().ledger(accountType.first).account_.code()
									.parent()
						};
						assert(parent.hasValue() && "A child account must always have a parent.");
						const auto parentType{bankAccountTypes.at(bankAccounts.first).at(*parent)};
						accountCodes.emplace(
							BudgetBankAccount{
								BudgetItemIdentifier{type, sourceAccountCode.first},
								BankAccount{
									bankAccounts.first,
									{
										this->bankLedgers_.ledgers().ledger(*parent).account_
												.code(),
										parentType
									}
								}
							}
						);
					}
					else
						accountCodes.emplace(
							BudgetBankAccount{
								BudgetItemIdentifier{type, sourceAccountCode.first},
								BankAccount{
									bankAccounts.first,
									{
										this->bankLedgers_.ledgers().ledger(accountType.first)
												.account_.code(),
										accountType.second
									}
								}
							}
						);
					// Since only one budget item will match to one bank account, we can remove it
					// from the search space.
					bankAccountTypes[bankAccounts.first].erase(accountType.first);
					foundBankAccount = true;
					break;
				}
			}

			if (foundBankAccount)
				break;
		}
	}
	return accountCodes;
}

BankAccountSurvey BudgetBankLedgers::projectedSurvey(
	const BankName& bn,
	const AccountNumber& an,
	const QDate& surveyedOn,
	const hours& hrInWeek,
	const Days& dayInWeek
)
{
	// TODO: confirm the bank exists.
	// TODO: confirm the bank account exists.
	const auto& bankLedger{this->ledgers().ledger(an)};
	const BankAccount bankAccount{
		bn,
		BankAccountType{
			bankLedger.account_.code(),
			this->bankLedgers_.bankAccountTypes().at(bn).at(an)
		}
	};
	const auto foundReconciled{this->reconciliations_.find(bn, an)};
	const auto reconciledAccount{
		foundReconciled.valueOr(
			ReconciledBankAccount{bankAccount, bankLedger.account_.balance().code()}
		)
	};
	Money projectedBalance{reconciledAccount.balance()}; // TODO: unit test the previous balance is used.
	map<BudgetItemIdentifier, set<Transaction>> usedBudget;
	map<BudgetItemIdentifier, Money> unusedBudget;
	const QDate lastReconciledOn{
		reconciledAccount.reconciledOn().valueOr(
			 ! bankLedger.transactions_.empty()
				// Use the date of the opening balance when it's never been reconciled.
				? bankLedger.transactions_.begin()->second.occurredOn()
				// When there wasn't an opening balance, best can be done is the date of the survey.
				: surveyedOn // TODO: unit test when there was not an opening balance.
		)
	};
	// Assume every budget item was due during the survey period at least once.
	const auto monthsSinceLast{
		1 + static_cast<int>(
			static_cast<double>(lastReconciledOn.daysTo(surveyedOn)) / (daysInYear / monthsInYear)
		)
	};

	for (const auto& budgetItem : this->budget_)
	{
		// Zero it to the same currency.
		unusedBudget[budgetItem.first].setCode(budgetItem.second->amount().code());
		const auto perMonth{
			adjustTo(
				EventFrequency::Monthly,
				budgetItem.second->amount(),
				budgetItem.second->period(),
				hrInWeek,
				dayInWeek
			)
		};

		for (int count{0} ; count < monthsSinceLast ; ++count)
			unusedBudget[budgetItem.first] += perMonth;
	}

	// Gather up all the transactions, removing the money used from the unused tracker.
	for (const auto& txnNumTxn : bankLedger.transactions_)
	{
		if (txnNumTxn.second.status() == TransactionStatuses::Reconciled) // TODO: unit test this
			continue;

		if (txnNumTxn.second.occurredOn() > surveyedOn) // TODO: unit test this
			continue;
		BudgetItemIdentifier id{};

		if (txnNumTxn.second.debiting() == an) // Increasing the bank account balance.
		{
			id = this->budgetLedgers_.lookUpBudgetId(txnNumTxn.second.crediting().number());
			projectedBalance += txnNumTxn.second.amount();
			unusedBudget[id] -= txnNumTxn.second.amount();
		}
		else
		{
			id = this->budgetLedgers_.lookUpBudgetId(txnNumTxn.second.debiting().number());
			projectedBalance -= txnNumTxn.second.amount();
			unusedBudget[id] -= txnNumTxn.second.amount();
		}

		if (unusedBudget[id] < 0) // TODO: decide if unspent should be negative when you over spend.
			unusedBudget[id] = Money{unusedBudget[id].code()};
		usedBudget[move(id)].emplace(txnNumTxn.second);
	}
	return BankAccountSurvey{
		reconciledAccount,
		move(projectedBalance),
		move(usedBudget),
		move(unusedBudget)
	};
}

void BudgetBankLedgers::addBudgetBankAssociation(
	const Bill& b,
	AccountCode&,
	const BankAccount&
)
try
{
	throw logic_error{"Not supported."};
}
catch (const exception& error)
{
	this->removeBill(b.source());
	--(*this->accountCodeNumberGenerator_);
	throw BudgetItemError{
		b.source(),
		QObject::tr("Failure making the bill bank account association."),
		error
	};
}

void BudgetBankLedgers::addBudgetBankAssociation(
	const Debt& d,
	AccountCode& createdBudgetItem,
	const BankAccount& associate
)
try
{
	assert(
		associate.account_.code_.number().integer() == AccountNumber::invalid_
		&& "A debt should never have the bank account set directly when adding."
	);
	this->bankLedgers_.addAssociation(
		associate.bank_,
		createdBudgetItem.number(),
		associate.account_.type_
	);
}
catch (const exception& error)
{
	this->removeDebt(d.source());
	--(*this->accountCodeNumberGenerator_);
	throw BudgetItemError{
		d.source(),
		QObject::tr("Failure making the debt bank account association."),
		error
	};
}

void BudgetBankLedgers::addBudgetBankAssociation(
	const Goal& g,
	AccountCode& createdBudgetItem,
	const BankAccount& associate
)
try
{
	assert(
		associate.account_.code_.number().integer() != AccountNumber::invalid_
		&& "A goal should have the bank account set directly when adding."
	);
	this->bankLedgers_.addAssociation(
		associate.bank_,
		createdBudgetItem.number(),
		associate.account_.type_
	);
	auto parented{createdBudgetItem};
	parented.setParent(associate.account_.code_.number());
	this->ledgers_.recode(createdBudgetItem, parented);
	createdBudgetItem = parented;
}
catch (const exception& error)
{
	if (this->bankLedgers_.hasAssociation(associate.bank_, createdBudgetItem.number()))
		this->bankLedgers_.removeAssociation(associate.bank_, createdBudgetItem.number());
	this->removeGoal(g.source());
	--(*this->accountCodeNumberGenerator_);
	throw BudgetItemError{
		g.source(),
		QObject::tr("Failure making the goal bank account association."),
		error
	};
}

void BudgetBankLedgers::addBudgetBankAssociation(
	const Nontrack& n,
	AccountCode&,
	const BankAccount&
)
try
{
	throw logic_error{"Not supported."};
}
catch (const exception& error)
{
	this->removeNontrack(n.source());
	--(*this->accountCodeNumberGenerator_);
	throw BudgetItemError{
		n.source(),
		QObject::tr("Failure making the nontrack bank account association."),
		error
	};
}

void BudgetBankLedgers::addBudgetBankAssociation(
	const Wage& w,
	AccountCode&,
	const BankAccount&
)
try
{
	throw logic_error{"Not supported."};
}
catch (const exception& error)
{
	this->removeWage(w.source());
	--(*this->accountCodeNumberGenerator_);
	throw BudgetItemError{
		w.source(),
		QObject::tr("Failure making the wage bank account association."),
		error
	};
}

void BudgetBankLedgers::updateBudgetBankAssociation(
	const Bill& preUpdate,
	const AccountCode&,
	const Optional<BankAccount>& associate
)
try
{
	if (associate.hasValue())
		throw logic_error{"Not supported."};
	else
	{
		const auto accountNumber{
			this->budgetLedgers_.budgetAccountCode<Bill>(preUpdate.source()).number()
		};
		const auto bank{this->bankLedgers_.findBankName(accountNumber)};

		if (bank.hasValue())
			throw logic_error{"Not supported."};
	}
}
catch (const exception& error)
{
	this->budgetLedgers_.update(preUpdate, *this->accountCodeNumberGenerator_);
	throw BudgetItemError{
		preUpdate.source(),
		QObject::tr("Failure updating the bill bank account association."),
		error
	};
}

void BudgetBankLedgers::updateBudgetBankAssociation(
	const Debt& preUpdate,
	const AccountCode& createdBudgetItem,
	const Optional<BankAccount>& associate
)
try
{
	const auto accountNumber{createdBudgetItem.number()};

	if (associate.hasValue())
	{
		assert(
			associate->account_.code_.number().integer() == AccountNumber::invalid_
			&& "A debt should never have the bank account set directly when updating."
		);
		this->bankLedgers_.addAssociation(
			associate->bank_,
			accountNumber,
			associate->account_.type_
		);
	}
	else
	{
		const auto bank{this->bankLedgers_.findBankName(accountNumber)};

		// Not all debts will be associated with a bank account, e.g. personal private loans.
		if (bank.hasValue())
			this->bankLedgers_.removeAssociation(*bank, accountNumber);
	}
}
catch (const exception& error)
{
	this->budgetLedgers_.update(preUpdate, *this->accountCodeNumberGenerator_);
	throw BudgetItemError{
		preUpdate.source(),
		QObject::tr("Failure updating the debt bank account association."),
		error
	};
}

void BudgetBankLedgers::updateBudgetBankAssociation(
	const Goal& preUpdate,
	const AccountCode& createdBudgetItem,
	const Optional<BankAccount>& associate
)
try
{
	if (associate.hasValue())
	{
		assert(
			associate->account_.code_.number().integer() != AccountNumber::invalid_
			&& "A goal should have the bank account set directly when updating."
		);
		this->bankLedgers_.addAssociation(
			associate->bank_,
			createdBudgetItem.number(),
			SupportedAccountTypes::Goal
		);
		auto parented{createdBudgetItem};
		parented.setParent(associate->account_.code_.number());
		this->ledgers_.recode(createdBudgetItem, parented);
	}
	else
	{
		const auto bank{this->bankLedgers_.findBankName(createdBudgetItem.number())};

		// Not all goals will be associated with a bank account, e.g. using a home safe, etc.
		if (bank.hasValue())
			this->bankLedgers_.removeAssociation(*bank, createdBudgetItem.number());
		auto parented{createdBudgetItem};
		parented.setParent({});
		this->ledgers_.recode(createdBudgetItem, parented);
	}
}
catch (const exception& error)
{
	if (
		associate.hasValue()
		&& this->bankLedgers_.hasAssociation(associate->bank_, createdBudgetItem.number())
	)
			this->bankLedgers_.removeAssociation(associate->bank_, createdBudgetItem.number());
	this->budgetLedgers_.update(preUpdate, *this->accountCodeNumberGenerator_);
	throw BudgetItemError{
		preUpdate.source(),
		QObject::tr("Failure updating the goal bank account association."),
		error
	};
}

void BudgetBankLedgers::updateBudgetBankAssociation(
	const Nontrack& preUpdate,
	const AccountCode&,
	const Optional<BankAccount>& associate
)
try
{
	if (associate.hasValue())
		throw logic_error{"Not supported."};
	else
	{
		const auto accountNumber{
			this->budgetLedgers_.budgetAccountCode<Nontrack>(preUpdate.source()).number()
		};
		const auto bank{this->bankLedgers_.findBankName(accountNumber)};

		if (bank.hasValue())
			throw logic_error{"Not supported."};
	}
}
catch (const exception& error)
{
	this->budgetLedgers_.update(preUpdate, *this->accountCodeNumberGenerator_);
	throw BudgetItemError{
		preUpdate.source(),
		QObject::tr("Failure updating the nontrack bank account association."),
		error
	};
}

void BudgetBankLedgers::updateBudgetBankAssociation(
	const Wage& preUpdate,
	const AccountCode&,
	const Optional<BankAccount>& associate
)
try
{
	if (associate.hasValue())
		throw logic_error{"Not supported."};
	else
	{
		const auto accountNumber{
			this->budgetLedgers_.budgetAccountCode<Wage>(preUpdate.source()).number()
		};
		const auto bank{this->bankLedgers_.findBankName(accountNumber)};

		if (bank.hasValue())
			throw logic_error{"Not supported."};
	}
}
catch (const exception& error)
{
	this->budgetLedgers_.update(preUpdate, *this->accountCodeNumberGenerator_);
	throw BudgetItemError{
		preUpdate.source(),
		QObject::tr("Failure updating the wage bank account association."),
		error
	};
}

bool drn::navigation::internal::operator==(
	const BudgetBankLedgers& lhs,
	const BudgetBankLedgers& rhs
)
{
	return lhs.banks() == rhs.banks()
		&& lhs.budget() == rhs.budget()
		&& lhs.ledgers() == rhs.ledgers()
		&& lhs.bankLedgers() == rhs.bankLedgers()
		&& lhs.budgetLedgers() == rhs.budgetLedgers();
}

bool drn::navigation::internal::operator!=(
	const BudgetBankLedgers& lhs,
	const BudgetBankLedgers& rhs
)
{
	return ! (lhs == rhs);
}

ostream& drn::navigation::internal::operator<<(ostream& o, const BudgetBankLedgers& bba)
{
	o << "Banks (" << bba.banks() << "), Budget (" << bba.budget()
		<< "), Ledgers (" << bba.ledgers()
		<< "), Budget Account Iso4217Codes (" << bba.budgetLedgers().budgetAccountCodes()
		<< "), Bank Account Types (";

	bool isFirstBank{true};
	for (const auto& bankNameAccountTypes : bba.bankLedgers().bankAccountTypes())
	{
		o << (isFirstBank ? isFirstBank = false, "" : ", ") << bankNameAccountTypes.first << " => ";

		bool isFirstType{true};
		for (const auto& numType : bankNameAccountTypes.second)
			o << (isFirstType ? isFirstType = false, "" : ", ") << numType.first << "=>"
					<< numType.second;
	}
	return o << ')';
}
