/*******************************************************************************
***  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_ACCOUNTING_TRANSACTION_H_
#define DRN_ACCOUNTING_TRANSACTION_H_

#include <pecunia/Money.h>
#include <QDate>
#include <QString>

#include <iosfwd>

#include "Account.h"
#include "accounting_export.h"
#include <foundation/Optional.hpp>


namespace drn
{
namespace accounting
{
namespace internal
{

class AccountTransactionKey;

}

/**
 * @brief The unique number that identifies a transaction in the accounting system.
 */
class DRN_ACCOUNTING_EXPORT TransactionNumber
{
	quint32 value_;

public:
	/**
	 * @brief Default initalises the object to an invalid numerical value.
	 */
	TransactionNumber();
	/**
	 * @brief Fully initalises the object with the supplied values.
	 *
	 * @exception TransactionError When the supplied new value is zero.
	 *
	 * @param value The number for the transaction.
	 */
	explicit TransactionNumber(const quint32& value);
	/**
	 * @brief Equality operator compares all members.
	 *
	 * @param other The other object whose members should all be equal.
	 *
	 * @return When all member variables are the same it'll give true, else false.
	 */
	bool operator==(const TransactionNumber& other) const;
	/**
	 * @brief Equality operator compares the instance against a value.
	 *
	 * @param other The numerical value that should be equal.
	 *
	 * @return When the member variable is the same it'll give true, else false.
	 */
	bool operator==(const quint32 other) const;
	/**
	 * @brief Inequality operator compares all members.
	 *
	 * @param other The other object whose members should be not equal.
	 *
	 * @return When any member variables is not the same it'll give true, else false.
	 */
	bool operator!=(const TransactionNumber& other) const;
	/**
	 * @brief Inequality operator compares the instance against a value.
	 *
	 * @param other The numerical value that should not be equal.
	 *
	 * @return When the member variable is not the same it'll give true, else false.
	 */
	bool operator!=(const quint32 other) const;
	/**
	 * @brief Less-than comparison between the current instance member variables and another.
	 *
	 * @param other The other object whose members should all be less-than.
	 *
	 * @return When all member variables in the current instance is less than the supplied.
	 */
	bool operator<(const TransactionNumber& other) const;
	/**
	 * @brief Greater-than comparison between the current instance member variables and another.
	 *
	 * @param other The other object whose members should all be greater-than.
	 *
	 * @return When all member variables in the current instance is greater than the supplied.
	 */
	bool operator>(const TransactionNumber& other) const;
	/**
	 * @brief Increment the object by one.
	 *
	 * @return The instance after the increment operation is performed.
	 */
	TransactionNumber& operator++();
	/**
	 * @brief Increment the object by one.
	 *
	 * @return The instance before the increment operation is performed.
	 */
	TransactionNumber operator++(int);
	/**
	 * @brief The current weak type numerical value of the transaction.
	 */
	const quint32& integer() const;

	/// The number that indicates that it is invalid and empty.
	static const quint32 invalid_;
};
/**
 * @brief The possible states a transaction may be in with regards to the accounts involved.
 */
enum class DRN_ACCOUNTING_EXPORT TransactionStatuses : quint8
{
	/// The transaction has not been cleared nor reconciled with the account.
	Unreconciled,
	/// The transaction has been cleared through the account, but has not been reconciled.
	Cleared,
	/// The transaction has been reconciled with the account.
	Reconciled,
};
/**
 * @brief The representation of the exchange of money between two accounts.
 */
class DRN_ACCOUNTING_EXPORT Transaction
{
	::QDate occurredOn_;
	AccountCode debiting_;
	AccountCode crediting_;
	pecunia::currency::Money amount_;
	foundation::Optional<::QString> description_;
	foundation::Optional<TransactionNumber> number_;
	TransactionStatuses status_;

public:
	Transaction() = default;
	/**
	 * @brief New transaction initialisation constructor.
	 *
	 * @exception TransactionError When the transaction date is invalid.
	 * @exception TransactionError When the code of the debit account is the same as the code
	 * of the credit account.
	 * @exception TransactionError When the amount is less than zero.
	 *
	 * @param occurredOn The date the Transaction occurred on.
	 * @param debiting The code of the account that is debited.
	 * @param crediting The code of the account that is credited.
	 * @param amount The amount of money applied to each account.
	 * @param description A human friendly description of the transaction.
	 * @param id The identifier for the transaction in the general ledger book iff its posted.
	 * @param status The current status of the transaction.
	 */
	Transaction(
		const ::QDate& occurredOn,
		const AccountCode& debiting,
		const AccountCode& crediting,
		const pecunia::currency::Money& amount,
		foundation::Optional<::QString> description = {},
		foundation::Optional<TransactionNumber> id = {},
		const TransactionStatuses& status = TransactionStatuses::Unreconciled
	);
	Transaction(const Transaction&) = default;
	Transaction(Transaction&&) noexcept = default;
	Transaction& operator=(const Transaction& other);
	Transaction& operator=(Transaction&& other) noexcept;
	/**
	 * @brief Equality operator compares all members.
	 *
	 * @param other The other object whose members should all be equal.
	 *
	 * @return When all member variables are the same it'll give true, else false.
	 */
	bool operator==(const Transaction& other) const;
	/**
	 * @brief Inequality operator compares all members.
	 *
	 * @param other The other object whose members should be not equal.
	 *
	 * @return When any member variables is not the same it'll give true, else false.
	 */
	bool operator!=(const Transaction& other) const;
	/**
	 * @brief Less-than comparison between the current instance member variables and another.
	 *
	 * @param other The other object whose members should all be less-than.
	 *
	 * @return When all member variables in the current instance is less than the supplied in the
	 * order: occurred, number, crediting, debiting, amount gives true, false else-wise.
	 */
	bool operator<(const Transaction& other) const;
	/**
	 * @brief The date the Transaction occurred on.
	 */
	const ::QDate& occurredOn() const;
	/**
	 * @brief The code of the account that was debited.
	 */
	const AccountCode& debiting() const;
	/**
	 * @brief The code of the account that was credited.
	 */
	const AccountCode& crediting() const;
	/**
	 * @brief The amount of money applied to each account.
	 */
	const pecunia::currency::Money& amount() const;
	/**
	 * @brief The description of the transaction, if one was supplied.
	 */
	const foundation::Optional<::QString>& description() const;
	/**
	 * @brief The number of the transaction iff its been posted.
	 */
	const foundation::Optional<TransactionNumber>& number() const;
	/**
	 * @brief Changes the number of the transaction. Once set it cannot be changed.
	 *
	 * @exception TransactionError When the transaction number is already set.
	 *
	 * @param value The new unique identifier for the transaction in the accounting system.
	 * @param allowed The object that allows access to modifying the transaction.
	 */
	void setNumber(TransactionNumber value, const internal::AccountTransactionKey& allowed);
	/**
	 * @brief The current status with regards to this transaction and the accounts involved.
	 */
	const TransactionStatuses& status() const;
	/**
	 * @brief Changes the transaction to have not cleared nor reconciled with the account.
	 *
	 * @exception TransactionError When the transaction is already reconciled.
	 */
	void markUnreconciled();
	/**
	 * @brief Changes the transaction to have cleared the account.
	 *
	 * @exception TransactionError When the transaction is already reconciled.
	 */
	void markCleared();
	/**
	 * @brief Changes the transaction to have reconciled with the account.
	 */
	void markReconciled();
	/**
	 * @brief Changes the debiting account code.
	 *
	 * @exception TransactionError When the new account code is the same as the crediting account.
	 *
	 * @param code The new account code.
	 * @param allowed The object that allows access to modifying the transaction.
	 */
	void changeDebit(const AccountCode& code, const internal::AccountTransactionKey& allowed);
	/**
	 * @brief Changes the crediting account code.
	 *
	 * @exception TransactionError When the new account code is the same as the debiting account.
	 *
	 * @param code The new account code.
	 * @param allowed The object that allows access to modifying the transaction.
	 */
	void changeCredit(const AccountCode& code, const internal::AccountTransactionKey& allowed);
	/**
	 * @brief Converts the transaction amount into a requested currency.
	 *
	 * @exception TransactionError When the currency of the amount is the same as the request
	 * currency.
	 *
	 * @param changeto The currency to change the transaction into.
	 * @param allowed The object that allows access to modifying the transaction.
	 */
	void convertAmount(
		const pecunia::currency::Iso4217Codes& changeto,
		const internal::AccountTransactionKey& allowed
	);
};

/**
 * @brief Converts a weak string representation of a transaction status into its strong from.
 *
 * @exception logic_error When the weak representation is not valid for a transaction status.
 *
 * @param weak The weak representation of a transaction status.
 *
 * @return The strong form of the transaction status.
 */
DRN_ACCOUNTING_EXPORT TransactionStatuses toTransactionStatuses(const ::QString& weak);
DRN_ACCOUNTING_EXPORT std::ostream& operator<<(std::ostream& o, const TransactionNumber& tn);
DRN_ACCOUNTING_EXPORT std::ostream& operator<<(std::ostream& o, const TransactionStatuses& ts);
DRN_ACCOUNTING_EXPORT std::ostream& operator<<(std::ostream& o, const Transaction& t);
DRN_ACCOUNTING_EXPORT ::QString presentationText(const TransactionNumber& tn);
DRN_ACCOUNTING_EXPORT ::QString presentationText(const TransactionStatuses& ts);
DRN_ACCOUNTING_EXPORT ::QString presentationText(const Transaction& t);
/**
 * @brief The abbreviation for crediting an account.
 */
DRN_ACCOUNTING_EXPORT const ::QString& creditAbbreviation();
/**
 * @brief The abbreviation for debiting an account.
 */
DRN_ACCOUNTING_EXPORT const ::QString& debitAbbreviation();

}}

#endif
