/*******************************************************************************
***  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/>.    ***
*******************************************************************************/
#ifndef DRN_FILE_STORAGE_INTERNAL_XMLATTRIBUTE_H_
#define DRN_FILE_STORAGE_INTERNAL_XMLATTRIBUTE_H_

#include <QObject>
#include <QString>
#include <QXmlStreamReader>

#include <limits>
#include <type_traits>

#include <file-storage/FileStorageErrors.h>
#include <foundation/Optional.hpp>


class QXmlStreamWriter;

namespace drn
{
namespace file_storage
{
namespace internal
{

template<typename ExtractedType>
ExtractedType readAttributeSigned(QXmlStreamReader& xml, const QLatin1String& attribute);
template<typename ExtractedType>
ExtractedType readAttributeUnsigned(QXmlStreamReader& xml, const QLatin1String& attribute);
QString readAttributeString(QXmlStreamReader& xml, const QLatin1String& attribute);
template<typename ExtractedType>
ExtractedType readAttributeFloatingPoint(QXmlStreamReader& xml, const QLatin1String& attribute);
void writeAttributeBoolean(QXmlStreamWriter& xml, const QLatin1String& attribute, const bool value);
bool readAttributeBoolean(QXmlStreamReader& xml, const QLatin1String& attribute);
QDate readAttributeDate(QXmlStreamReader& xml, const QLatin1String& attribute);
void writeAttributeDate(
	QXmlStreamWriter& xml,
	const QLatin1String& writeTag,
	const QLatin1String& attribute,
	const QDate& date
);
template<typename ExtractedType>
foundation::Optional<ExtractedType> readOptionalAttributeUnsigned(
	QXmlStreamReader& xml,
	const QLatin1String& attribute
);

}}}

template<typename ExtractedType>
ExtractedType drn::file_storage::internal::readAttributeSigned(
	QXmlStreamReader& xml,
	const QLatin1String& attribute
)
{
	static_assert(
		std::is_signed<ExtractedType>::value,
		"The attribute extraction type must be signed."
	);
	bool isParsed{false};
	const auto value{xml.attributes().value(attribute).toLongLong(&isParsed)};

	if ( ! isParsed)
		throw AttributeError{
			xml.name().toString(),
			attribute,
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			::QObject::tr("The attribute value must be a number.")
		};

	if (value > std::numeric_limits<ExtractedType>::max())
		throw AttributeError{
			xml.name().toString(),
			attribute,
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			::QObject::tr("The attribute value is too large to store in the destination object.")
		};

	if (value < std::numeric_limits<ExtractedType>::min())
		throw AttributeError{
			xml.name().toString(),
			attribute,
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			::QObject::tr("The attribute value is too small to store in the destination object.")
		};
	return static_cast<ExtractedType>(value);
}

template<typename ExtractedType>
ExtractedType drn::file_storage::internal::readAttributeUnsigned(
	QXmlStreamReader& xml,
	const QLatin1String& attribute
)
{
	static_assert(
		std::is_unsigned<ExtractedType>::value,
		"The attribute extraction type must be unsigned."
	);
	bool isParsed{false};
	const auto value{xml.attributes().value(attribute).toULongLong(&isParsed)};

	if ( ! isParsed)
		throw AttributeError{
			xml.name().toString(),
			attribute,
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			::QObject::tr("The attribute value must be a number.")
		};

	if (value > std::numeric_limits<ExtractedType>::max())
		throw AttributeError{
			xml.name().toString(),
			attribute,
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			::QObject::tr("The attribute value is too large to store in the destination object.")
		};
	return static_cast<ExtractedType>(value);
}

template<typename ExtractedType>
ExtractedType drn::file_storage::internal::readAttributeFloatingPoint(
	QXmlStreamReader& xml,
	const QLatin1String& attribute
)
{
	static_assert(
		std::is_floating_point<ExtractedType>::value,
		"The attribute extraction type must be a floating point type."
	);
	bool isParsed{false};
	const auto value{xml.attributes().value(attribute).toDouble(&isParsed)};

	if ( ! isParsed)
		throw AttributeError{
			xml.name().toString(),
			attribute,
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			::QObject::tr("The attribute value must be a number.")
		};

	if (value > std::numeric_limits<ExtractedType>::max())
		throw AttributeError{
			xml.name().toString(),
			attribute,
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			::QObject::tr("The attribute value is too large to store in the destination object.")
		};

	if (value < std::numeric_limits<ExtractedType>::lowest())
		throw AttributeError{
			xml.name().toString(),
			attribute,
			xml.errorString(),
			xml.lineNumber(),
			xml.columnNumber(),
			::QObject::tr("The attribute value is too low to store in the destination object.")
		};
	return static_cast<ExtractedType>(value);
}

template<typename ExtractedType>
drn::foundation::Optional<ExtractedType> drn::file_storage::internal::readOptionalAttributeUnsigned(
	QXmlStreamReader& xml,
	const QLatin1String& attribute
)
{
	static_assert(
		std::is_unsigned<ExtractedType>::value,
		"The optional attribute extraction type must be unsigned."
	);

	if ( ! xml.attributes().hasAttribute(attribute))
		return {};
	return readAttributeUnsigned<ExtractedType>(xml, attribute);
}

#endif
