/*******************************************************************************
***  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 "AccountEntryWidget.h"
using ::QWidget;
using ::QString;
using std::set;
using drn::accounting::AccountCode;
using drn::accounting::AccountNumber;
using drn::accounting::AccountTypes;
using drn::accounting::allAccountTypes;
using drn::accounting::presentationText;
using drn::accounting::toAccountCode;
using drn::desktop_ui::internal::AccountEntryWidget;
using drn::foundation::SwitchStates;
using drn::foundation::Optional;

#include <pecunia/Codes.h>
using pecunia::currency::Iso4217Codes;
#include <pecunia/Money.h>
using pecunia::currency::Money;
#include <QtDebug>
// using qDebug
#include <QIntValidator>
using ::QIntValidator;

#include <utility>
using std::move;

#include <banking/BankAccountType.h>
using drn::banking::allSupportedAccountTypes;
using drn::banking::SupportedAccountTypes;
using drn::banking::toSupportedAccountTypes;
#include <foundation/ObserverPtr.hpp>
using drn::foundation::ObserverPtr;
#include <foundation/QtMemory.hpp>
using drn::foundation::makeQtPtr;


namespace
{

const auto newAccountNumber{presentationText(AccountNumber{})};
const QString newAccountName{QObject::tr("New Account")};
const QString noParent{QObject::tr("No Parent")};

}

void AccountEntryWidget::markDirty()
{
	if ( ! this->isEditable_)
		return;

	if ( ! this->isDirty_)
	{
		qInfo() << "Marking as dirty account entry #"
				<< this->original_.valueOrDefault().number().integer();
		this->isDirty_.flipOn();
	}
}

AccountEntryWidget::AccountEntryWidget(
	Optional<AccountCode> original,
	const bool isClosed,
	const AccountTypes at,
	const Money& balance,
	const set<AccountNumber>& potentialParents,
	const set<Iso4217Codes>& usableCurrencies,
	const ObserverPtr<QWidget>& parent
) :
	AccountEntryWidget{
		std::move(original),
		isClosed,
		at,
		balance,
		potentialParents,
		usableCurrencies,
		{},
		parent
	}
{}

AccountEntryWidget::AccountEntryWidget(
	Optional<AccountCode> original,
	const bool isClosed,
	const Optional<AccountTypes> at,
	const Money& balance,
	const set<AccountNumber>& potentialParents,
	const set<Iso4217Codes>& usableCurrencies,
	const Optional<SupportedAccountTypes>& bankAccountType,
	const ObserverPtr<QWidget>& parent
) :
	QWidget{parent.observed()},
	isDirty_{SwitchStates::Off},
	isEditable_{SwitchStates::On},
	original_{::move(original)},
	isOriginallyClosed_{isClosed}
{
	this->setupUi(this);

	for (const auto& accountType : allSupportedAccountTypes)
		this->bankTypeCmbbx->addItem(presentationText(accountType));

	for (const auto& accountType : allAccountTypes)
		this->typeCmbbx->addItem(presentationText(accountType));

	this->parentCmbbx->addItem(noParent);
	for (const auto& potentialParent : potentialParents)
		this->addParentableAccountNumber(potentialParent);
	auto validator{makeQtPtr<QIntValidator>(this)};
	validator->setBottom(0);
	this->numberLndt->setValidator(validator.observed());

	if (at.hasValue())
		this->typeCmbbx->setCurrentText(presentationText(*at));
	else
	{
		this->typeLbl->hide();
		this->typeCmbbx->hide();
	}

	// Check if a bank account is being displayed, and adjust accordingly.
	if (bankAccountType.hasValue())
	{
		this->bankTypeCmbbx->setCurrentText(presentationText(*bankAccountType));
		this->numberLbl->hide();
		this->numberLndt->hide();
		this->parentLbl->hide();
		this->parentCmbbx->hide();
	}
	else
	{
		this->bankTypeLbl->hide();
		this->bankTypeCmbbx->hide();
	}
	this->balanceMnyntrywdgt->setUsableCodes(usableCurrencies);
	this->balanceMnyntrywdgt->setMoney(balance);

	if (isClosed)
		this->closedChckbx->setChecked(true);
	this->errorsLbl->hide();
	this->numberLndt->setPlaceholderText(newAccountNumber);
	this->nameLndt->setPlaceholderText(newAccountName);

	if (this->original_.hasValue())
	{
		if (this->original_->parent().hasValue())
			this->parentCmbbx->setCurrentText(presentationText(*this->original_->parent()));
		this->numberLndt->setText(presentationText(this->original_->number()));
		this->numberLndt->setReadOnly(true);
		this->nameLndt->setText(this->original_->name());
		this->balanceMnyntrywdgt->setLabel(tr("Running Balance:"));
		this->balanceMnyntrywdgt->setReadOnly(true);
		this->typeCmbbx->setEnabled(false);
		this->bankTypeCmbbx->setEnabled(false);
	}
	else
	{
		this->balanceMnyntrywdgt->setLabel(tr("Opening Balance:"));
	}

	//{ Connect the dirty flag

	connect(
		this->typeCmbbx,
		qOverload<qint32>(&QComboBox::currentIndexChanged),
		this,
		&AccountEntryWidget::markDirty
	);
	connect(
		this->bankTypeCmbbx,
		qOverload<qint32>(&QComboBox::currentIndexChanged),
		this,
		&AccountEntryWidget::markDirty
	);
	connect(
		this->numberLndt,
		&QLineEdit::editingFinished,
		this,
		&AccountEntryWidget::markDirty
	);
	connect(
		this->nameLndt,
		&QLineEdit::editingFinished,
		this,
		&AccountEntryWidget::markDirty
	);
	connect(
		this->parentCmbbx,
		qOverload<qint32>(&QComboBox::currentIndexChanged),
		this,
		&AccountEntryWidget::markDirty
	);
	connect(
		this->balanceMnyntrywdgt,
		&MoneyEntryWidget::editingFinished,
		this,
		&AccountEntryWidget::markDirty
	);
	connect(
		this->balanceMnyntrywdgt,
		qOverload<qint32>(&MoneyEntryWidget::currentIndexChanged),
		this,
		&AccountEntryWidget::markDirty
	);
	connect(
		this->closedChckbx,
		&QCheckBox::stateChanged,
		this,
		&AccountEntryWidget::markDirty
	);
	connect(
		this->removeChckbx,
		&QCheckBox::stateChanged,
		this,
		&AccountEntryWidget::markDirty
	);

	//}
}

bool AccountEntryWidget::isDirty() const noexcept
{
	return this->isDirty_;
}

Optional<AccountCode> AccountEntryWidget::original() const noexcept
{
	return this->original_;
}

bool AccountEntryWidget::isOriginallyClosed() const noexcept
{
	return this->isOriginallyClosed_;
}

const QString& AccountEntryWidget::newAccountCodeText()
{
	static const auto txt{newAccountNumber + AccountCode::separator_ + newAccountName};
	return txt;
}

AccountNumber::ValueType AccountEntryWidget::number() const
{
	bool converted;
	const auto number{this->numberLndt->text().toUInt(&converted)};

	if (converted)
		return number;
	return AccountNumber::invalid_;
}

QString AccountEntryWidget::name() const
{
	return this->nameLndt->text();
}

Optional<AccountNumber::ValueType> AccountEntryWidget::parentNumber() const
{
	const auto current{this->parentCmbbx->currentText()};

	if (current == noParent)
		return {};
	bool converted;
	const auto number{current.toUInt(&converted)};

	if (converted)
		return number;
	return AccountNumber::invalid_;
}

bool AccountEntryWidget::isClosed() const
{
	return this->closedChckbx->isChecked();
}

bool AccountEntryWidget::isRemoved() const
{
	return this->removeChckbx->isChecked();
}

QString AccountEntryWidget::type() const
{
	return this->typeCmbbx->currentText();
}

Money AccountEntryWidget::balance() const
{
	return this->balanceMnyntrywdgt->money();
}

QString AccountEntryWidget::accountCodeText() const
{
	const auto num{this->number()};
	const auto txt{this->nameLndt->text()};

	if (num == AccountNumber::invalid_ && txt.isEmpty())
		return AccountEntryWidget::newAccountCodeText();

	if (num == AccountNumber::invalid_)
		return newAccountNumber + AccountCode::separator_ + txt;

	if (txt.isEmpty())
		return presentationText(AccountNumber{num}) + AccountCode::separator_ + newAccountName;
	return presentationText(AccountNumber{num}) + AccountCode::separator_ + txt;
}

bool AccountEntryWidget::isEmptyCode() const
{
	const auto numTxt{this->numberLndt->text()};
	const auto txt{this->nameLndt->text()};
	return numTxt.isEmpty() && txt.isEmpty();
}

void AccountEntryWidget::saved(AccountCode ac)
{
	this->original_ =  std::move(ac);
	this->numberLndt->setText(presentationText(this->original_->number()));
	this->errorsLbl->hide();
	this->errorsLbl->clear();
	this->resetDirtyFlag();
}

void AccountEntryWidget::setErrorMessage(const QString& error)
{
	this->errorsLbl->setText(error);
	this->markDirty();
	this->errorsLbl->show();
}

void AccountEntryWidget::addParentableAccountNumber(const AccountNumber& an)
{
	if (
		( ! this->original_.hasValue() || an != this->original_->number())
		&& an.integer() != this->number()
	)
	{
		const auto numberText{presentationText(an)};

		if (this->parentCmbbx->findText(numberText) == -1)
			this->parentCmbbx->addItem(numberText);
	}
}

void AccountEntryWidget::resetDirtyFlag()
{
	qDebug() << "Resetting the dirty flag for" << this->numberLndt->text();
	this->isDirty_.flipOff();
}

Optional<SupportedAccountTypes> AccountEntryWidget::bankAccountType() const
{
	const auto type{toSupportedAccountTypes(this->bankTypeCmbbx->currentText())};
	return type == SupportedAccountTypes::Unknown
			? Optional<SupportedAccountTypes>{}
			: Optional<SupportedAccountTypes>{type};
}

void AccountEntryWidget::setEditable(const bool isEditable)
{
	this->typeCmbbx->setEnabled(isEditable);
	this->numberLndt->setReadOnly( ! isEditable);
	this->nameLndt->setReadOnly( ! isEditable);
	this->bankTypeCmbbx->setEnabled(isEditable);
	this->parentCmbbx->setEnabled(isEditable);
	this->balanceMnyntrywdgt->setReadOnly( ! isEditable);
	this->closedChckbx->setEnabled(isEditable);
	this->removeChckbx->setEnabled(isEditable);

	if (isEditable)
		this->isEditable_.flipOn();
	else
		this->isEditable_.flipOff();
}
