/*******************************************************************************
***  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 "TransactionPoster.h"
using std::make_pair;
using std::move;
using std::ostream;
using std::pair;
using ::quint32;
using drn::accounting::TransactionNumber;
using drn::accounting::internal::EntryTypes;
using drn::accounting::internal::TransactionPoster;

#include <QObject>
using ::QObject;

#include <string>
using std::string;

#include <accounting/Account.h>
using drn::accounting::Account;
using drn::accounting::AccountCode;
using drn::accounting::AccountTypes;
using drn::accounting::presentationText;
#include <accounting/AccountingErrors.h>
using drn::accounting::TransactionError;
#include <accounting/Transaction.h>
using drn::accounting::Transaction;
#include "AccountTransactionKey.hpp"
using drn::accounting::internal::AccountTransactionKey;
#include <foundation/EnumMacros.hpp>
// using BEGIN_ENUMERATION_TO_OTHER
// using MAP_ENUMERATION_TO_OTHER
// using END_ENUMERATION_TO_OTHER


namespace
{

string toString(const EntryTypes& et)
{
	BEGIN_ENUMERATION_TO_OTHER(et);
		MAP_ENUMERATION_TO_OTHER(EntryTypes::Unknown, "Unknown");
		MAP_ENUMERATION_TO_OTHER(EntryTypes::Debit, "Debit");
		MAP_ENUMERATION_TO_OTHER(EntryTypes::Credit, "Credit");
	END_ENUMERATION_TO_OTHER(et, EntryTypes);
}

}

pair<Transaction, Transaction> TransactionPoster::perform(
	Account& debit,
	Account& credit,
	const Transaction& transaction,
	const TransactionNumber& number
)
{
	if (debit.code() != transaction.debiting())
		throw TransactionError{
			number,
			transaction,
			QObject::tr(
				"The debiting account is not the same as the supplied account, %1, in the "
					"transaction."
			).arg(presentationText(debit.code()))
		};

	if (debit.type() == AccountTypes::Unknown)
		throw TransactionError{
			number,
			transaction,
			QObject::tr("The debiting account type is not known.")
		};

	if (credit.code() != transaction.crediting())
		throw TransactionError{
			number,
			transaction,
			QObject::tr(
				"The crediting account is not the same as the supplied account, %1, in the "
					"transaction."
			).arg(presentationText(credit.code()))
		};

	if (credit.type() == AccountTypes::Unknown)
		throw TransactionError{
			number,
			transaction,
			QObject::tr("The crediting account type is not known.")
		};
	const AccountTransactionKey key{};

	switch (debit.type())
	{
	case AccountTypes::Asset:
	case AccountTypes::Expense:
		debit.increase(transaction.amount(), key);
		break;

	case AccountTypes::Liability:
	case AccountTypes::Revenue:
	case AccountTypes::Capital:
		debit.decrease(transaction.amount(), key);
		break;

	case AccountTypes::Unknown:
		break;
	};

	switch (credit.type())
	{
	case AccountTypes::Liability:
	case AccountTypes::Revenue:
	case AccountTypes::Capital:
		credit.increase(transaction.amount(), key);
		break;

	case AccountTypes::Asset:
	case AccountTypes::Expense:
		credit.decrease(transaction.amount(), key);
		break;

	case AccountTypes::Unknown:
		break;
	};
	auto debitTxn{transaction};
	debitTxn.setNumber(number, key);

	if (debitTxn.amount().code() != debit.balance().code())
		debitTxn.convertAmount(debit.balance().code(), key);
	auto creditTxn{transaction};
	creditTxn.setNumber(number, key);

	if (creditTxn.amount().code() != credit.balance().code())
		creditTxn.convertAmount(credit.balance().code(), key);
	return make_pair(move(debitTxn), move(creditTxn));
}

void TransactionPoster::reverse(Account& debit, Account& credit, const Transaction& transaction)
{
	if (debit.code() != transaction.debiting())
		throw TransactionError{
			transaction.number(),
			transaction,
			QObject::tr(
				"The debiting account is not the same as the supplied account, %1, in the reverse "
					"transaction."
			).arg(presentationText(debit.code()))
		};

	if (debit.type() == AccountTypes::Unknown)
		throw TransactionError{
			transaction.number(),
			transaction,
			QObject::tr("The reverse debiting account type is not known.")
		};

	if (credit.code() != transaction.crediting())
		throw TransactionError{
			transaction.number(),
			transaction,
			QObject::tr(
				"The crediting account is not the same as the supplied account, %1, in the "
					"reverse transaction."
			).arg(presentationText(credit.code()))
		};

	if (credit.type() == AccountTypes::Unknown)
		throw TransactionError{
			transaction.number(),
			transaction,
			QObject::tr("The reverse crediting account type is not known.")
		};
	const AccountTransactionKey key{};

	switch (debit.type())
	{
	case AccountTypes::Liability:
	case AccountTypes::Revenue:
	case AccountTypes::Capital:
		debit.increase(transaction.amount(), key);
		break;

	case AccountTypes::Asset:
	case AccountTypes::Expense:
		debit.decrease(transaction.amount(), key);
		break;

	case AccountTypes::Unknown:
		break;
	};

	switch (credit.type())
	{
	case AccountTypes::Asset:
	case AccountTypes::Expense:
		credit.increase(transaction.amount(), key);
		break;

	case AccountTypes::Liability:
	case AccountTypes::Revenue:
	case AccountTypes::Capital:
		credit.decrease(transaction.amount(), key);
		break;

	case AccountTypes::Unknown:
		break;
	};
}

void TransactionPoster::recodeAccount(
	const AccountCode& toRecode,
	Transaction& transaction,
	const AccountCode& recodeAs
)
{
	AccountTransactionKey key{};

	if (transaction.crediting() == toRecode)
		transaction.changeCredit(recodeAs, key);
	else if (transaction.debiting() == toRecode)
		transaction.changeDebit(recodeAs, key);
	else
		throw TransactionError{
			transaction.number(),
			transaction,
			QObject::tr(
				"Cannot change the transaction to use '%1' as the account code '%2' is not "
					"involved in the transaction."
			)
				.arg(presentationText(recodeAs))
				.arg(presentationText(toRecode))
		};
}

ostream& drn::accounting::internal::operator<<(ostream& o, const EntryTypes& et)
{
	return o << toString(et);
}

EntryTypes drn::accounting::internal::selectTAccount(
	const AccountTypes& type,
	const bool isIncreasing
)
{
	switch (type)
	{
	case AccountTypes::Asset:
		return isIncreasing ? EntryTypes::Debit : EntryTypes::Credit;
	case AccountTypes::Capital:
		return isIncreasing ? EntryTypes::Credit : EntryTypes::Debit;
	case AccountTypes::Expense:
		return isIncreasing ? EntryTypes::Debit : EntryTypes::Credit;
	case AccountTypes::Liability:
		return isIncreasing ? EntryTypes::Credit : EntryTypes::Debit;
	case AccountTypes::Revenue:
		return isIncreasing ? EntryTypes::Credit : EntryTypes::Debit;
	case AccountTypes::Unknown:
	default:
		assert(false && "The supplied account type is not known.");
		return EntryTypes::Unknown;
	};
}
