/*******************************************************************************
***  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_BANKLEDGERS_H_
#define DRN_NAVIGATION_INTERNAL_BANKLEDGERS_H_

#include <QtGlobal>
#include <QString>

#include <iosfwd>
#include <map>
#include <set>

#include <accounting/AccountCode.h>
#include <banking/BankName.h>
#include <banking/BankAccountType.h>
#include <banking/BankMap.h>
#include "foundation/ObserverPtr.hpp"


namespace pecunia
{
namespace currency
{

class Money;

}}

namespace drn
{
namespace accounting
{

class AccountCode;
class AccountNumberGenerator;
enum class AccountTypes : quint8;
class GeneralLedger;

}
namespace foundation
{

template<typename>
class Optional;

}
namespace navigation
{
namespace internal
{

/**
 * @brief A manager for the relationship between the general ledger and the banks.
 */
class BankLedgers
{
	foundation::ObserverPtr<banking::BankMap> banks_;
	foundation::ObserverPtr<accounting::GeneralLedger> ledgers_;
	std::map<
		banking::BankName,
		std::map<accounting::AccountNumber, banking::SupportedAccountTypes>
	> bankAccountTypes_;

public:
	/**
	 * @brief Fully constructs the associations for banks and the general ledger.
	 *
	 * @exception BankError When a financial institution uses the accounts of another financial
	 * institution.
	 * @exception BankError When a financial institution user understandable account type is not
	 * known.
	 * @exception BankError When a financial institution has no associations.
	 * @exception BankError When a financial institution uses the a default ledger account.
	 *
	 * @param banks The banks to be managed.
	 * @param ledgers The ledgers for the banks to use.
	 */
	BankLedgers(
		foundation::ObserverPtr<banking::BankMap> banks,
		foundation::ObserverPtr<accounting::GeneralLedger> ledgers,
		std::map<
			banking::BankName,
			std::map<accounting::AccountNumber, banking::SupportedAccountTypes>
		> bankAccountTypes
	);
	/**
	 * @brief An accessor for the banks being managed.
	 *
	 * @return The current banks managed.
	 */
	const banking::BankMap& banks() const;
	/**
	 * @brief An accessor for the ledgers being managed.
	 *
	 * @return The current ledgers managed.
	 */
	const accounting::GeneralLedger& ledgers() const;
	/**
	 * @brief An accessor to the association between a bank, its accounts and the user
	 * understandable bank account type.
	 *
	 * @return The current association of bank and their account types..
	 */
	const std::map<
		banking::BankName,
		std::map<accounting::AccountNumber, banking::SupportedAccountTypes>
	>& bankAccountTypes() const;
	/**
	 * @brief Locates the name of a financial institution associated with a general ledger account.
	 *
	 * @param number The account number to find the associated financial institution.
	 *
	 * @return When found gives the financial institution identifier, else an empty value.
	 */
	foundation::Optional<banking::BankName> findBankName(const accounting::AccountNumber& number);
	/**
	 * @brief Adds a bank to the system. Any accounts associated with this bank will be associated
	 * as an unknown bank account type.
	 *
	 * @exception BankError When the accounts associated with the bank are not in the general
	 * ledger.
	 * @exception BankError When the associated account ledger in the financial institution does not
	 * match the associations with the bank types.
	 *
	 * @param bank The bank to be added.
	 * @param accountNumbersBankTypes The account ledger associations.
	 */
	void add(
		banking::Bank bank,
		std::map<accounting::AccountNumber, banking::SupportedAccountTypes> accountNumbersBankTypes = {}
	);
	/**
	 * @brief Adds an account to the system.
	 *
	 * @exception BankError When the bank does not exist.
	 *
	 * @param bn The bank to associate the account with.
	 * @param accountName The name of the account.
	 * @param type The type of bank account that is associated.
	 * @param balance The opening balance of the account.
	 * @param generator The account code number generator ready to be used.
	 */
	void addAccount(
		const banking::BankName& bn,
		const ::QString& accountName,
		const banking::SupportedAccountTypes& type,
		const pecunia::currency::Money& balance,
		accounting::AccountNumberGenerator& generator
	);
	/**
	 * @brief Adds an association with an account number to a bank.
	 *
	 * @exception BankError When the bank does not exist.
	 * @exception BankError When another bank is already associated with that number.
	 * @exception BankError When the account number does not exist.
	 *
	 * @param name The name of the bank to add the account number to.
	 * @param number The number to add to the bank.
	 * @param type The type of bank account the general ledger account is managing.
	 */
	void addAssociation(
		const banking::BankName& name,
		const accounting::AccountNumber& number,
		const banking::SupportedAccountTypes& type
	);
	/**
	 * @brief Renames the financial institution.
	 *
	 * @exception BankError When the financial institution name to be changed does not exist.
	 * @exception BankError When the financial institution name to be changed and to be changed
	 * into are the same.
	 * @exception BankError When the financial institution name to change to already exists.
	 * @exception BankError When the financial institution is already closed.
	 *
	 * @param original The current name of the financial institution.
	 * @param changed The changed name of the financial institution.
	 */
	void rename(const banking::BankName& original, const banking::BankName& changed);
	/**
	 * @brief Marks the financial institution and associated accounts as closed for usage.
	 *
	 * @exception BankError When the financial institution does not exist.
	 * @exception AccountError When an associated ledger account cannot be closed.
	 *
	 * @param bn The name of the financial institution.
	 */
	void close(const banking::BankName& bn);
	/**
	 * @brief Marks the financial institution and associated accounts as opened for usage.
	 *
	 * @exception BankError When the financial institution does not exist.
	 *
	 * @param name The name of the financial institution.
	 */
	void open(const banking::BankName& name);
	/**
	 * @brief Determines if the container of bank accounts is empty.
	 *
	 * @return Gives true when there are not any banks in the container, false otherwise.
	 */
	bool isEmpty() const;
	/**
	 * @brief Clears all the bank account types associated with banks.
	 */
	void clearBankAccountType();
	/**
	 * @brief Removes a financial institution and all ledger accounts associated with it.
	 *
	 * @exception BankError When the financial institution does not exist.
	 * @exception BankError When the financial institution is closed.
	 * @exception BankError When any of the financial institution's associated ledger accounts
	 * cannot be removed.
	 *
	 * @param bn The name of the financial institution to remove.
	 */
	void remove(const banking::BankName& bn);
	/**
	 * @brief Updates the financial institution information and the account ledger associations.
	 *
	 * @exception BankError When the financial information to update is closed.
	 * @exception BankError When the associated account ledger in the financial institution does not
	 * match the associations with the bank types.
	 * @exception BankError When the account ledger to associate does not exist.
	 *
	 * @param bank The financial institution to update.
	 * @param accountNumbersBankTypes The new account ledger associations.
	 */
	void update(
		banking::Bank bank,
		const std::map<
			accounting::AccountNumber,
			banking::SupportedAccountTypes
		>& accountNumbersBankTypes
	);
	/**
	 * @brief Recodes the financial institution account association.
	 *
	 * @exception AccountError When no financial institution is associated with the original
	 * account.
	 * @exception BankError When a financial institution is already associated with the new
	 * account.
	 * @exception BankError When the association removal fails.
	 *
	 * @param original The original number for the financial institution account association.
	 * @param changed The new number for the financial institution account association.
	 */
	void recode(
		const accounting::AccountNumber& original,
		const accounting::AccountNumber& changed
	);
	bool hasAssociation(const banking::BankName& name, const accounting::AccountNumber& number);
	/**
	 * @brief Removes an account association from a financial institution.
	 *
	 * @exception BankError When the financial institution does not exist.
	 * @exception BankError When no financial institution is associated with the original account.
	 *
	 * @param name The financial institution to remove the account association from.
	 * @param number The account number to be removed.
	 */
	void removeAssociation(const banking::BankName& name, const accounting::AccountNumber& number);
	/**
	 * @brief Looks-up all account codes associated with a given bank.
	 *
	 * @param name The name of the bank whose account codes are desired.
	 *
	 * @return The account codes associated with the bank or an empty container when either the bank
	 * does not exist or has no associated ledger accounts.
	 */
	std::set<banking::BankAccountType> bankAccountCodes(const banking::BankName& name) const;
};

bool operator==(const BankLedgers& lhs, const BankLedgers& rhs);
std::ostream& operator<<(std::ostream& o, const BankLedgers& ba);

}}}

#endif
