/*******************************************************************************
***  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 "TransactionWidget.h"
using Qt::CheckState;
using ::QWidget;
using std::set;
using ::quint32;
using std::vector;
using drn::accounting::AccountCode;
using drn::desktop_ui::internal::TransactionWidget;
using drn::foundation::Optional;

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

#include <algorithm>
using std::sort;

#include <accounting/Transaction.h>
using drn::accounting::Transaction;
using drn::accounting::TransactionNumber;
using drn::accounting::TransactionStatuses;
#include <desktop-ui/DesktopUiErrors.h>
using drn::desktop_ui::DataEntryError;
#include <desktop-ui/MoneyEntryWidget.h>
using drn::desktop_ui::MoneyEntryWidget;
#include <foundation/ObserverPtr.hpp>
using drn::foundation::ObserverPtr;


namespace
{

CheckState toCheckState(const TransactionStatuses& status)
{
	switch (status)
	{
	case TransactionStatuses::Unreconciled:
		return Qt::Unchecked;
	case TransactionStatuses::Cleared:
		return Qt::PartiallyChecked;
	case TransactionStatuses::Reconciled:
		return Qt::Checked;
	default:
		Q_ASSERT(false);
		return Qt::Unchecked;
	};
}

TransactionStatuses toTransactionStatuses(const CheckState& state)
{
	switch (state)
	{
	case Qt::Unchecked:
		return TransactionStatuses::Unreconciled;
	case Qt::PartiallyChecked:
		return TransactionStatuses::Cleared;
	case Qt::Checked:
		return TransactionStatuses::Reconciled;
	default:
		Q_ASSERT(false);
		return TransactionStatuses::Unreconciled;
	};
}

const auto txnNumDescription{QObject::tr("TXN #")};

}

void TransactionWidget::markDirty()
{
	qDebug() << "Marking transaction" << this->numberLbl->text() << "dirty";
	this->isDirty_.flipOn();
}

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

TransactionWidget::TransactionWidget(
	const Transaction& transaction,
	const AccountCode& displayedAccountCode,
	vector<AccountCode> accountCodes,
	const set<Iso4217Codes>& usableCurrencies,
	const ObserverPtr<QWidget>& parent
) :
	QWidget{parent.observed()},
	isDirty_{},
	displayedAccount_{displayedAccountCode}
{
	this->setAttribute(Qt::WA_DeleteOnClose);
	this->setupUi(this);
	this->amountMnyntry->showLabel(false);
	this->amountMnyntry->setUsableCodes(usableCurrencies);
	sort(accountCodes.begin(), accountCodes.end());

	for (const auto& accountCode : accountCodes)
	{
		const auto code{presentationText(accountCode)};

		if (accountCode != this->displayedAccount_)
			this->accountCmbbx->addItem(code);
	}

	if (transaction.number().hasValue())
	{
		this->occurredOnDtdt->setDate(transaction.occurredOn());
		this->setNumber(*transaction.number());
		this->descriptionLndt->setText(transaction.description().valueOrDefault());
		this->descriptionLndt->setCursorPosition(0);
		this->reconciledChckbx->setCheckState(toCheckState(transaction.status()));
		this->accountCmbbx->setCurrentText(
			presentationText(
				this->displayedAccount_ == transaction.debiting()
					? transaction.crediting()
					: transaction.debiting()
			)
		);
		this->debitRdbttn->setChecked(this->displayedAccount_ != transaction.debiting());
		this->creditRdbttn->setChecked(this->displayedAccount_ != transaction.crediting());
		this->amountMnyntry->setMoney(transaction.amount());

		if (transaction.status() == TransactionStatuses::Reconciled)
		{
			this->occurredOnDtdt->setReadOnly(true);
			this->descriptionLndt->setReadOnly(true);
			this->reconciledChckbx->setEnabled(false);
			this->accountCmbbx->setEnabled(false);
			this->debitRdbttn->setEnabled(false);
			this->creditRdbttn->setEnabled(false);
			this->amountMnyntry->setReadOnly(true);
			this->reconciledChckbx->setEnabled(false);
			this->removeChckbx->hide();
		}
	}
	else
	{
		this->occurredOnDtdt->setDate(QDate::currentDate());
		this->numberLbl->clear();
	}
	this->errorMessageLbl->hide();

	//{ Connect to Mark Dirty

	connect(
		this->occurredOnDtdt,
		&QDateTimeEdit::dateChanged,
		this,
		&TransactionWidget::markDirty
	);
	connect(
		this->descriptionLndt,
		&QLineEdit::editingFinished,
		this,
		&TransactionWidget::markDirty
	);
	connect(
		this->reconciledChckbx,
		&QCheckBox::stateChanged,
		this,
		&TransactionWidget::markDirty
	);
	connect(
		this->debitRdbttn,
		&QRadioButton::clicked,
		this,
		&TransactionWidget::markDirty
	);
	connect(
		this->creditRdbttn,
		&QRadioButton::clicked,
		this,
		&TransactionWidget::markDirty
	);
	connect(
		this->accountCmbbx,
		qOverload<qint32>(&QComboBox::currentIndexChanged),
		this,
		&TransactionWidget::markDirty
	);
	connect(
		this->amountMnyntry,
		&MoneyEntryWidget::editingFinished,
		this,
		&TransactionWidget::markDirty
	);
	connect(
		this->amountMnyntry,
		qOverload<qint32>(&MoneyEntryWidget::currentIndexChanged),
		this,
		&TransactionWidget::markDirty
	);
	connect(
		this->removeChckbx,
		&QCheckBox::stateChanged,
		this,
		&TransactionWidget::markDirty
	);

	//}
}

bool TransactionWidget::isDirty() const
{
	return this->isDirty_.isOn();
}

Optional<TransactionNumber> TransactionWidget::number() const
{
	const auto numberTextSplit{this->numberLbl->text().split(txnNumDescription)};

	if (numberTextSplit.count() == 2)
	{
		const auto numberText{numberTextSplit.at(1)};
		bool isConverted{false};
		const quint32 txnNum{numberText.toUInt(&isConverted)};

		if (isConverted)
			return TransactionNumber{txnNum};
	}
	return {};
}

bool TransactionWidget::shouldRemove() const
{
	return this->removeChckbx->isChecked();
}

QString TransactionWidget::description() const
{
	return this->descriptionLndt->text();
}

QDate TransactionWidget::occurredOn() const
{
	return this->occurredOnDtdt->date();
}

bool TransactionWidget::isDebiting() const
{
	return this->debitRdbttn->isChecked();
}

bool TransactionWidget::isCrediting() const
{
	return this->creditRdbttn->isChecked();
}

QString TransactionWidget::account() const
{
	return this->accountCmbbx->currentText();
}

Money TransactionWidget::amount() const
{
	return this->amountMnyntry->money();
}

// N.B. This is optional since the transaction entry may have already been posted, but was
// modified.
void TransactionWidget::saved(const Optional<TransactionNumber>& posted)
{
	if (posted.hasValue())
		this->setNumber(*posted);
	this->isDirty_.flipOff();
}

void TransactionWidget::setNumber(const TransactionNumber& txnNum)
{
	qDebug() << "Set Transaction Number for" << this->description() << "to" << txnNum.integer();
	this->numberLbl->setText(txnNumDescription + QString::number(txnNum.integer()));
}

bool TransactionWidget::isCleared() const
{
	return toTransactionStatuses(this->reconciledChckbx->checkState())
		== TransactionStatuses::Cleared;
}

bool TransactionWidget::isReconciled() const
{
	return toTransactionStatuses(this->reconciledChckbx->checkState())
		== TransactionStatuses::Reconciled;
}

const AccountCode& TransactionWidget::displayedAccount() const
{
	return this->displayedAccount_;
}

void TransactionWidget::setErrorMessage(const QString& errorMessage)
{
	this->errorMessageLbl->setText(errorMessage);
	this->isDirty_.flipOn();
	this->errorMessageLbl->show();
}
