/*******************************************************************************
***  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 "BudgetFile.h"
using ::QString;
using std::map;
using std::tuple;
using drn::file_storage::BudgetFile;

#include <QtDebug>
// using qCritical;
// using qDebug;
#include <QFile>
using ::QFile;
#include <QFileInfo>
using ::QFileInfo;

#include <exception>
using std::exception;

#include <accounting/AccountCode.h>
using drn::accounting::AccountCode;
using drn::accounting::AccountNumber;
#include <accounting/LedgerBooks.h>
using drn::accounting::GeneralLedger;
#include <banking/Bank.h>
using drn::banking::Bank;
#include <banking/BankAccountType.h>
using drn::banking::SupportedAccountTypes;
#include <banking/BankMap.h>
using drn::banking::BankMap;
#include <banking/BankName.h>
using drn::banking::BankName;
#include <budgeting/Budget.h>
using  drn::budgeting::Budget;
#include <budgeting/BudgetSource.h>
using drn::budgeting::BudgetSource;
#include <foundation/Error.h>
using drn::foundation::Error;
#include <foundation/Optional.hpp>
using drn::foundation::Optional;
#include <foundation/TypeIndexMap.hpp>
using drn::foundation::TypeIndexMap;
#include <internal/XmlBudgetFile.h>
using drn::file_storage::internal::read;
using drn::file_storage::internal::write;


const QChar BudgetFile::defaultBackUpExtension_{'~'};
const QString BudgetFile::oldBackUpExtension_{".old"};

BudgetFile::BudgetFile() :
	directoryPath_{},
	name_{}
{}

BudgetFile::BudgetFile(const QString& fileLocation, const QString& backUpExtension) :
	directoryPath_{},
	name_{},
	backUpExtension_{
		backUpExtension == BudgetFile::defaultBackUpExtension_
			? backUpExtension
			: '.' + backUpExtension
	}
{
	if (fileLocation.trimmed().isEmpty())
		throw Error{QObject::tr("The file location cannot be empty.")};
	const QFileInfo info{fileLocation};
	this->directoryPath_ = info.absolutePath();
	this->name_ = info.fileName();

	if (info.isRelative())
		throw Error{QObject::tr("The file location must be an absolute path.")};

	// N.B. testing the passed in value since the full stop is added when it's not the default.
	if (backUpExtension.startsWith("."))
		throw Error{
			QObject::tr("The back-up budget file extension cannot start with a full stop (.).")
		};
}

const QString& BudgetFile::directoryPath() const
{
	return this->directoryPath_;
}

const QString& BudgetFile::name() const
{
	return this->name_;
}

const QString& BudgetFile::backUpExtension() const
{
	return this->backUpExtension_;
}

tuple<
	GeneralLedger,
	Budget,
	TypeIndexMap<BudgetSource, AccountCode>,
	BankMap,
	map<BankName, map<AccountNumber, SupportedAccountTypes>>
> BudgetFile::load() const
{
	QFile file{this->directoryPath_ + '/' + this->name_};

	if ( ! file.open(QIODevice::ReadOnly | QIODevice::Text))
		throw Error{
			QObject::tr("The budget file, %1, failed to be loaded, due to %2.")
				.arg(this->name_)
				.arg(file.errorString())
		};
	return read(file);
}

void BudgetFile::store(
	const GeneralLedger& gl,
	const Budget& b,
	const TypeIndexMap<BudgetSource, AccountCode>& budgetCodes,
	const BankMap& banks,
	const map<BankName, map<AccountNumber, SupportedAccountTypes>>& bankAccountTypes
) const
{
	const auto fileNamePath{this->directoryPath_ + '/' + this->name_};
	qDebug() << "Storing Budget File" << fileNamePath;
	QFile file{fileNamePath};
	Optional<QFile> backUpFile;

	if (file.exists())
	{
		const auto backUpFileNamePath{fileNamePath + this->backUpExtension_};
		backUpFile.emplace(backUpFileNamePath);
		Optional<QFile> oldBackUpFile;

		if (backUpFile->exists())
		{
			const auto oldBackUpFileName{backUpFileNamePath + BudgetFile::oldBackUpExtension_};

			if ( ! backUpFile->rename(oldBackUpFileName))
				throw Error{
					QObject::tr(
						"The back-up budget file, %1, failed to be backed-up prior to backing up "
							"the budget file due to %2."
					)
						.arg(backUpFileNamePath)
						.arg(backUpFile->errorString())
				};
			qDebug() << "Successfully backed-up the old back-up budget file.";
			oldBackUpFile.emplace(oldBackUpFileName);
		}

		if (file.copy(backUpFileNamePath))
		{
			qDebug() << "Successfully backed-up the budget file.";

			if (oldBackUpFile.hasValue())
			{
				if (oldBackUpFile->remove())
					qDebug() << "Successfully removed the old back-up budget file.";
				else
					qCritical() << QObject::tr(
						"Failed to remove the old back-up budget file '%1' due to %2."
					).arg(oldBackUpFile->fileName()).arg(oldBackUpFile->errorString());
			}
		}
		else
		{
			if (oldBackUpFile.hasValue())
			{
				if (oldBackUpFile->rename(backUpFileNamePath))
					qDebug() << "Successfully restored the old back-up budget file.";
				else
					qCritical() << QObject::tr(
						"Failed to restore the back-up budget file '%1' due to %2."
					).arg(oldBackUpFile->fileName()).arg(oldBackUpFile->errorString());
			}
			throw Error{
				QObject::tr(
					"The budget file, %1, failed to be backed-up prior to saving due to %2."
				)
					.arg(this->name_)
					.arg(file.errorString())
			};
		}
	}
	else
		qDebug() << "No pre-existing budget file found, no back-up needed.";

	if ( ! file.open(QIODevice::WriteOnly | QIODevice::Text))
		throw Error{
			QObject::tr("The budget file, %1, failed to be saved while opening due to %2.")
				.arg(this->name_)
				.arg(file.errorString())
		};

	try
	{
		write(b, gl, banks, bankAccountTypes, budgetCodes, file);

		if (file.error() != QFileDevice::NoError)
			throw Error{
				QObject::tr("The budget file, %1, failed to be saved due to %2.")
					.arg(this->name_)
					.arg(file.errorString())
			};
	}
	catch (const exception&)
	{
		qCritical() << "An error occurred after writing the budget file started.";

		if (file.remove())
			qDebug() << "Successfully removed the broken budget file.";
		else
		{
			qWarning() << "Failed to remove the broken budget file, " + file.fileName()
					+ ", due to " + file.errorString() + '.';
			throw;
		}

		if (backUpFile.hasValue())
		{
			if (backUpFile->rename(fileNamePath))
				qDebug() << "Successfully restored the backed-up budget file.";
			else
			{
				qWarning() << "Failed to restore the backed-up budget file, "
						+ backUpFile->fileName() + ", failed to be renamed due to "
						+ backUpFile->errorString() + '.';
			}
		}
		throw;
	}
}
