/*******************************************************************************
***  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/>.    ***
*******************************************************************************/
#ifndef DRN_NAVIGATION_INTERNAL_BUDGETLEDGERS_H_
#define DRN_NAVIGATION_INTERNAL_BUDGETLEDGERS_H_

#include <iosfwd>

#include <accounting/AccountCode.h>
#include <budgeting/BudgetSource.h>
#include <foundation/ObserverPtr.hpp>
#include <foundation/TypeIndexMap.hpp>


class QDate;
class QString;

namespace pecunia
{
namespace currency
{

class Money;

}}

namespace drn
{
namespace accounting
{

class AccountNumber;
class AccountNumberGenerator;
class GeneralLedger;
class TransactionNumber;

}
namespace budgeting
{

struct Bill;
class Budget;
class BudgetedMoney;
struct BudgetItemDue;
struct BudgetItemIdentifier;
class Debt;
class Goal;
struct Nontrack;
struct Wage;

}
namespace foundation
{

template<typename>
class Optional;

}
namespace navigation
{
namespace internal
{

/**
 * @brief A manager for the relationship between the general ledger and the budget items. The
 * mapping between the budget items and the general ledger is via the name of the budget item.
 */
class BudgetLedgers
{
	accounting::TransactionNumber post(
		const accounting::AccountCode& debit,
		const accounting::AccountCode& credit,
		const pecunia::currency::Money& amount,
		const ::QDate& postedOn,
		const ::QString& description,
		budgeting::BudgetedMoney& itemBudget
	);

	foundation::ObserverPtr<budgeting::Budget> budget_;
	foundation::ObserverPtr<accounting::GeneralLedger> ledgers_;
	foundation::TypeIndexMap<budgeting::BudgetSource, accounting::AccountCode> budgetCodes_;

public:
	/// The suffix to place in the account code name for each budget item ledger account.
	static const ::QString budgetItemAccountSuffix_;

	/**
	 * @brief Fully constructs the manager object.
	 *
	 * @exception Error When the budgetCodes contains a budget source that is not in the budget.
	 * @exception Error When the budgetCodes contains an account code that is not in the ledgers.
	 *
	 * @param budget The budget to operate upon.
	 * @param ledgers The general ledger to operate upon.
	 * @param budgetCodes The mapping between the a budget item source and its associated general
	 * ledger account.
	 */
	BudgetLedgers(
		foundation::ObserverPtr<budgeting::Budget> budget,
		foundation::ObserverPtr<accounting::GeneralLedger> ledgers,
		foundation::TypeIndexMap<budgeting::BudgetSource, accounting::AccountCode> budgetCodes
	);
	/**
	 * @brief The budget being managed.
	 */
	const budgeting::Budget& budget() const;
	/**
	 * @brief The general ledger being managed.
	 */
	const accounting::GeneralLedger& ledgers() const;
	/**
	 * @brief The mapping between budget items, their source, and the account code associated with
	 * it.
	 */
	const foundation::TypeIndexMap<
		budgeting::BudgetSource, accounting::AccountCode
	>& budgetAccountCodes() const;
	/**
	 * @brief Adds a new wage to the budget and the co-responding account in the general ledger.
	 *
	 * @exception BudgetItemError When the budget item cannot be created.
	 * @exception AccountError when the account cannot be created.
	 *
	 * @param wage The wage budget item to be entered.
	 * @param generator The generator for automatically creating new accounts.
	 *
	 * @return The account code for the generated budgeted account.
	 */
	accounting::AccountCode add(
		budgeting::Wage wage,
		accounting::AccountNumberGenerator& generator
	);
	/**
	 * @brief Adds a new bill to the budget and the co-responding account in the general ledger.
	 *
	 * @exception BudgetItemError When the budget item cannot be created.
	 * @exception AccountError when the account cannot be created.
	 *
	 * @param bill The bill budget item to be entered.
	 * @param generator The generator for automatically creating new accounts.
	 *
	 * @return The account code for the generated budgeted account.
	 */
	accounting::AccountCode add(
		budgeting::Bill bill,
		accounting::AccountNumberGenerator& generator
	);
	/**
	 * @brief Adds a new debt to the budget and the co-responding account in the general ledger.
	 *
	 * @exception BudgetItemError When the budget item cannot be created.
	 * @exception AccountError when the account cannot be created.
	 *
	 * @param debt The debt budget item to be entered.
	 * @param generator The generator for automatically creating new accounts.
	 *
	 * @return The account code for the generated budgeted account.
	 */
	accounting::AccountCode add(
		budgeting::Debt debt,
		accounting::AccountNumberGenerator& generator
	);
	/**
	 * @brief Adds a new goal to the budget and the co-responding account in the general ledger.
	 *
	 * @exception BudgetItemError When the budget item cannot be created.
	 * @exception AccountError when the account cannot be created.
	 *
	 * @param goal The goal budget item to be entered.
	 * @param generator The generator for automatically creating new accounts.
	 *
	 * @return The account code for the generated budgeted account.
	 */
	accounting::AccountCode add(
		budgeting::Goal goal,
		accounting::AccountNumberGenerator& generator
	);
	/**
	 * @brief Adds the nontrack spending in the budget and the co-responding account in the
	 * general ledger.
	 *
	 * @exception BudgetItemError When the budget item cannot be created.
	 * @exception AccountError when the account cannot be created.
	 *
	 * @param nontrack The nontrack budget item to be added.
	 * @param generator The generator for automatically creating new accounts.
	 *
	 * @return The account code for the generated budgeted account.
	 */
	accounting::AccountCode add(
		budgeting::Nontrack nontrack,
		accounting::AccountNumberGenerator& generator
	);
	/**
	 * @brief Updates the wage item in the budget and the co-responding account in the general
	 * ledger.
	 *
	 * @exception BudgetItemError When the budget item cannot be updated.
	 * @exception AccountError When the account associated with the budget item cannot be updated.
	 *
	 * @param wage The updated values for the wage item.
	 * @param generator The generator for automatically creating new accounts.
	 */
	void update(budgeting::Wage wage, accounting::AccountNumberGenerator& generator);
	/**
	 * @brief Updates the bill item in the budget and the co-responding account in the general
	 * ledger.
	 *
	 * @exception BudgetItemError When the budget item cannot be updated.
	 * @exception AccountError When the account associated with the budget item cannot be updated.
	 *
	 * @param bill The updated values for the bill item.
	 * @param generator The generator for automatically creating new accounts.
	 */
	void update(budgeting::Bill bill, accounting::AccountNumberGenerator& generator);
	/**
	 * @brief Updates the debt item in the budget and the co-responding account in the general
	 * ledger.
	 *
	 * @exception BudgetItemError When the budget item cannot be updated.
	 * @exception AccountError When the account associated with the budget item cannot be updated.
	 *
	 * @param debt The updated values for the debt item.
	 * @param generator The generator for automatically creating new accounts.
	 */
	void update(budgeting::Debt debt, accounting::AccountNumberGenerator& generator);
	/**
	 * @brief Updates the goal item in the budget and the co-responding account in the general
	 * ledger.
	 *
	 * @exception BudgetItemError When the budget item cannot be updated.
	 * @exception AccountError When the account associated with the budget item cannot be updated.
	 *
	 * @param goal The updated values for the goal item.
	 * @param generator The generator for automatically creating new accounts.
	 */
	void update(budgeting::Goal goal, accounting::AccountNumberGenerator& generator);
	/**
	 * @brief Updates the nontrack item in the budget and the co-responding account in the general
	 * ledger.
	 *
	 * @exception BudgetItemError When the budget item cannot be updated.
	 * @exception AccountError When the account associated with the budget item cannot be updated.
	 *
	 * @param nontrack The updated values for the nontrack item.
	 * @param generator The generator for automatically creating new accounts.
	 */
	void update(
		budgeting::Nontrack nontrack,
		accounting::AccountNumberGenerator& generator
	);
	/**
	 * @brief Removes the budget item from the budget and the co-responding account in the general
	 * ledger.
	 *
	 * @exception BudgetItemError When the budget item cannot be removed.
	 * @exception AccountError When the account associated with the budget item cannot be removed.
	 *
	 * @param source The budget item to be removed.
	 */
	void removeWage(const budgeting::BudgetSource& source);
	/**
	 * @brief Removes the budget item from the budget and the co-responding account in the general
	 * ledger.
	 *
	 * @exception BudgetItemError When the budget item cannot be removed.
	 * @exception AccountError When the account associated with the budget item cannot be removed.
	 *
	 * @param source The budget item to be removed.
	 */
	void removeBill(const budgeting::BudgetSource& source);
	/**
	 * @brief Removes the budget item from the budget and the co-responding account in the general
	 * ledger.
	 *
	 * @exception BudgetItemError When the budget item cannot be removed.
	 * @exception AccountError When the account associated with the budget item cannot be removed.
	 *
	 * @param source The budget item to be removed.
	 */
	void removeDebt(const budgeting::BudgetSource& source);
	/**
	 * @brief Removes the budget item from the budget and the co-responding account in the general
	 * ledger.
	 *
	 * @exception BudgetItemError When the budget item cannot be removed.
	 * @exception AccountError When the account associated with the budget item cannot be removed.
	 *
	 * @param source The budget item to be removed.
	 */
	void removeGoal(const budgeting::BudgetSource& source);
	/**
	 * @brief Removes the budget item from the budget and the co-responding account in the general
	 * ledger.
	 *
	 * @exception BudgetItemError When the budget item cannot be removed.
	 * @exception AccountError When the account associated with the budget item cannot be removed.
	 *
	 * @param source The budget item to be removed.
	 */
	void removeNontrack(const budgeting::BudgetSource& source);
	/**
	 * @brief Changes the source name of a budget item and the co-responding account in the general
	 * ledger.
	 *
	 * @exception BudgetItemError When the budget item cannot be renamed.
	 *
	 * @param original The current source name of the budget item.
	 * @param changed The new source name for the budget item.
	 */
	void renameWage(
		const budgeting::BudgetSource& original,
		const budgeting::BudgetSource& changed,
		const foundation::Optional<accounting::AccountNumber>& changedNumber
	);
	/**
	 * @brief Changes the source name of a budget item and the co-responding account in the general
	 * ledger.
	 *
	 * @exception BudgetItemError When the budget item cannot be renamed.
	 *
	 * @param original The current source name of the budget item.
	 * @param changed The new source name for the budget item.
	 * @param changedNumber (Optional) When supplied uses the number as the changed account number.
	 */
	void renameBill(
		const budgeting::BudgetSource& original,
		const budgeting::BudgetSource& changed,
		const foundation::Optional<accounting::AccountNumber>& changedNumber
	);
	/**
	 * @brief Changes the source name of a budget item and the co-responding account in the general
	 * ledger.
	 *
	 * @exception BudgetItemError When the budget item cannot be renamed.
	 *
	 * @param original The current source name of the budget item.
	 * @param changed The new source name for the budget item.
	 * @param changedNumber (Optional) When supplied uses the number as the changed account number.
	 */
	void renameDebt(
		const budgeting::BudgetSource& original,
		const budgeting::BudgetSource& changed,
		const foundation::Optional<accounting::AccountNumber>& changedNumber
	);
	/**
	 * @brief Changes the source name of a budget item and the co-responding account in the general
	 * ledger.
	 *
	 * @exception BudgetItemError When the budget item cannot be renamed.
	 *
	 * @param original The current source name of the budget item.
	 * @param changed The new source name for the budget item.
	 * @param changedNumber (Optional) When supplied uses the number as the changed account number.
	 */
	void renameGoal(
		const budgeting::BudgetSource& original,
		const budgeting::BudgetSource& changed,
		const foundation::Optional<accounting::AccountNumber>& changedNumber
	);
	/**
	 * @brief Changes the source name of a budget item and the co-responding account in the general
	 * ledger.
	 *
	 * @exception BudgetItemError When the budget item cannot be renamed.
	 *
	 * @param original The current source name of the budget item.
	 * @param changed The new source name for the budget item.
	 * @param changedNumber (Optional) When supplied uses the number as the changed account number.
	 */
	void renameNontrack(
		const budgeting::BudgetSource& original,
		const budgeting::BudgetSource& changed,
		const foundation::Optional<accounting::AccountNumber>& changedNumber
	);
	/**
	 * @brief An accessor to the account code of a budget item.
	 *
	 * @exception Error When the budget item does not exist.
	 * @exception Error When the budget item source does not exist.
	 *
	 * @tparam BudgetItemType The type of budget item whose account code is desired.
	 *
	 * @param source The source of the budget item.
	 *
	 * @return The account code of the budget item of that budget source.
	 */
	template<typename BudgetItemType>
	const accounting::AccountCode& budgetAccountCode(const budgeting::BudgetSource& source) const;
	const accounting::AccountCode& lookUpAccountCode(
		const budgeting::BudgetItemIdentifier& id
	) const; // TODO: unit test
	/**
	 * @brief Finds the budget item identification using the ledger account associated with it.
	 *
	 * @exception Error When the budget item identification does not exist for an account.
	 *
	 * @param an The ledger account associated with the budget item.
	 *
	 * @return The found budget item identification.
	 */
	budgeting::BudgetItemIdentifier lookUpBudgetId(const accounting::AccountNumber& an) const;
	/**
	 * @brief Clears all mappings between the budget item type-budget source, and its account code.
	 */
	void clearCodes();
	/**
	 * @brief Determines if the container of budget item accounts is empty.
	 *
	 * @return Gives true when there are not any budgets associated with an account.
	 */
	bool isEmpty() const;
	/**
	 * @brief Posts a bill budget item transaction to the general ledger, and moving it to the next
	 * time it's due.
	 *
	 * @exception TransactionError When the transaction fails to be posted.
	 * @exception BudgetItemError When the budget item is fails to be updated.
	 *
	 * @param budgetItem The budget item to post in the ledger books.
	 * @param fundSource The source where the funds are used for the budget item.
	 * @param amount The amount to post in the transaction.
	 * @param postedOn The date the transaction is to be posted.
	 *
	 * @return The transaction identification number when successfully posted.
	 */
	accounting::TransactionNumber postBill(
		const budgeting::BudgetSource& budgetItem,
		const accounting::AccountCode& fundSource,
		const pecunia::currency::Money& amount,
		const ::QDate& postedOn
	);
	/**
	 * @brief Posts a debt budget item transaction to the general ledger, and moving it to the next
	 * time it's due.
	 *
	 * @exception TransactionError When the transaction fails to be posted.
	 * @exception BudgetItemError When the budget item is fails to be updated.
	 *
	 * @param budgetItem The budget item to post in the ledger books.
	 * @param fundSource The source where the funds are used for the budget item.
	 * @param amount The amount to post in the transaction.
	 * @param postedOn The date the transaction is to be posted.
	 *
	 * @return The transaction identification number when successfully posted.
	 */
	accounting::TransactionNumber postDebt(
		const budgeting::BudgetSource& budgetItem,
		const accounting::AccountCode& fundSource,
		const pecunia::currency::Money& amount,
		const ::QDate& postedOn
	);
	/**
	 * @brief Posts a goal budget item transaction to the general ledger, and moving it to the next
	 * time it's due.
	 *
	 * @exception TransactionError When the transaction fails to be posted.
	 * @exception BudgetItemError When the budget item is fails to be updated.
	 *
	 * @param budgetItem The budget item to post in the ledger books.
	 * @param fundSource The source where the funds are used for the budget item.
	 * @param amount The amount to post in the transaction.
	 * @param postedOn The date the transaction is to be posted.
	 *
	 * @return The transaction identification number when successfully posted.
	 */
	accounting::TransactionNumber postGoal(
		const budgeting::BudgetSource& budgetItem,
		const accounting::AccountCode& fundSource,
		const pecunia::currency::Money& amount,
		const ::QDate& postedOn
	);
	/**
	 * @brief Posts a nontrack budget item transaction to the general ledger, and moving it to the
	 * next time it's due.
	 *
	 * @exception TransactionError When the transaction fails to be posted.
	 * @exception BudgetItemError When the budget item is fails to be updated.
	 *
	 * @param budgetItem The budget item to post in the ledger books.
	 * @param fundSource The source where the funds are used for the budget item.
	 * @param amount The amount to post in the transaction.
	 * @param postedOn The date the transaction is to be posted.
	 *
	 * @return The transaction identification number when successfully posted.
	 */
	accounting::TransactionNumber postNontrack(
		const budgeting::BudgetSource& budgetItem,
		const accounting::AccountCode& fundSource,
		const pecunia::currency::Money& amount,
		const ::QDate& postedOn
	);
	/**
	 * @brief Posts a wage budget item transaction to the general ledger, and moving it to the next
	 * time it's due.
	 *
	 * @exception TransactionError When the transaction fails to be posted.
	 * @exception BudgetItemError When the budget item fails to be updated.
	 *
	 * @param budgetItem The budget item providing the funds in the ledger books.
	 * @param fundTarget The source where the funds are placed from the budget item.
	 * @param amount The amount to post in the transaction.
	 * @param postedOn The date the transaction is to be posted.
	 *
	 * @return The transaction identification number when successfully posted.
	 */
	accounting::TransactionNumber postWage(
		const budgeting::BudgetSource& budgetItem,
		const accounting::AccountCode& fundTarget,
		const pecunia::currency::Money& amount,
		const ::QDate& postedOn
	);
	/**
	 * @brief Posts a budget item in an off cycle moment, i.e. unexpected.
	 *
	 * @exception TransactionError When the transaction fails to be posted.
	 *
	 * @param bii The identification of the budget item that is to be posted.
	 * @param amount The amount to post.
	 * @param ac The account involved in the posting
	 * @param postOn The date the transaction is posted.
	 *
	 * @return drn::accounting::TransactionNumber
	 */
	accounting::TransactionNumber postUnexpected(
		const budgeting::BudgetItemIdentifier& bii,
		const pecunia::currency::Money& amount,
		const accounting::AccountCode& ac,
		const ::QDate& postOn
	);
	/**
	 * @brief Skips the bill budget item due, moving it to the next time it's due.
	 *
	 * @exception BudgetItemError When the budget item is fails to be updated.
	 *
	 * @param budgetItem The item that is to be skipped.
	 */
	void skipBill(const budgeting::BudgetSource& budgetItem);
	/**
	 * @brief Skips the debt budget item due, moving it to the next time it's due.
	 *
	 * @exception BudgetItemError When the budget item is fails to be updated.
	 *
	 * @param budgetItem The item that is to be skipped.
	 */
	void skipDebt(const budgeting::BudgetSource& budgetItem);
	/**
	 * @brief Skips the goal budget item due, moving it to the next time it's due.
	 *
	 * @exception BudgetItemError When the budget item is fails to be updated.
	 *
	 * @param budgetItem The item that is to be skipped.
	 */
	void skipGoal(const budgeting::BudgetSource& budgetItem);
	/**
	 * @brief Skips the nontrack budget item due, moving it to the next time it's due.
	 *
	 * @exception BudgetItemError When the budget item is fails to be updated.
	 *
	 * @param budgetItem The item that is to be skipped.
	 */
	void skipNontrack(const budgeting::BudgetSource& budgetItem);
	/**
	 * @brief Skips the wage budget item due, moving it to the next time it's due.
	 *
	 * @exception BudgetItemError When the budget item is fails to be updated.
	 *
	 * @param budgetItem The item that is to be skipped.
	 */
	void skipWage(const budgeting::BudgetSource& budgetItem);
};

bool operator==(const BudgetLedgers& lhs, const BudgetLedgers& rhs);
bool operator!=(const BudgetLedgers& lhs, const BudgetLedgers& rhs);
std::ostream& operator<<(std::ostream& o, const BudgetLedgers& bia);

}}}

template<typename BudgetItemType>
const drn::accounting::AccountCode& drn::navigation::internal::BudgetLedgers::budgetAccountCode(
	const drn::budgeting::BudgetSource& source
) const
{
	return this->budgetCodes_.value<BudgetItemType>(source);
}

#endif
