/*******************************************************************************
***  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 "BudgetLedgers.h"
using drn::accounting::AccountCode;
using drn::accounting::AccountNumber;
using drn::budgeting::BudgetSource;
using drn::foundation::ObserverPtr;
using drn::foundation::TypeIndexMap;
using drn::navigation::internal::BudgetLedgers;

#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 <stdexcept>
using std::logic_error;
#include <typeindex>
using std::type_index;
#include <utility>
using std::move;

#include <accounting/Account.h>
using drn::accounting::Account;
using drn::accounting::AccountTypes;
#include <accounting/AccountNumberGenerator.h>
using drn::accounting::AccountNumberGenerator;
#include <accounting/LedgerBooks.h>
using drn::accounting::GeneralLedger;
#include <accounting/Transaction.h>
using drn::accounting::Transaction;
using drn::accounting::TransactionNumber;
#include <budgeting/Bill.h>
using drn::budgeting::Bill;
#include <budgeting/Budget.h>
using drn::budgeting::Budget;
#include <budgeting/BudgetedMoney.h>
using drn::budgeting::BudgetedMoney;
#include <budgeting/BudgetItemDue.h>
using drn::budgeting::BudgetItemDue;
#include <budgeting/BudgetItemIdentifier.h>
using drn::budgeting::BudgetItemIdentifier;
#include <budgeting/BudgetItemTypes.h>
using drn::budgeting::BudgetItemTypes;
using drn::budgeting::toBudgetItemTypes;
#include <budgeting/BudgetingErrors.h>
using drn::budgeting::BudgetItemError;
#include <budgeting/Debt.h>
using drn::budgeting::Debt;
#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/Optional.hpp>
using drn::foundation::Optional;
#include <foundation/Error.h>
using drn::foundation::Error;


namespace
{

void verifyBudgetCodes(
	const Budget& budget,
	const GeneralLedger& ledgers,
	const TypeIndexMap<BudgetSource, AccountCode>& budgetCodes
)
{
	for (const auto& typeSourceCode : budgetCodes)
		for (const auto& sourceCode : typeSourceCode.second)
		{
			static const type_index billType{typeid(Bill)};
			static const type_index debtType{typeid(Debt)};
			static const type_index goalType{typeid(Goal)};
			static const type_index nontrackType{typeid(Nontrack)};
			static const type_index wageType{typeid(Wage)};

			if (billType == typeSourceCode.first)
			{
				if ( ! budget.bills_.has(sourceCode.first))
					throw Error{
						QObject::tr("The budget does not have a bill source named '%1'.")
							.arg(sourceCode.first.name())
					};
			}
			else if (debtType == typeSourceCode.first)
			{
				if ( ! budget.debts_.has(sourceCode.first))
					throw Error{
						QObject::tr("The budget does not have a debt source named '%1'.")
							.arg(sourceCode.first.name())
					};
			}
			else if (goalType == typeSourceCode.first)
			{
				if ( ! budget.goals_.has(sourceCode.first))
					throw Error{
						QObject::tr("The budget does not have a goal source named '%1'.")
							.arg(sourceCode.first.name())
					};
			}
			else if (nontrackType == typeSourceCode.first)
			{
				if ( ! budget.nontracks_.has(sourceCode.first))
					throw Error{
						QObject::tr("The budget does not have a non-tracked source named '%1'.")
							.arg(sourceCode.first.name())
					};
			}
			else if (wageType == typeSourceCode.first)
			{
				if ( ! budget.wages_.has(sourceCode.first))
					throw Error{
						QObject::tr("The budget does not have a wage source named '%1'.")
							.arg(sourceCode.first.name())
					};
			}
			else
				throw Error{QObject::tr("Unknown budget code type!")};

			if ( ! ledgers.hasLedger(sourceCode.second))
				throw Error{
					QObject::tr("The account ledger does not have an account code '%1'.")
						.arg(presentationText(sourceCode.second))
				};
		}
}

QString generateBudgetItemPostDescription(const BudgetSource& source, const QDate& dueOn)
{
	return QObject::tr("Processed the budget item '%1' that was due on %2.")
		.arg(source.name())
		.arg(dueOn.toString());
}

}

TransactionNumber BudgetLedgers::post(
	const AccountCode& debit,
	const AccountCode& credit,
	const Money& amount,
	const QDate& postedOn,
	const QString& description,
	BudgetedMoney& itemBudget
)
{
	Optional<TransactionNumber> txnNum{};

	try
	{
		const Transaction txn{postedOn, debit, credit, amount, description};
		txnNum = this->ledgers_->post(txn);
		itemBudget.updateNextOccurOn();
		return *txnNum;
	}
	catch (const exception&)
	{
		if (txnNum.hasValue())
			this->ledgers_->remove(*txnNum);
		throw;
	}
}

const QString BudgetLedgers::budgetItemAccountSuffix_{"[Budget Item]"};

BudgetLedgers::BudgetLedgers(
	ObserverPtr<Budget> budget,
	ObserverPtr<GeneralLedger> ledgers,
	TypeIndexMap<BudgetSource, AccountCode> budgetCodes
) :
	budget_{budget},
	ledgers_{ledgers},
	budgetCodes_{move(budgetCodes)}
{
	assert(budget != nullptr && "The budget cannot be null.");
	assert(ledgers != nullptr && "The general ledger cannot be null");
	verifyBudgetCodes(*this->budget_, *this->ledgers_, this->budgetCodes_);
}

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

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

const TypeIndexMap<BudgetSource, AccountCode>& BudgetLedgers::budgetAccountCodes() const
{
	return this->budgetCodes_;
}

AccountCode BudgetLedgers::add(Wage wage, AccountNumberGenerator& generator)
{
	auto source{wage.source()};
	AccountCode ac{++generator, source.name() + ' ' + BudgetLedgers::budgetItemAccountSuffix_};
	Account a{AccountTypes::Revenue, ac, Money{0, 0u, wage.amount().code()}, false};

	try
	{
		this->budget_->wages_.add(move(wage));
		this->ledgers_->add(move(a), generator);
		this->budgetCodes_.add<Wage>(move(source), ac);
	}
	catch (const exception&)
	{
		--generator;
		throw;
	}
	return ac;
}

AccountCode BudgetLedgers::add(Bill bill, AccountNumberGenerator& generator)
{
	auto source{bill.source()};
	AccountCode ac{++generator, source.name() + ' ' + BudgetLedgers::budgetItemAccountSuffix_};
	Account a{AccountTypes::Expense, ac, Money{0, 0u, bill.amount().code()}, false};

	try
	{
		this->budget_->bills_.add(move(bill));
		this->ledgers_->add(move(a), generator);
		this->budgetCodes_.add<Bill>(move(source), ac);
	}
	catch (const exception&)
	{
		--generator;
		throw;
	}
	return ac;
}

AccountCode BudgetLedgers::add(Debt debt, AccountNumberGenerator& generator)
{
	auto source{debt.source()};
	AccountCode ac{++generator, source.name() + ' ' + BudgetLedgers::budgetItemAccountSuffix_};
	Account a{AccountTypes::Liability, ac, debt.borrowed(), false};

	try
	{
		this->budget_->debts_.add(move(debt));
		this->ledgers_->add(move(a), generator);
		this->budgetCodes_.add<Debt>(move(source), ac);
	}
	catch (const exception&)
	{
		--generator;
		throw;
	}
	return ac;
}

AccountCode BudgetLedgers::add(Goal goal, AccountNumberGenerator& generator)
{
	auto source{goal.source()};
	AccountCode ac{++generator, source.name() + ' ' + BudgetLedgers::budgetItemAccountSuffix_};
	Account a{AccountTypes::Asset, ac, Money{0, 0u, goal.amount().code()}, false};

	try
	{
		this->budget_->goals_.add(move(goal));
		this->ledgers_->add(move(a), generator);
		this->budgetCodes_.add<Goal>(move(source), ac);
	}
	catch (const exception&)
	{
		--generator;
		throw;
	}
	return ac;
}

AccountCode BudgetLedgers::add(Nontrack nontrack, AccountNumberGenerator& generator)
{
	auto source{nontrack.source()};
	AccountCode ac{++generator, source.name() + ' ' + BudgetLedgers::budgetItemAccountSuffix_};
	Account a{AccountTypes::Expense, ac, Money{0, 0u, nontrack.amount().code()}, false};

	try
	{
		this->budget_->nontracks_.add(move(nontrack));
		this->ledgers_->add(move(a), generator);
		this->budgetCodes_.add<Nontrack>(move(source), ac);
	}
	catch (const exception&)
	{
		--generator;
		throw;
	}
	return ac;
}

void BudgetLedgers::update(Wage wage, AccountNumberGenerator& generator)
{
	const auto source{wage.source()};

	if ( ! this->budget_->wages_.has(source))
		throw BudgetItemError{source, QObject::tr("Cannot update a wage that does not exist.")};
	const auto currentCode{this->budget_->wages_.at(source).amount().code()};
	const auto updatedCode{wage.amount().code()};
	const auto backUp{this->budget_->wages_.at(source)};
	this->budget_->wages_.update(move(wage));

	if (currentCode != updatedCode)
		try
		{
			this->ledgers_->convertTo(
				this->budgetCodes_.value<Wage>(source),
				updatedCode,
				generator
			);
		}
		catch (const exception&)
		{
			this->budget_->wages_.update(backUp);
			throw;
		}
}

void BudgetLedgers::update(Bill bill, AccountNumberGenerator& generator)
{
	const auto source{bill.source()};

	if ( ! this->budget_->bills_.has(source))
		throw BudgetItemError{source, QObject::tr("Cannot update a bill that does not exist.")};
	const auto currentCode{this->budget_->bills_.at(source).amount().code()};
	const auto updatedCode{bill.amount().code()};
	const auto backUp{this->budget_->bills_.at(source)};
	this->budget_->bills_.update(move(bill));

	if (currentCode != updatedCode)
		try
		{
			this->ledgers_->convertTo(
				this->budgetCodes_.value<Bill>(source),
				updatedCode,
				generator
			);
		}
		catch (const exception&)
		{
			this->budget_->bills_.update(backUp);
			throw;
		}
}

void BudgetLedgers::update(Debt debt, AccountNumberGenerator& generator)
{
	const auto source{debt.source()};

	if ( ! this->budget_->debts_.has(source))
		throw BudgetItemError{source, QObject::tr("Cannot update a debt that does not exist.")};
	const auto currentCode{this->budget_->debts_.at(source).amount().code()};
	const auto updatedCode{debt.amount().code()};
	const auto backUp{this->budget_->debts_.at(source)};
	this->budget_->debts_.update(move(debt));

	if (currentCode != updatedCode)
		try
		{
			this->ledgers_->convertTo(
				this->budgetCodes_.value<Debt>(source),
				updatedCode,
				generator
			);
		}
		catch (const exception&)
		{
			this->budget_->debts_.update(backUp);
			throw;
		}
}

void BudgetLedgers::update(Goal goal, AccountNumberGenerator& generator)
{
	const auto source{goal.source()};

	if ( ! this->budget_->goals_.has(source))
		throw BudgetItemError{source, QObject::tr("Cannot update a goal that does not exist.")};
	const auto currentCode{this->budget_->goals_.at(source).amount().code()};
	const auto updatedCode{goal.amount().code()};
	const auto backUp{this->budget_->goals_.at(source)};
	this->budget_->goals_.update(move(goal));

	if (currentCode != updatedCode)
		try
		{
			this->ledgers_->convertTo(
				this->budgetCodes_.value<Goal>(source),
				updatedCode,
				generator
			);
		}
		catch (const exception&)
		{
			this->budget_->goals_.update(backUp);
			throw;
		}
}

void BudgetLedgers::update(Nontrack nontrack, AccountNumberGenerator& generator)
{
	const auto source{nontrack.source()};

	if ( ! this->budget_->nontracks_.has(source))
		throw BudgetItemError{
			source,
			QObject::tr("Cannot update a nontrack that does not exist.")
		};
	const auto currentCode{this->budget_->nontracks_.at(source).amount().code()};
	const auto updatedCode{nontrack.amount().code()};
	const auto backUp{this->budget_->nontracks_.at(source)};
	this->budget_->nontracks_.update(move(nontrack));

	if (currentCode != updatedCode)
		try
		{
			this->ledgers_->convertTo(
				this->budgetCodes_.value<Nontrack>(source),
				updatedCode,
				generator
			);
		}
		catch (const exception&)
		{
			this->budget_->nontracks_.update(backUp);
			throw;
		}
}

void BudgetLedgers::removeWage(const BudgetSource& source)
{
	if ( ! this->budget_->wages_.has(source))
		throw BudgetItemError{source, QObject::tr("Cannot remove a wage that does not exist.")};
	const auto backUp{this->budget_->wages_.at(source)};
	this->budget_->wages_.remove(source);
	const auto code{this->budgetCodes_.value<Wage>(source)};

	try
	{
		if (this->ledgers_->canRemove(code))
			this->ledgers_->remove(code);
		else
			this->ledgers_->close(code);
	}
	catch (const exception&)
	{
		this->budget_->wages_.add(backUp);
		throw;
	}
	this->budgetCodes_.remove<Wage>(source);
}

void BudgetLedgers::removeBill(const BudgetSource& source)
{
	if ( ! this->budget_->bills_.has(source))
		throw BudgetItemError{source, QObject::tr("Cannot remove a bill that does not exist.")};
	const auto backUp{this->budget_->bills_.at(source)};
	this->budget_->bills_.remove(source);
	const auto code{this->budgetCodes_.value<Bill>(source)};

	try
	{
		if (this->ledgers_->canRemove(code))
			this->ledgers_->remove(code);
		else
			this->ledgers_->close(code);
	}
	catch (const exception&)
	{
		this->budget_->bills_.add(backUp);
		throw;
	}
	this->budgetCodes_.remove<Bill>(source);
}

void BudgetLedgers::removeDebt(const BudgetSource& source)
{
	if ( ! this->budget_->debts_.has(source))
		throw BudgetItemError{source, QObject::tr("Cannot remove a debt that does not exist.")};
	const auto backUp{this->budget_->debts_.at(source)};
	this->budget_->debts_.remove(source);
	const auto code{this->budgetCodes_.value<Debt>(source)};

	try
	{
		if (this->ledgers_->canRemove(code))
			this->ledgers_->remove(code);
		else
			this->ledgers_->close(code);
	}
	catch (const exception&)
	{
		this->budget_->debts_.add(backUp);
		throw;
	}
	this->budgetCodes_.remove<Debt>(source);
}

void BudgetLedgers::removeGoal(const BudgetSource& source)
{
	if ( ! this->budget_->goals_.has(source))
		throw BudgetItemError{source, QObject::tr("Cannot remove a goal that does not exist.")};
	const auto backUp{this->budget_->goals_.at(source)};
	this->budget_->goals_.remove(source);
	const auto code{this->budgetCodes_.value<Goal>(source)};

	try
	{
		if (this->ledgers_->canRemove(code))
			this->ledgers_->remove(code);
		else
			this->ledgers_->close(code);
	}
	catch (const exception&)
	{
		this->budget_->goals_.add(backUp);
		throw;
	}
	this->budgetCodes_.remove<Goal>(source);
}

void BudgetLedgers::removeNontrack(const BudgetSource& source)
{
	if ( ! this->budget_->nontracks_.has(source))
		throw BudgetItemError{
			source,
			QObject::tr("Cannot remove a nontrack that does not exist.")
		};
	const auto backUp{this->budget_->nontracks_.at(source)};
	this->budget_->nontracks_.remove(source);
	const auto code{this->budgetCodes_.value<Nontrack>(source)};

	try
	{
		if (this->ledgers_->canRemove(code))
			this->ledgers_->remove(code);
		else
			this->ledgers_->close(code);
	}
	catch (const exception&)
	{
		this->budget_->nontracks_.add(backUp);
		throw;
	}
	this->budgetCodes_.remove<Nontrack>(source);
}

void BudgetLedgers::renameWage(
	const BudgetSource& original,
	const BudgetSource& changed,
	const Optional<AccountNumber>& changedNumber
)
{
	this->budget_->wages_.rename(original, changed);
	const auto currentCode{this->budgetCodes_.value<Wage>(original)};
	const AccountCode changedCode{
		changedNumber.hasValue() ? *changedNumber : currentCode.number(),
		changed.name() + ' ' + BudgetLedgers::budgetItemAccountSuffix_
	};
	this->ledgers_->recode(currentCode, changedCode);
	this->budgetCodes_.remove<Wage>(original);
	this->budgetCodes_.add<Wage>(changed, changedCode);
}

void BudgetLedgers::renameBill(
	const BudgetSource& original,
	const BudgetSource& changed,
	const Optional<AccountNumber>& changedNumber
)
{
	this->budget_->bills_.rename(original, changed);
	const auto currentCode{this->budgetCodes_.value<Bill>(original)};
	const AccountCode changedCode{
		changedNumber.hasValue() ? *changedNumber : currentCode.number(),
		changed.name() + ' ' + BudgetLedgers::budgetItemAccountSuffix_
	};
	this->ledgers_->recode(currentCode, changedCode);
	this->budgetCodes_.remove<Bill>(original);
	this->budgetCodes_.add<Bill>(changed, changedCode);
}

void BudgetLedgers::renameDebt(
	const BudgetSource& original,
	const BudgetSource& changed,
	const Optional<AccountNumber>& changedNumber
)
{
	this->budget_->debts_.rename(original, changed);
	const auto currentCode{this->budgetCodes_.value<Debt>(original)};
	const AccountCode changedCode{
		changedNumber.hasValue() ? *changedNumber : currentCode.number(),
		changed.name() + ' ' + BudgetLedgers::budgetItemAccountSuffix_
	};
	this->ledgers_->recode(currentCode, changedCode);
	this->budgetCodes_.remove<Debt>(original);
	this->budgetCodes_.add<Debt>(changed, changedCode);
}

void BudgetLedgers::renameGoal(
	const BudgetSource& original,
	const BudgetSource& changed,
	const Optional<AccountNumber>& changedNumber
)
{
	this->budget_->goals_.rename(original, changed);
	const auto currentCode{this->budgetCodes_.value<Goal>(original)};
	const AccountCode changedCode{
		changedNumber.hasValue() ? *changedNumber : currentCode.number(),
		changed.name() + ' ' + BudgetLedgers::budgetItemAccountSuffix_
	};
	this->ledgers_->recode(currentCode, changedCode);
	this->budgetCodes_.remove<Goal>(original);
	this->budgetCodes_.add<Goal>(changed, changedCode);
}

void BudgetLedgers::renameNontrack(
	const BudgetSource& original,
	const BudgetSource& changed,
	const Optional<AccountNumber>& changedNumber
)
{
	this->budget_->nontracks_.rename(original, changed);
	const auto currentCode{this->budgetCodes_.value<Nontrack>(original)};
	const AccountCode changedCode{
		changedNumber.hasValue() ? *changedNumber : currentCode.number(),
		changed.name() + ' ' + BudgetLedgers::budgetItemAccountSuffix_
	};
	this->ledgers_->recode(currentCode, changedCode);
	this->budgetCodes_.remove<Nontrack>(original);
	this->budgetCodes_.add<Nontrack>(changed, changedCode);
}

const AccountCode& BudgetLedgers::lookUpAccountCode(const BudgetItemIdentifier& id) const
{
	switch (id.type_)
	{
	case BudgetItemTypes::Bill:
		return this->budgetAccountCode<Bill>(id.source_);
	case BudgetItemTypes::Debt:
		return this->budgetAccountCode<Debt>(id.source_);
	case BudgetItemTypes::Goal:
		return this->budgetAccountCode<Goal>(id.source_);
	case BudgetItemTypes::Nontrack:
		return this->budgetAccountCode<Nontrack>(id.source_);
	case BudgetItemTypes::Wage:
		return this->budgetAccountCode<Wage>(id.source_);
	case BudgetItemTypes::Unknown:
	default:
		throw Error{
			QObject::tr("The budget item '%1' is not associated with an account!")
					.arg(presentationText(id))
		};
	};
}

BudgetItemIdentifier BudgetLedgers::lookUpBudgetId(const AccountNumber& an) const // TODO: unit tests
{
	Optional<BudgetItemIdentifier> id{};

	for (const auto& budgetTypeSourceAccount : this->budgetCodes_)
	{
		for (const auto& sourceAccount : budgetTypeSourceAccount.second)
		{
			if (sourceAccount.second == an)
			{
				id = {
					toBudgetItemTypes(budgetTypeSourceAccount.first),
					sourceAccount.first
				};
				break;
			}
		}

		if (id.hasValue())
			break;
	}

	if ( ! id.hasValue())
		throw Error{
			QObject::tr("The account '%1' is not associated with a budget item!").arg(an.integer())
		};
	return *id;
}

void BudgetLedgers::clearCodes()
{
	this->budgetCodes_.clear();
}

bool BudgetLedgers::isEmpty() const
{
	return this->budgetCodes_.empty();
}

TransactionNumber BudgetLedgers::postBill(
	const BudgetSource& budgetItem,
	const AccountCode& fundSource,
	const Money& amount,
	const QDate& postedOn
)
{
	const auto itemAccount{this->budgetAccountCode<Bill>(budgetItem)};
	auto& itemBudget{this->budget_->bills_.at(budgetItem)};
	return this->post(
		itemAccount,
		fundSource,
		amount,
		postedOn,
		generateBudgetItemPostDescription(budgetItem, itemBudget.nextOccurOn()),
		itemBudget
	);
}

TransactionNumber BudgetLedgers::postDebt(
	const BudgetSource& budgetItem,
	const AccountCode& fundSource,
	const Money& amount,
	const QDate& postedOn
)
{
	const auto itemAccount{this->budgetAccountCode<Debt>(budgetItem)};
	auto& itemBudget{this->budget_->debts_.at(budgetItem)};
	return this->post(
		itemAccount,
		fundSource,
		amount,
		postedOn,
		generateBudgetItemPostDescription(budgetItem, itemBudget.nextOccurOn()),
		itemBudget
	);
}

TransactionNumber BudgetLedgers::postGoal(
	const BudgetSource& budgetItem,
	const AccountCode& fundSource,
	const Money& amount,
	const QDate& postedOn
)
{
	const auto itemAccount{this->budgetAccountCode<Goal>(budgetItem)};
	auto& itemBudget{this->budget_->goals_.at(budgetItem)};
	return this->post(
		itemAccount,
		fundSource,
		amount,
		postedOn,
		generateBudgetItemPostDescription(budgetItem, itemBudget.nextOccurOn()),
		itemBudget
	);
}

TransactionNumber BudgetLedgers::postNontrack(
	const BudgetSource& budgetItem,
	const AccountCode& fundSource,
	const Money& amount,
	const QDate& postedOn
)
{
	const auto itemAccount{this->budgetAccountCode<Nontrack>(budgetItem)};
	auto& itemBudget{this->budget_->nontracks_.at(budgetItem)};
	return this->post(
		itemAccount,
		fundSource,
		amount,
		postedOn,
		generateBudgetItemPostDescription(budgetItem, itemBudget.nextOccurOn()),
		itemBudget
	);
}

TransactionNumber BudgetLedgers::postWage(
	const BudgetSource& budgetItem,
	const AccountCode& fundTarget,
	const Money& amount,
	const QDate& postedOn
)
{
	const auto itemAccount{this->budgetAccountCode<Wage>(budgetItem)};
	auto& itemBudget{this->budget_->wages_.at(budgetItem)};
	return this->post(
		fundTarget,
		itemAccount,
		amount,
		postedOn,
		generateBudgetItemPostDescription(budgetItem, itemBudget.nextOccurOn()),
		itemBudget
	);
}

TransactionNumber BudgetLedgers::postUnexpected(
	const BudgetItemIdentifier& bii,
	const Money& amount,
	const AccountCode& ac,
	const QDate& postOn
)
{
	const auto itemAccount{
		[&bii, this] ()
		{
			const auto& budgetAccountCodes{this->budgetAccountCodes()};

			switch(bii.type_)
			{
			case BudgetItemTypes::Bill:
				if (budgetAccountCodes.has<Bill>(bii.source_))
					return this->budgetAccountCode<Bill>(bii.source_);
				break;
			case BudgetItemTypes::Debt:
				if (budgetAccountCodes.has<Debt>(bii.source_))
					return this->budgetAccountCode<Debt>(bii.source_);
				break;
			case BudgetItemTypes::Goal:
				if (budgetAccountCodes.has<Goal>(bii.source_))
					return this->budgetAccountCode<Goal>(bii.source_);
				break;
			case BudgetItemTypes::Nontrack:
				if (budgetAccountCodes.has<Nontrack>(bii.source_))
					return this->budgetAccountCode<Nontrack>(bii.source_);
				break;
			case BudgetItemTypes::Wage:
				if (budgetAccountCodes.has<Wage>(bii.source_))
					return this->budgetAccountCode<Wage>(bii.source_);
				break;
			case BudgetItemTypes::Unknown:
				throw BudgetItemError{
					bii.source_,
					QObject::tr("Cannot post unexpected budget item that has an unknown type.")
				};
				break;
			default:
				throw logic_error{
					"The Budget Item Type is not implemented for posting unexpected budget items."
				};
			};
			throw BudgetItemError{
				bii.source_,
				QObject::tr("Failed to locate the unexpected budget item source to post.")
			};
		}()
	};
	const Transaction txn{
		postOn,
		bii.type_ == BudgetItemTypes::Wage ? ac : itemAccount,
		bii.type_ == BudgetItemTypes::Wage ? itemAccount : ac,
		amount,
		QObject::tr("Processed unexpected budget item '%1'.").arg(bii.source_.name())
	};
	return this->ledgers_->post(txn);
}

void BudgetLedgers::skipBill(const BudgetSource& budgetItem)
{
	this->budget_->bills_.at(budgetItem).updateNextOccurOn();
}

void BudgetLedgers::skipDebt(const BudgetSource& budgetItem)
{
	this->budget_->debts_.at(budgetItem).updateNextOccurOn();
}

void BudgetLedgers::skipGoal(const BudgetSource& budgetItem)
{
	this->budget_->goals_.at(budgetItem).updateNextOccurOn();
}

void BudgetLedgers::skipNontrack(const BudgetSource& budgetItem)
{
	this->budget_->nontracks_.at(budgetItem).updateNextOccurOn();
}

void BudgetLedgers::skipWage(const BudgetSource& budgetItem)
{
	this->budget_->wages_.at(budgetItem).updateNextOccurOn();
}

bool drn::navigation::internal::operator==(
	const BudgetLedgers& lhs,
	const BudgetLedgers& rhs
)
{
	return lhs.budget() == rhs.budget() && lhs.ledgers() == rhs.ledgers()
		&& lhs.budgetAccountCodes() == rhs.budgetAccountCodes();
}

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

ostream& drn::navigation::internal::operator<<(ostream& o, const BudgetLedgers& bia)
{
	return o << "Budget (" << bia.budget() << "), General Ledger (" << bia.ledgers()
		<< "), Budget-Account ISO-4217 Codes (" << bia.budgetAccountCodes() << ')';
}
