/*******************************************************************************
***  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 "BudgetElements.h"
using ::QDate;
using ::QLatin1String;
using ::QString;
using std::int64_t;
using std::map;
using ::quint8;
using ::quint32;
using std::vector;
using drn::file_storage::internal::AccountElement;
using drn::file_storage::internal::AccountsElement;
using drn::file_storage::internal::ApplicationElement;
using drn::file_storage::internal::BasicElement;
using drn::file_storage::internal::BankElement;
using drn::file_storage::internal::BankAccountElement;
using drn::file_storage::internal::BanksElement;
using drn::file_storage::internal::BillElement;
using drn::file_storage::internal::BillsElement;
using drn::file_storage::internal::DiscretionariesElement;
using drn::file_storage::internal::DiscretionaryElement;
using drn::file_storage::internal::DebtElement;
using drn::file_storage::internal::DebtsElement;
using drn::file_storage::internal::GoalElement;
using drn::file_storage::internal::GoalsElement;
using drn::file_storage::internal::IndexedElement;
using drn::file_storage::internal::LedgerElement;
using drn::file_storage::internal::LedgersElement;
using drn::file_storage::internal::TransactionElement;
using drn::file_storage::internal::WageElement;
using drn::file_storage::internal::WagesElement;
using drn::foundation::Optional;

#include <QDebug>
// used for qDebug.
#include <QObject>
using ::QObject;
#include <QXmlStreamReader>
using ::QXmlStreamReader;
#include <QXmlStreamWriter>
using ::QXmlStreamWriter;

#include <utility>
using std::make_pair;
using std::move;

#include <file-storage/FileStorageErrors.h>
using drn::file_storage::AttributeError;
using drn::file_storage::BudgetFileError;
#include "XmlAttribute.h"
using drn::file_storage::internal::readAttributeDate;
using drn::file_storage::internal::readAttributeFloatingPoint;
using drn::file_storage::internal::readAttributeSigned;
using drn::file_storage::internal::readAttributeString;
using drn::file_storage::internal::readAttributeUnsigned;
using drn::file_storage::internal::writeAttributeBoolean;
using drn::file_storage::internal::writeAttributeDate;


namespace
{

void readIndexedAttributes(IndexedElement& element, QXmlStreamReader& xml)
{
	const auto id{readAttributeUnsigned<IndexedElement::IdType>(xml, IndexedElement::idAttribute_)};

	if (id == IndexedElement::invalidId_)
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QString{"The %1 identifier cannot be invalid."}.arg(element.tag())
		};
	element.id_ = id;
}

void writeIndexedAttributes(const IndexedElement& element, QXmlStreamWriter& xml)
{
	xml.writeAttribute(IndexedElement::idAttribute_, QString::number(element.id_));
}

}

//{ IndexedElement

const QLatin1String IndexedElement::idAttribute_{"id"};
const IndexedElement::IdType IndexedElement::invalidId_{0u};

IndexedElement::IndexedElement() :
	id_{IndexedElement::invalidId_}
{}

IndexedElement::IndexedElement(const IdType id) :
	id_{id}
{
	if (id == IndexedElement::invalidId_)
		throw BudgetFileError{
			{},
			{},
			{},
			QObject::tr("An indexed element identifier cannot be invalid.")
		};
}

//}

//{ ApplicationElement

const uint8_t ApplicationElement::currentRevision_{1u};
const QLatin1String ApplicationElement::revisionAttribute_{"revision"};

const QLatin1String& ApplicationElement::tag() const
{
	static const QLatin1String tag{"DuxReiNummariae"};
	return tag;
}

void ApplicationElement::read(QXmlStreamReader& xml)
{
	qDebug() << "Reading Element:" << this->tag();

	if (xml.name() != this->tag())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("This file does not appear to be a budget application file.")
		};
	const auto revision{
		readAttributeUnsigned<uint8_t>(xml, ApplicationElement::revisionAttribute_)
	};

	if (revision > ApplicationElement::currentRevision_)
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The budget revision, %1, is not known to be capable of being parsed.")
				.arg(revision)
		};
	this->revision_ = revision;
}

void ApplicationElement::write(QXmlStreamWriter& xml) const
{
	qDebug() << "Writing Element:" << this->tag();
	xml.writeStartElement(this->tag());
	xml.writeAttribute(
		ApplicationElement::revisionAttribute_,
		QString::number(ApplicationElement::currentRevision_)
	);
	// We do not close the element as this represents the very top level.
}

//}

//{ WageElement

const QLatin1String WageElement::majorAttribute_{"major"};
const QLatin1String WageElement::minorAttribute_{"minor"};
const QLatin1String WageElement::periodAttribute_{"period"};
const QLatin1String WageElement::nameAttribute_{"name"};
const QLatin1String WageElement::currencyAttribute_{"currency"};
const QLatin1String WageElement::nextOccurOnAttribute_{"nextOccurOn"};
const QLatin1String WageElement::accountIdAttribute_{"accountId"};

WageElement::WageElement() :
	IndexedElement{},
	major_{},
	minor_{},
	period_{},
	sourceName_{},
	currency_{},
	nextOccurOn_{},
	accountId_{IndexedElement::invalidId_}
{}

WageElement::WageElement(
	const IdType id,
	const MajorType major,
	const MinorType minor,
	const QString& period,
	const QString& sourceName,
	const QString& currency,
	const QDate& nextOccurOn,
	const quint32& accountNumber
) :
	IndexedElement{id},
	major_{major},
	minor_{minor},
	period_{period},
	sourceName_{sourceName},
	currency_{currency},
	nextOccurOn_{nextOccurOn},
	accountId_{accountNumber}
{
	if (this->currency_ == IndexedElement::invalidId_)
		throw BudgetFileError{
			{},
			{},
			{},
			QObject::tr("The wage currency identifier cannot be zero.")
		};

	if (this->accountId_ == IndexedElement::invalidId_)
		throw BudgetFileError{
			{},
			{},
			{},
			QObject::tr("The wage account identifier cannot be zero.")
		};
}

const QLatin1String& WageElement::tag() const
{
	static const QLatin1String tag{"wage"};
	return tag;
}

void WageElement::read(QXmlStreamReader& xml)
{
	qDebug() << "Reading Element:" << this->tag();

	if ( ! xml.isStartElement())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element is not the starting element.")
		};

	if (xml.name() != this->tag())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element tag is not valid under the current element.")
		};
	readIndexedAttributes(*this, xml);
	this->major_ = readAttributeSigned<MajorType>(xml, WageElement::majorAttribute_);
	this->minor_ = readAttributeUnsigned<MinorType>(xml, WageElement::minorAttribute_);
	this->sourceName_ = readAttributeString(xml, WageElement::nameAttribute_);
	this->currency_ = readAttributeString(xml, WageElement::currencyAttribute_);
	this->period_ = readAttributeString(xml, WageElement::periodAttribute_);
	this->nextOccurOn_ = readAttributeDate(xml, WageElement::nextOccurOnAttribute_);
	this->accountId_ = readAttributeUnsigned<quint32>(xml, WageElement::accountIdAttribute_);
	xml.skipCurrentElement();
}

void WageElement::write(QXmlStreamWriter& xml) const
{
	qDebug() << "Writing Element:" << this->tag();
	xml.writeStartElement(this->tag());
	writeIndexedAttributes(*this, xml);
	xml.writeAttribute(WageElement::majorAttribute_, QString::number(this->major_));
	xml.writeAttribute(WageElement::minorAttribute_, QString::number(this->minor_));
	xml.writeAttribute(WageElement::periodAttribute_, this->period_);
	xml.writeAttribute(WageElement::nameAttribute_, this->sourceName_);
	xml.writeAttribute(WageElement::currencyAttribute_, this->currency_);
	writeAttributeDate(xml, this->tag(), WageElement::nextOccurOnAttribute_, this->nextOccurOn_);
	xml.writeAttribute(WageElement::accountIdAttribute_, QString::number(this->accountId_));
	xml.writeEndElement();
}

//}

//{ WagesElement

const QLatin1String& WagesElement::tag() const
{
	static const QLatin1String tag{"wages"};
	return tag;
}

void WagesElement::read(QXmlStreamReader& xml)
{
	qDebug() << "Reading Element:" << this->tag();

	if ( ! xml.isStartElement())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element is not the starting element.")
		};

	if (xml.name() != this->tag())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element tag is not valid under the current element.")
		};

	while (xml.readNextStartElement())
	{
		WageElement wage{};

		if (xml.name() == wage.tag())
		{
			wage.read(xml);
			this->wages_.emplace_back(move(wage));
		}
		else
			break;
	};
}

void WagesElement::write(QXmlStreamWriter& xml) const
{
	qDebug() << "Writing Element:" << this->tag();
	xml.writeStartElement(this->tag());

	for (const auto& wageElement : this->wages_)
		wageElement.write(xml);
	xml.writeEndElement();
}

//}

//{ BillElement

const QLatin1String BillElement::majorAttribute_{"major"};
const QLatin1String BillElement::minorAttribute_{"minor"};
const QLatin1String BillElement::periodAttribute_{"period"};
const QLatin1String BillElement::nameAttribute_{"name"};
const QLatin1String BillElement::currencyAttribute_{"currency"};
const QLatin1String BillElement::nextOccurOnAttribute_{"nextOccurOn"};
const QLatin1String BillElement::accountIdAttribute_{"accountId"};

BillElement::BillElement() :
	IndexedElement{},
	major_{},
	minor_{},
	period_{},
	sourceName_{},
	currency_{},
	nextOccurOn_{},
	accountId_{IndexedElement::invalidId_}
{}

BillElement::BillElement(
	const IdType id,
	const MajorType major,
	const MinorType minor,
	const QString& period,
	const QString& sourceName,
	const QString& currency,
	const QDate& nextOccurOn,
	const quint32& accountNumber
) :
	IndexedElement{id},
	major_{major},
	minor_{minor},
	period_{period},
	sourceName_{sourceName},
	currency_{currency},
	nextOccurOn_{nextOccurOn},
	accountId_{accountNumber}
{
	if (this->currency_ == IndexedElement::invalidId_)
		throw BudgetFileError{
			{},
			{},
			{},
			QObject::tr("The bill currency identifier cannot be zero.")
		};

	if (this->accountId_ == IndexedElement::invalidId_)
		throw BudgetFileError{
			{},
			{},
			{},
			QObject::tr("The bill account identifier cannot be zero.")
		};
}

const QLatin1String& BillElement::tag() const
{
	static const QLatin1String tag{"bill"};
	return tag;
}

void BillElement::read(QXmlStreamReader& xml)
{
	qDebug() << "Reading Element:" << this->tag();

	if ( ! xml.isStartElement())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element is not the starting element.")
		};

	if (xml.name() != this->tag())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element tag is not valid under the current element.")
		};
	readIndexedAttributes(*this, xml);
	this->major_ = readAttributeSigned<MajorType>(xml, BillElement::majorAttribute_);
	this->minor_ = readAttributeUnsigned<MinorType>(xml, BillElement::minorAttribute_);
	this->sourceName_ = readAttributeString(xml, BillElement::nameAttribute_);
	this->currency_ = readAttributeString(xml, BillElement::currencyAttribute_);
	this->period_ = readAttributeString(xml, BillElement::periodAttribute_);
	this->nextOccurOn_ = readAttributeDate(xml, BillElement::nextOccurOnAttribute_);
	this->accountId_ = readAttributeUnsigned<quint32>(xml, BillElement::accountIdAttribute_);
	xml.skipCurrentElement();
}

void BillElement::write(QXmlStreamWriter& xml) const
{
	qDebug() << "Writing Element:" << this->tag();
	xml.writeStartElement(this->tag());
	writeIndexedAttributes(*this, xml);
	xml.writeAttribute(BillElement::majorAttribute_, QString::number(this->major_));
	xml.writeAttribute(BillElement::minorAttribute_, QString::number(this->minor_));
	xml.writeAttribute(BillElement::periodAttribute_, this->period_);
	xml.writeAttribute(BillElement::nameAttribute_, this->sourceName_);
	xml.writeAttribute(BillElement::currencyAttribute_, this->currency_);
	writeAttributeDate(xml, this->tag(), BillElement::nextOccurOnAttribute_, this->nextOccurOn_);
	xml.writeAttribute(BillElement::accountIdAttribute_, QString::number(this->accountId_));
	xml.writeEndElement();
}

//}

//{ BillsElement

const QLatin1String& BillsElement::tag() const
{
	static const QLatin1String tag{"bills"};
	return tag;
}

void BillsElement::read(QXmlStreamReader& xml)
{
	qDebug() << "Reading Element:" << this->tag();

	if ( ! xml.isStartElement())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element is not the starting element.")
		};

	if (xml.name() != this->tag())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element tag is not valid under the current element.")
		};

	while (xml.readNextStartElement())
	{
		BillElement bill{};

		if (xml.name() == bill.tag())
		{
			bill.read(xml);
			this->bills_.emplace_back(move(bill));
		}
		else
			break;
	};
}

void BillsElement::write(QXmlStreamWriter& xml) const
{
	qDebug() << "Writing Element:" << this->tag();
	xml.writeStartElement(this->tag());

	for (const auto& billElement : this->bills_)
		billElement.write(xml);
	xml.writeEndElement();
}

//}

//{ DebtElement

const QLatin1String DebtElement::totalMajorAttribute_{"totalMajor"};
const QLatin1String DebtElement::totalMinorAttribute_{"totalMinor"};
const QLatin1String DebtElement::periodAttribute_{"period"};
const QLatin1String DebtElement::nameAttribute_{"name"};
const QLatin1String DebtElement::currencyAttribute_{"currency"};
const QLatin1String DebtElement::minimumMajorAttribute_{"minimumMajor"};
const QLatin1String DebtElement::minimumMinorAttribute_{"minimumMinor"};
const QLatin1String DebtElement::interestAttribute_{"interest"};
const QLatin1String DebtElement::nextOccurOnAttribute_{"nextOccurOn"};
const QLatin1String DebtElement::accountIdAttribute_{"accountId"};

DebtElement::DebtElement() :
	IndexedElement{},
	totalMajor_{},
	totalMinor_{},
	period_{},
	sourceName_{},
	currency_{},
	minimumMajor_{},
	minimumMinor_{},
	interest_{},
	nextOccurOn_{},
	accountId_{IndexedElement::invalidId_}
{}

DebtElement::DebtElement(
	const IdType id,
	const MajorType totalMajor,
	const MinorType totalMinor,
	const QString& period,
	const QString& sourceName,
	const QString& currency,
	const MajorType minimumMajor,
	const MinorType minimumMinor,
	const qreal interest,
	const QDate& nextOccurOn,
	const quint32& accountNumber
) :
	IndexedElement{id},
	totalMajor_{totalMajor},
	totalMinor_{totalMinor},
	period_{period},
	sourceName_{sourceName},
	currency_{currency},
	minimumMajor_{minimumMajor},
	minimumMinor_{minimumMinor},
	interest_{interest},
	nextOccurOn_{nextOccurOn},
	accountId_{accountNumber}
{
	if (this->currency_ == IndexedElement::invalidId_)
		throw BudgetFileError{
			{},
			{},
			{},
			QObject::tr("The debt currency identifier cannot be zero.")
		};

	if (this->accountId_ == IndexedElement::invalidId_)
		throw BudgetFileError{
			{},
			{},
			{},
			QObject::tr("The debt account identifier cannot be zero.")
		};
}

const QLatin1String& DebtElement::tag() const
{
	static const QLatin1String tag{"debt"};
	return tag;
}

void DebtElement::read(QXmlStreamReader& xml)
{
	qDebug() << "Reading Element:" << this->tag();

	if ( ! xml.isStartElement())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element is not the starting element.")
		};

	if (xml.name() != this->tag())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element tag is not valid under the current element.")
		};
	readIndexedAttributes(*this, xml);
	this->totalMajor_ = readAttributeSigned<MajorType>(xml, DebtElement::totalMajorAttribute_);
	this->totalMinor_ = readAttributeUnsigned<MinorType>(
		xml,
		DebtElement::totalMinorAttribute_
	);
	this->period_ = readAttributeString(xml, DebtElement::periodAttribute_);
	this->sourceName_ = readAttributeString(xml, DebtElement::nameAttribute_);
	this->currency_ = readAttributeString(xml, DebtElement::currencyAttribute_);
	this->minimumMajor_ = readAttributeSigned<MajorType>(
		xml,
		DebtElement::minimumMajorAttribute_
	);
	this->minimumMinor_ = readAttributeUnsigned<MinorType>(
		xml,
		DebtElement::minimumMinorAttribute_
	);
	this->interest_ = readAttributeFloatingPoint<qreal>(xml, DebtElement::interestAttribute_);
	this->nextOccurOn_ = readAttributeDate(xml, DebtElement::nextOccurOnAttribute_);
	this->accountId_ = readAttributeUnsigned<quint32>(xml, DebtElement::accountIdAttribute_);
	xml.skipCurrentElement();
}

void DebtElement::write(QXmlStreamWriter& xml) const
{
	qDebug() << "Writing Element:" << this->tag();
	xml.writeStartElement(this->tag());
	writeIndexedAttributes(*this, xml);
	xml.writeAttribute(DebtElement::totalMajorAttribute_, QString::number(this->totalMajor_));
	xml.writeAttribute(DebtElement::totalMinorAttribute_, QString::number(this->totalMinor_));
	xml.writeAttribute(DebtElement::periodAttribute_, this->period_);
	xml.writeAttribute(DebtElement::nameAttribute_, this->sourceName_);
	xml.writeAttribute(DebtElement::currencyAttribute_, this->currency_);
	xml.writeAttribute(
		DebtElement::minimumMajorAttribute_,
		QString::number(this->minimumMajor_)
	);
	xml.writeAttribute(
		DebtElement::minimumMinorAttribute_,
		QString::number(this->minimumMinor_)
	);
	xml.writeAttribute(DebtElement::interestAttribute_, QString::number(this->interest_));
	writeAttributeDate(xml, this->tag(), DebtElement::nextOccurOnAttribute_, this->nextOccurOn_);
	xml.writeAttribute(DebtElement::accountIdAttribute_, QString::number(this->accountId_));
	xml.writeEndElement();
}

//}

//{ DebtsElement

const QLatin1String& DebtsElement::tag() const
{
	static const QLatin1String tag{"debts"};
	return tag;
}

void DebtsElement::read(QXmlStreamReader& xml)
{
	qDebug() << "Reading Element:" << this->tag();

	if ( ! xml.isStartElement())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element is not the starting element.")
		};

	if (xml.name() != this->tag())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element tag is not valid under the current element.")
		};

	while (xml.readNextStartElement())
	{
		DebtElement debt{};

		if (xml.name() == debt.tag())
		{
			debt.read(xml);
			this->debts_.emplace_back(move(debt));
		}
		else
			break;
	};
}

void DebtsElement::write(QXmlStreamWriter& xml) const
{
	qDebug() << "Writing Element:" << this->tag();
	xml.writeStartElement(this->tag());

	for (const auto& libilityElement : this->debts_)
		libilityElement.write(xml);
	xml.writeEndElement();
}

//}

//{ GoalElement

const QLatin1String GoalElement::majorAttribute_{"major"};
const QLatin1String GoalElement::minorAttribute_{"minor"};
const QLatin1String GoalElement::periodAttribute_{"period"};
const QLatin1String GoalElement::nameAttribute_{"name"};
const QLatin1String GoalElement::currencyAttribute_{"currency"};
const QLatin1String GoalElement::finalMajorAttribute_{"finalMajor"};
const QLatin1String GoalElement::finalMinorAttribute_{"finalMinor"};
const QLatin1String GoalElement::nextOccurOnAttribute_{"nextOccurOn"};
const QLatin1String GoalElement::accountIdAttribute_{"accountId"};

GoalElement::GoalElement() :
	IndexedElement{},
	major_{},
	minor_{},
	period_{},
	sourceName_{},
	currency_{},
	finalMajor_{},
	finalMinor_{},
	nextOccurOn_{},
	accountId_{IndexedElement::invalidId_}
{}

GoalElement::GoalElement(
	const IdType id,
	const MajorType major,
	const MinorType minor,
	const QString& period,
	const QString& sourceName,
	const QString& currency,
	const MajorType finalMajor,
	const MinorType finalMinor,
	const QDate& nextOccurOn,
	const quint32& accountNumber
) :
	IndexedElement{id},
	major_{major},
	minor_{minor},
	period_{period},
	sourceName_{sourceName},
	currency_{currency},
	finalMajor_{finalMajor},
	finalMinor_{finalMinor},
	nextOccurOn_{nextOccurOn},
	accountId_{accountNumber}
{
	if (this->currency_ == IndexedElement::invalidId_)
		throw BudgetFileError{
			{},
			{},
			{},
			QObject::tr("The goal currency identifier cannot be zero.")
		};

	if (this->accountId_ == IndexedElement::invalidId_)
		throw BudgetFileError{
			{},
			{},
			{},
			QObject::tr("The goal account identifier cannot be zero.")
		};
}

const QLatin1String& GoalElement::tag() const
{
	static const QLatin1String tag{"goal"};
	return tag;
}

void GoalElement::read(QXmlStreamReader& xml)
{
	qDebug() << "Reading Element:" << this->tag();

	if ( ! xml.isStartElement())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element is not the starting element.")
		};

	if (xml.name() != this->tag())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element tag is not valid under the current element.")
		};
	readIndexedAttributes(*this, xml);
	this->major_ = readAttributeSigned<MajorType>(xml, GoalElement::majorAttribute_);
	this->minor_ = readAttributeUnsigned<MinorType>(xml, GoalElement::minorAttribute_);
	this->sourceName_ = readAttributeString(xml, GoalElement::nameAttribute_);
	this->currency_ = readAttributeString(xml, GoalElement::currencyAttribute_);
	this->period_ = readAttributeString(xml, GoalElement::periodAttribute_);
	this->finalMajor_ = readAttributeSigned<MajorType>(xml, GoalElement::finalMajorAttribute_);
	this->finalMinor_ = readAttributeUnsigned<MinorType>(xml, GoalElement::finalMinorAttribute_);
	this->nextOccurOn_ = readAttributeDate(xml, GoalElement::nextOccurOnAttribute_);
	this->accountId_ = readAttributeUnsigned<quint32>(xml, GoalElement::accountIdAttribute_);
	xml.skipCurrentElement();
}

void GoalElement::write(QXmlStreamWriter& xml) const
{
	qDebug() << "Writing Element:" << this->tag();
	xml.writeStartElement(this->tag());
	writeIndexedAttributes(*this, xml);
	xml.writeAttribute(GoalElement::majorAttribute_, QString::number(this->major_));
	xml.writeAttribute(GoalElement::minorAttribute_, QString::number(this->minor_));
	xml.writeAttribute(GoalElement::periodAttribute_, this->period_);
	xml.writeAttribute(GoalElement::nameAttribute_, this->sourceName_);
	xml.writeAttribute(GoalElement::currencyAttribute_, this->currency_);
	xml.writeAttribute(GoalElement::finalMajorAttribute_, QString::number(this->finalMajor_));
	xml.writeAttribute(GoalElement::finalMinorAttribute_, QString::number(this->finalMinor_));
	writeAttributeDate(xml, this->tag(), GoalElement::nextOccurOnAttribute_, this->nextOccurOn_);
	xml.writeAttribute(GoalElement::accountIdAttribute_, QString::number(this->accountId_));
	xml.writeEndElement();
}

//}

//{ GoalsElement

const QLatin1String& GoalsElement::tag() const
{
	static const QLatin1String tag{"goals"};
	return tag;
}

void GoalsElement::read(QXmlStreamReader& xml)
{
	qDebug() << "Reading Element:" << this->tag();

	if ( ! xml.isStartElement())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element is not the starting element.")
		};

	if (xml.name() != this->tag())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element tag is not valid under the current element.")
		};

	while (xml.readNextStartElement())
	{
		GoalElement goal{};

		if (xml.name() == goal.tag())
		{
			goal.read(xml);
			this->goals_.emplace_back(move(goal));
		}
		else
			break;
	};
}

void GoalsElement::write(QXmlStreamWriter& xml) const
{
	qDebug() << "Writing Element:" << this->tag();
	xml.writeStartElement(this->tag());

	for (const auto& goalElement : this->goals_)
		goalElement.write(xml);
	xml.writeEndElement();
}

//}

//{ DiscretionaryElement

const QLatin1String DiscretionaryElement::majorAttribute_{"major"};
const QLatin1String DiscretionaryElement::minorAttribute_{"minor"};
const QLatin1String DiscretionaryElement::periodAttribute_{"period"};
const QLatin1String DiscretionaryElement::nameAttribute_{"name"};
const QLatin1String DiscretionaryElement::currencyAttribute_{"currency"};
const QLatin1String DiscretionaryElement::nextOccurOnAttribute_{"nextOccurOn"};
const QLatin1String DiscretionaryElement::accountIdAttribute_{"accountId"};

DiscretionaryElement::DiscretionaryElement() :
	major_{},
	minor_{},
	period_{},
	currency_{},
	nextOccurOn_{},
	accountId_{IndexedElement::invalidId_}
{}

DiscretionaryElement::DiscretionaryElement(
	const IdType id,
	const MajorType major,
	const MinorType minor,
	const QString& period,
	const QString& sourceName,
	const QString& currency,
	const QDate& nextOccurOn,
	const quint32& accountNumber
) :
	IndexedElement{id},
	major_{major},
	minor_{minor},
	period_{period},
	sourceName_{sourceName},
	currency_{currency},
	nextOccurOn_{nextOccurOn},
	accountId_{accountNumber}
{
	if (this->currency_ == IndexedElement::invalidId_)
		throw BudgetFileError{
			{},
			{},
			{},
			QObject::tr("The nontrack spending currency identifier cannot be zero.")
		};

	if (this->accountId_ == IndexedElement::invalidId_)
		throw BudgetFileError{
			{},
			{},
			{},
			QObject::tr("The nontrack account identifier cannot be zero.")
		};
}

const QLatin1String& DiscretionaryElement::tag() const
{
	static const QLatin1String tag{"nontrack"};
	return tag;
}

void DiscretionaryElement::read(QXmlStreamReader& xml)
{
	qDebug() << "Reading Element:" << this->tag();

	if ( ! xml.isStartElement())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element is not the starting element.")
		};

	if (xml.name() != this->tag())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element tag is not valid under the current element.")
		};
	readIndexedAttributes(*this, xml);
	this->major_ = readAttributeSigned<MajorType>(xml, DiscretionaryElement::majorAttribute_);
	this->minor_ = readAttributeUnsigned<MinorType>(xml, DiscretionaryElement::minorAttribute_);
	this->sourceName_ = readAttributeString(xml, DiscretionaryElement::nameAttribute_);
	this->currency_ = readAttributeString(xml, DiscretionaryElement::currencyAttribute_);
	this->period_ = readAttributeString(xml, DiscretionaryElement::periodAttribute_);
	this->nextOccurOn_ = readAttributeDate(xml, DiscretionaryElement::nextOccurOnAttribute_);
	this->accountId_ = readAttributeUnsigned<quint32>(
		xml,
		DiscretionaryElement::accountIdAttribute_
	);
	xml.skipCurrentElement();
}

void DiscretionaryElement::write(QXmlStreamWriter& xml) const
{
	qDebug() << "Writing Element:" << this->tag();
	xml.writeStartElement(this->tag());
	writeIndexedAttributes(*this, xml);
	xml.writeAttribute(DiscretionaryElement::majorAttribute_, QString::number(this->major_));
	xml.writeAttribute(DiscretionaryElement::minorAttribute_, QString::number(this->minor_));
	xml.writeAttribute(DiscretionaryElement::periodAttribute_, this->period_);
	xml.writeAttribute(DiscretionaryElement::nameAttribute_, this->sourceName_);
	xml.writeAttribute(DiscretionaryElement::currencyAttribute_, this->currency_);
	writeAttributeDate(
		xml,
		this->tag(),
		DiscretionaryElement::nextOccurOnAttribute_,
		this->nextOccurOn_
	);
	xml.writeAttribute(
		DiscretionaryElement::accountIdAttribute_,
		QString::number(this->accountId_)
	);
	xml.writeEndElement();
}

//}

//{ DiscretionariesElement

const QLatin1String& DiscretionariesElement::tag() const
{
	static const QLatin1String tag{"nontracks"};
	return tag;
}

void DiscretionariesElement::read(QXmlStreamReader& xml)
{
	qDebug() << "Reading Element:" << this->tag();

	if ( ! xml.isStartElement())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element is not the starting element.")
		};

	if (xml.name() != this->tag())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element tag is not valid under the current element.")
		};

	while (xml.readNextStartElement())
	{
		DiscretionaryElement discretionary{};

		if (xml.name() == discretionary.tag())
		{
			discretionary.read(xml);
			this->discretionaries_.emplace_back(move(discretionary));
		}
		else
			break;
	};
}

void DiscretionariesElement::write(QXmlStreamWriter& xml) const
{
	qDebug() << "Writing Element:" << this->tag();
	xml.writeStartElement(this->tag());

	for (const auto& discretionaryElement : this->discretionaries_)
		discretionaryElement.write(xml);
	xml.writeEndElement();
}

//}

//{ AccountElement

const QLatin1String AccountElement::nameAttribute_{"name"};
const QLatin1String AccountElement::typeAttribute_{"type"};
const QLatin1String AccountElement::majorAttribute_{"major"};
const QLatin1String AccountElement::minorAttribute_{"minor"};
const QLatin1String AccountElement::currencyAttribute_{"currency"};
const QLatin1String AccountElement::closedAttribute_{"closed"};
const QLatin1String AccountElement::parentAttribute_{"parentId"};

AccountElement::AccountElement() :
	IndexedElement{},
	name_{},
	type_{},
	major_{},
	minor_{},
	currency_{},
	isClosed_{}
{}

AccountElement::AccountElement(
	const IndexedElement::IdType& number,
	const QString& name,
	const QString& type,
	const MajorType& major,
	const MinorType& minor,
	const QString& currency,
	const bool closed,
	const Optional<quint32> parent
) :
	IndexedElement{number},
	name_{name},
	type_{type},
	major_{major},
	minor_{minor},
	currency_{currency},
	isClosed_{closed},
	parent_{move(parent)}
{
	if (this->currency_.isEmpty())
		throw BudgetFileError{
			{},
			{},
			{},
			QObject::tr("The account currency identifier cannot be zero.")
		};
}

const QLatin1String& AccountElement::tag() const
{
	static const QLatin1String tag{"account"};
	return tag;
}

void AccountElement::read(QXmlStreamReader& xml)
{
	qDebug() << "Reading Element:" << this->tag();

	if ( ! xml.isStartElement())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element is not the starting element.")
		};

	if (xml.name() != this->tag())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element tag is not valid under the current element.")
		};
	readIndexedAttributes(*this, xml);
	this->name_ = readAttributeString(xml, AccountElement::nameAttribute_);
	this->type_ = readAttributeString(xml, AccountElement::typeAttribute_);
	this->major_ = readAttributeSigned<MajorType>(xml, AccountElement::majorAttribute_);
	this->minor_ = readAttributeUnsigned<MinorType>(xml, AccountElement::minorAttribute_);
	this->currency_ = readAttributeString(xml, AccountElement::currencyAttribute_);
	this->isClosed_ = readAttributeBoolean(xml, AccountElement::closedAttribute_);
	this->parent_ = readOptionalAttributeUnsigned<quint32>(xml, AccountElement::parentAttribute_);
	xml.skipCurrentElement();
}

void AccountElement::write(QXmlStreamWriter& xml) const
{
	xml.writeStartElement(this->tag());
	writeIndexedAttributes(*this, xml);
	xml.writeAttribute(AccountElement::nameAttribute_, this->name_);
	xml.writeAttribute(AccountElement::typeAttribute_, this->type_);
	xml.writeAttribute(AccountElement::majorAttribute_, QString::number(this->major_));
	xml.writeAttribute(AccountElement::minorAttribute_, QString::number(this->minor_));
	xml.writeAttribute(AccountElement::currencyAttribute_, this->currency_);
	writeAttributeBoolean(xml, AccountElement::closedAttribute_, this->isClosed_);

	if (this->parent_.hasValue())
		xml.writeAttribute(AccountElement::parentAttribute_, QString::number(*this->parent_));
	xml.writeEndElement();
}

//}

//{ AccountsElement

const QLatin1String& AccountsElement::tag() const
{
	static const QLatin1String tag{"accounts"};
	return tag;
}

void AccountsElement::read(QXmlStreamReader& xml)
{
	qDebug() << "Reading Element:" << this->tag();

	if ( ! xml.isStartElement())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element is not the starting element.")
		};

	if (xml.name() != this->tag())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element tag is not valid under the current element.")
		};

	while (xml.readNextStartElement())
	{
		AccountElement account{};

		if (xml.name() == account.tag())
		{
			account.read(xml);
			this->accounts_.emplace_back(move(account));
		}
		else
			break;
	};
}

void AccountsElement::write(QXmlStreamWriter& xml) const
{
	qDebug() << "Writing Element:" << this->tag();
	xml.writeStartElement(this->tag());

	for (const auto& accountElement : this->accounts_)
		accountElement.write(xml);
	xml.writeEndElement();
}

//}

//{ TransactionElement

const QLatin1String TransactionElement::dateAttribute_{"date"};
const QLatin1String TransactionElement::accountIdAttribute_{"accountId"};
const QLatin1String TransactionElement::entryAttribute_{"entry"};
const QLatin1String TransactionElement::majorAttribute_{"major"};
const QLatin1String TransactionElement::minorAttribute_{"minor"};
const QLatin1String TransactionElement::currencyAttribute_{"currency"};
const QLatin1String TransactionElement::statusAttribute_{"status"};

TransactionElement::TransactionElement() :
	IndexedElement{},
	date_{},
	accountId_{},
	entry_{},
	major_{},
	minor_{},
	currency_{},
	status_{},
	description_{}
{}

TransactionElement::TransactionElement(
	const IndexedElement::IdType& transactionNumber,
	const QDate& date,
	const IndexedElement::IdType& accountId,
	const QString& entry,
	const MajorType& major,
	const MinorType& minor,
	const QString& currency,
	const QString& status,
	Optional<QString> description
) :
	IndexedElement{transactionNumber},
	date_{date},
	accountId_{accountId},
	entry_{entry},
	major_{major},
	minor_{minor},
	currency_{currency},
	status_{status},
	description_{move(description)}
{}

const QLatin1String& TransactionElement::tag() const
{
	static const QLatin1String tag{"transaction"};
	return tag;
}

void TransactionElement::read(QXmlStreamReader& xml)
{
	qDebug() << "Reading Element:" << this->tag();

	if ( ! xml.isStartElement())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element is not the starting element.")
		};

	if (xml.name() != this->tag())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element tag is not valid under the current element.")
		};
	readIndexedAttributes(*this, xml);
	this->date_ = readAttributeDate(xml, TransactionElement::dateAttribute_);
	this->accountId_ = readAttributeUnsigned<IndexedElement::IdType>(
		xml,
		TransactionElement::accountIdAttribute_
	);
	this->entry_ = readAttributeString(xml, TransactionElement::entryAttribute_);
	this->major_ = readAttributeSigned<MajorType>(xml, TransactionElement::majorAttribute_);
	this->minor_ = readAttributeUnsigned<MinorType>(xml, TransactionElement::minorAttribute_);
	this->currency_ = readAttributeString(xml, TransactionElement::currencyAttribute_);
	this->status_ = readAttributeString(xml, TransactionElement::statusAttribute_);
	auto text{xml.readElementText()};

	if ( ! text.isEmpty())
		this->description_ = move(text);
}

void TransactionElement::write(QXmlStreamWriter& xml) const
{
	xml.writeStartElement(this->tag());
	writeIndexedAttributes(*this, xml);
	writeAttributeDate(xml, this->tag(), TransactionElement::dateAttribute_, this->date_);
	xml.writeAttribute(TransactionElement::accountIdAttribute_, QString::number(this->accountId_));
	xml.writeAttribute(TransactionElement::entryAttribute_, this->entry_);
	xml.writeAttribute(TransactionElement::majorAttribute_, QString::number(this->major_));
	xml.writeAttribute(TransactionElement::minorAttribute_, QString::number(this->minor_));
	xml.writeAttribute(TransactionElement::currencyAttribute_, this->currency_);
	xml.writeAttribute(TransactionElement::statusAttribute_, this->status_);

	if (this->description_.hasValue())
		xml.writeCharacters(*this->description_);
	xml.writeEndElement();
}

//}

//{ LedgerElement

LedgerElement::LedgerElement() :
	IndexedElement{}
{}

LedgerElement::LedgerElement(const IndexedElement::IdType& accountId) :
	IndexedElement{accountId}
{}

const QLatin1String& LedgerElement::tag() const
{
	static const QLatin1String tag{"ledger"};
	return tag;
}

void LedgerElement::read(QXmlStreamReader& xml)
{
	readIndexedAttributes(*this, xml);

	while (xml.readNextStartElement())
	{
		TransactionElement transaction{};

		if (xml.name() == transaction.tag())
		{
			transaction.read(xml);
			this->transactions_.emplace_back(move(transaction));
		}
		else
			break;
	};
}

void LedgerElement::write(QXmlStreamWriter& xml) const
{
	xml.writeStartElement(this->tag());
	writeIndexedAttributes(*this, xml);

	for (const auto& transaction : this->transactions_)
		transaction.write(xml);
	xml.writeEndElement();
}

//}

//{ LedgersElement

const QLatin1String LedgersElement::openingAccountIdAttribute_{"openingAccountId"};

LedgersElement::LedgersElement() :
	openingAccountId_{},
	ledgers_{}
{}

LedgersElement::LedgersElement(Optional<IndexedElement::IdType> openingAccountId) :
	openingAccountId_{move(openingAccountId)},
	ledgers_{}
{}

const QLatin1String& LedgersElement::tag() const
{
	static const QLatin1String tag{"ledgers"};
	return tag;
}

void LedgersElement::read(QXmlStreamReader& xml)
{
	qDebug() << "Reading Element:" << this->tag();

	if ( ! xml.isStartElement())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element is not the starting element.")
		};

	if (xml.name() != this->tag())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element tag is not valid under the current element.")
		};

	if (xml.attributes().hasAttribute(LedgersElement::openingAccountIdAttribute_))
		this->openingAccountId_ = readAttributeUnsigned<quint32>(
			xml,
			LedgersElement::openingAccountIdAttribute_
		);
	else
		qInfo() << "No attribute" << LedgersElement::openingAccountIdAttribute_ << "in"
				<< this->tag() << "tag element.";

	while (xml.readNextStartElement())
	{
		LedgerElement ledger{};
		if (xml.name() == ledger.tag())
		{
			ledger.read(xml);
			this->ledgers_.emplace_back(move(ledger));
		}
		else
			break;
	};
}

void LedgersElement::write(QXmlStreamWriter& xml) const
{
	qDebug() << "Writing Element:" << this->tag();
	xml.writeStartElement(this->tag());

	if (this->openingAccountId_.hasValue())
		xml.writeAttribute(
			LedgersElement::openingAccountIdAttribute_,
			QString::number(*this->openingAccountId_)
		);

	for (const auto& ledgerElement : this->ledgers_)
		ledgerElement.write(xml);
	xml.writeEndElement();
}

//}

//{ BankAccountElement

const QLatin1String BankAccountElement::accountIdAttribute_{"id"};
const QLatin1String BankAccountElement::typeAttribute_{"type"};

BankAccountElement::BankAccountElement() :
	BankAccountElement{IndexedElement::invalidId_, {}}
{}

BankAccountElement::BankAccountElement(
	const IndexedElement::IdType accountId,
	const QString& type
) :
	accountId_{accountId},
	type_{type}
{}

const QLatin1String& BankAccountElement::tag() const
{
	static const QLatin1String tag{"account"};
	return tag;
}

void BankAccountElement::read(QXmlStreamReader& xml)
{
	qDebug() << "Reading Element:" << this->tag();

	if ( ! xml.isStartElement())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element is not the starting element.")
		};

	if (xml.name() != this->tag())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element tag is not valid under the current element.")
		};
	this->accountId_ = readAttributeUnsigned<quint32>(xml, BankAccountElement::accountIdAttribute_);
	this->type_ = readAttributeString(xml, BankAccountElement::typeAttribute_);
	xml.skipCurrentElement(); // Finished reading element.
}

void BankAccountElement::write(QXmlStreamWriter& xml) const
{
	xml.writeStartElement(this->tag());
	xml.writeAttribute(BankAccountElement::accountIdAttribute_, QString::number(this->accountId_));
	xml.writeAttribute(BankAccountElement::typeAttribute_, this->type_);
	xml.writeEndElement();
}

//}

//{ BankElement

const QLatin1String BankElement::nameAttribute_{"name"};
const QLatin1String BankElement::isClosedAttribute_{"closed"};

BankElement::BankElement() :
	BankElement{{}, false, {}}
{}

BankElement::BankElement(
	const QString& name,
	const bool isClosed,
	vector<BankAccountElement> accounts
) :
	name_{name},
	isClosed_{isClosed},
	accounts_{move(accounts)}
{}

const QLatin1String& BankElement::tag() const
{
	static const QLatin1String tag{"bank"};
	return tag;
}

void BankElement::read(QXmlStreamReader& xml)
{
	qDebug() << "Reading Element:" << this->tag();

	if ( ! xml.isStartElement())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element is not the starting element.")
		};

	if (xml.name() != this->tag())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element tag is not valid under the current element.")
		};
	this->name_ = readAttributeString(xml, BankElement::nameAttribute_);
	this->isClosed_ = readAttributeBoolean(xml, BankElement::isClosedAttribute_);

	while (xml.readNextStartElement())
	{
		BankAccountElement account{};

		if (xml.name() == account.tag())
		{
			account.read(xml);
			this->accounts_.emplace_back(move(account));
		}
		else
			break;
	};
}

void BankElement::write(QXmlStreamWriter& xml) const
{
	xml.writeStartElement(this->tag());
	xml.writeAttribute(BankElement::nameAttribute_, this->name_);
	writeAttributeBoolean(xml, BankElement::isClosedAttribute_, this->isClosed_);

	for (const auto& account : this->accounts_)
		account.write(xml);
	xml.writeEndElement();
}

//}

//{ BanksElement

BanksElement::BanksElement() :
	BanksElement{{}}
{}

BanksElement::BanksElement(vector<BankElement> banks) :
	banks_{move(banks)}
{}

const QLatin1String& BanksElement::tag() const
{
	static const QLatin1String tag{"banks"};
	return tag;
}

void BanksElement::read(QXmlStreamReader& xml)
{
	qDebug() << "Reading Element:" << this->tag();

	if ( ! xml.isStartElement())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element is not the starting element.")
		};

	if (xml.name() != this->tag())
		throw BudgetFileError{
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			QObject::tr("The XML element tag is not valid under the current element.")
		};

	while (xml.readNextStartElement())
	{
		BankElement bank{};

		if (xml.name() == bank.tag())
		{
			bank.read(xml);
			this->banks_.emplace_back(move(bank));
		}
		else
			break;
	};
}

void BanksElement::write(QXmlStreamWriter& xml) const
{
	xml.writeStartElement(this->tag());

	for (const auto& bank : this->banks_)
		bank.write(xml);
	xml.writeEndElement();
}

//}
