/*******************************************************************************
***  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 "BankAccountsWidget.h"
using ::QWidget;
using std::set;
using drn::banking::BankAccountType;
using drn::desktop_ui::internal::BankAccountsWidget;
using drn::foundation::ObserverPtr;

#include <QObject>
using ::QObject;

#include <algorithm>
using std::transform;
#include <iterator>
using std::inserter;

#include <accounting/AccountCode.h>
using drn::accounting::AccountCode;
using drn::accounting::presentationText;
using drn::accounting::toAccountCode;
#include <banking/BankName.h>
using drn::banking::BankName;
#include <banking/BankAccount.h>
using drn::banking::BankAccount;
#include <banking/BankAccountType.h>
using drn::banking::allSupportedAccountTypes;
using drn::banking::SupportedAccountTypes;
using drn::banking::presentationText;
using drn::banking::toSupportedAccountTypes;
#include <desktop-ui/DesktopUiErrors.h>
using drn::desktop_ui::DataEntryError;
#include "DisableSignalsTemporarily.hpp"
using drn::desktop_ui::internal::DisableSignalsTemporarily;
#include <foundation/Optional.hpp>
using drn::foundation::Optional;


namespace
{

set<SupportedAccountTypes> everySupportedAccountTypes()
{
	set<SupportedAccountTypes> sat{};
	transform(
		allSupportedAccountTypes.cbegin(),
		allSupportedAccountTypes.cend(),
		inserter(sat, sat.begin()),
		[] (const auto& bankAccountType)
		{
			return bankAccountType;
		}
	);
	return sat;
}

}

Optional<int> BankAccountsWidget::findBankIndex(const BankName& bn)
{
	// Skip the unknown bank name.
	for (auto index{1}, count{this->bankCmbbx->count()} ;  index < count ; ++index)
	{
		const auto item{this->bankCmbbx->itemText(index)};

		if (item == bn.institution())
			return index;
	}
	return {};
}

void BankAccountsWidget::onBankCurrentIndexChanged(const qint32 index)
{
	// N.B. index zero is the empty selection, so we ignore it.
	if (index > 0)
	{
		const BankName bank{this->bankCmbbx->itemText(index)};
		emit changedBankName(bank);
		emit lookUpBankAccounts(bank);
	}
	else
	{
		emit changedBankName({});
		this->lookedUpBankAccounts({});
	}
}

void BankAccountsWidget::onAccountCodesCurrentIndexChanged(const qint32 index)
{
	// N.B. index zero is the empty selection, so we ignore it.
	if (index > 0)
	{
		const auto num{toAccountCode(this->accountCmbbx->itemText(index)).number()};
		emit changedAccountNumber(num);
		const auto accountType{
			find_if(
				this->accountTypes_.cbegin(),
				this->accountTypes_.cend(),
				[&num] (const auto& account)
				{
					return num == account.code_;
				}
			)
		};
		Q_ASSERT(accountType != this->accountTypes_.cend());
		this->setSupportedAccountType(accountType->type_);
	}
	else
	{
		emit changedAccountNumber({});
		this->setSupportedAccountType(SupportedAccountTypes::Unknown);
	}
}

void BankAccountsWidget::onTypeCurrentIndexChanged(const qint32 index)
{
	// N.B. index zero is the empty selection, so we ignore it.
	if (index > 0)
		emit changedBankAccountType(toSupportedAccountTypes(this->typeCmbbx->itemText(index)));
	else
		emit changedBankAccountType(SupportedAccountTypes::Unknown);
}

void BankAccountsWidget::lookedUpBankAccounts(set<BankAccountType> accountTypes)
{
	this->accountTypes_ = std::move(accountTypes);
	this->accountCmbbx->clear();
	this->accountCmbbx->addItem("");

	for (const auto& accountType : this->accountTypes_)
		this->accountCmbbx->addItem(presentationText(accountType.code_));
}

BankAccountsWidget::BankAccountsWidget(const ObserverPtr<QWidget>& parent) :
	BankAccountsWidget{{}, {}, parent}
{}

BankAccountsWidget::BankAccountsWidget(
	const set<BankName>& banks,
	const Optional<set<SupportedAccountTypes>>& accountTypes,
	const ObserverPtr<QWidget>& parent
) :
	QWidget{parent.observed()},
	accountTypes_{}
{
	this->setAttribute(Qt::WA_DeleteOnClose);
	this->setupUi(this);
	this->accountCmbbx->addItem("");
	this->typeCmbbx->addItem("");
	this->setDescription({});
	this->setBanks(banks);

	for (const auto& type : accountTypes.valueOr(everySupportedAccountTypes()))
		if (type != SupportedAccountTypes::Unknown)
			this->typeCmbbx->addItem(presentationText(type));

	//{ Connect Bank

	connect(
		this->bankCmbbx,
		qOverload<qint32>(&QComboBox::currentIndexChanged),
		this,
		&BankAccountsWidget::onBankCurrentIndexChanged
	);

	//}

	//{ Connect Bank Account

	connect(
		this->accountCmbbx,
		qOverload<qint32>(&QComboBox::currentIndexChanged),
		this,
		&BankAccountsWidget::onAccountCodesCurrentIndexChanged
	);

	//}

	//{ Connect Bank Account Type

	connect(
		this->typeCmbbx,
		qOverload<qint32>(&QComboBox::currentIndexChanged),
		this,
		&BankAccountsWidget::onTypeCurrentIndexChanged
	);

	//}
}

set<BankName> BankAccountsWidget::banks() const noexcept
{
	set<BankName> banks;

	// Skipping the first element since it's always the unknown one.
	for (auto index{1}, count{this->bankCmbbx->count()} ; index < count ; ++index)
		banks.emplace(this->bankCmbbx->itemText(index));
	return banks;
}

Optional<BankName> BankAccountsWidget::bank() const noexcept
{
	auto text{this->bankCmbbx->currentText()};

	if (text.isEmpty())
		return {};
	return BankName{std::move(text)};
}

Optional<AccountCode> BankAccountsWidget::accountCode() const noexcept
{
	auto text{this->accountCmbbx->currentText()};

	if (text.isEmpty())
		return {};
	return toAccountCode(std::move(text));
}

Optional<SupportedAccountTypes> BankAccountsWidget::type() const noexcept
{
	auto text{this->typeCmbbx->currentText()};

	if (text.isEmpty())
		return {};
	return toSupportedAccountTypes(std::move(text));
}

void BankAccountsWidget::setBanks(const set<BankName>& banks)
{
	this->bankCmbbx->clear();
	this->bankCmbbx->addItem("");

	for (const auto& bank : banks)
		this->bankCmbbx->addItem(bank.institution());
}

void BankAccountsWidget::setBank(const Optional<BankName>& bn)
{
	if ( ! bn.hasValue())
	{
		this->bankCmbbx->setCurrentIndex(0);
		return;
	}
	const auto bankIndex{this->findBankIndex(*bn)};

	if (bankIndex.hasValue())
		this->bankCmbbx->setCurrentIndex(*bankIndex);
	else
		throw DataEntryError{
			QObject::tr("bank account"),
			bn->institution(),
			QObject::tr("Failed to locate the bank under the given name.")

		};
}

void BankAccountsWidget::setBankAccount(const BankAccount& ba)
{
	DisableSignalsTemporarily<BankAccountsWidget> _{this};
	this->setBank(ba.bank_);

	bool foundAccount{false};
	// Skip the unknown bank account code.
	for (auto index{1}, count{this->accountCmbbx->count()} ;  index < count ; ++index)
	{
		const auto item{this->accountCmbbx->itemText(index)};

		if (toAccountCode(item) == ba.account_.code_)
		{
			this->accountCmbbx->setCurrentIndex(index);
			foundAccount = true;
			break;
		}
	}

	if ( ! foundAccount)
	{
		this->accountTypes_.emplace(ba.account_);
		const auto text{presentationText(ba.account_.code_)};
		this->accountCmbbx->addItem(text);
		this->accountCmbbx->setCurrentText(text);
	}
	this->setSupportedAccountType(ba.account_.type_);
}

void BankAccountsWidget::setSupportedAccountTypes(const set<SupportedAccountTypes>& sats)
{
	DisableSignalsTemporarily<QComboBox> _{this->typeCmbbx};
	this->typeCmbbx->clear();
	this->typeCmbbx->addItem("");

	for (const auto& sat : sats)
		this->typeCmbbx->addItem(presentationText(sat));
}

void BankAccountsWidget::setSupportedAccountType(const SupportedAccountTypes& sat)
{
	this->typeCmbbx->setCurrentIndex(0);

	if (sat != SupportedAccountTypes::Unknown)
		this->typeCmbbx->setCurrentText(presentationText(sat));
}

void BankAccountsWidget::hideAccountCodes()
{
	this->accountLbl->hide();
	this->accountCmbbx->hide();
}

void BankAccountsWidget::showAccountCodes()
{
	this->accountLbl->show();
	this->accountCmbbx->show();
}

void BankAccountsWidget::enableSupportedAccountTypes()
{
	this->typeCmbbx->setEnabled(true);
}

void BankAccountsWidget::disableSupportedAccountTypes()
{
	this->typeCmbbx->setEnabled(false);
}

void BankAccountsWidget::setReadOnly(const bool isReadOnly)
{
	this->bankCmbbx->setEnabled( ! isReadOnly);
	this->accountCmbbx->setEnabled( ! isReadOnly);
	this->typeCmbbx->setEnabled( ! isReadOnly);
}

void BankAccountsWidget::setDescription(const Optional<QString>& description)
{
	if (description.hasValue())
	{
		this->descriptionLbl->setText(*description);
		this->descriptionLbl->show();
	}
	else
	{
		this->descriptionLbl->clear();
		this->descriptionLbl->hide();
	}
}
