/*******************************************************************************
***  This file is part of Pecunia.                                           ***
***                                                                          ***
***  Copyright (C) 2017, 2018, 2019, 2020, 2021, 2023                        ***
***  John Schneiderman <Licencing _AT_ Schneiderman _DOT_ me>                ***
***                                                                          ***
***  This program is free software: you can redistribute it and/or modify it ***
***  under the terms of the GNU Lesser 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 Lesser General Public License for more details.             ***
***                                                                          ***
***  You should have received a copy of the GNU Lesser General Public License***
***  along with this program. If not, see <http://www.gnu.org/licenses/>.    ***
*******************************************************************************/
#include "Money.h"
using namespace std::string_literals;
using std::int8_t;
using std::int16_t;
using std::int32_t;
using std::int64_t;
using std::pair;
using std::string;
using std::to_string;
using std::uint8_t;
using pecunia::currency::Iso4217Codes;
using pecunia::currency::Money;
using pecunia::currency::toStdString;
using pecunia::currency::toIso4217Code;
using pecunia::precision;
using pecunia::MinorUnit;
using pecunia::MinorSign;
using pecunia::FloatingPointBase;
using pecunia::FloatingPointRounderFunction;
using pecunia::RounderFunction;
using pecunia::UnitStorage;

#include <cassert>
// Used for assert.
#include <cstdlib>
// Used for lldiv.
#include <cmath>
using std::abs;
using std::pow;

#include <iomanip>
using std::fixed;
using std::setprecision;
#include <istream>
using std::istream;
#include <locale>
using std::locale;
using std::moneypunct;
using std::use_facet;
#include <limits>
using std::logic_error;
using std::numeric_limits;
#include <ostream>
using std::ostream;
#include <stdexcept>
using std::domain_error;
using std::exception;
using std::overflow_error;
using std::runtime_error;
using std::underflow_error;
#include <sstream>
using std::stringstream;

#include "FloatingPoint.h"
using pecunia::FloatingPoint;
#include <internal/Adjustments.h>
using pecunia::currency::internal::fromFloatingPointMajor;
using pecunia::currency::internal::fromFloatingPointMinor;
using pecunia::currency::internal::normaliseAmount;
#include <internal/Verification.h>
using pecunia::internal::VerifiedValue;
using pecunia::internal::verifyAdditionFits;
using pecunia::internal::verifyDivisionFits;
using pecunia::internal::verifyMultiplicationFits;
using pecunia::internal::verifySubtractionFits;
#include "MoneyManip.h"
using pecunia::currency::countryLeadingIndex;
#include <MoneySize.hpp> // cppcheck-suppress missingInclude
using pecunia::internal::extraFloatingPointPrecision;


UnitStorage Money::maximumMajorValue() const
{
	UnitStorage rawMaxMajor{
		static_cast<UnitStorage>(
			numeric_limits<UnitStorage>::max() - this->maximumMinorValue()
		)
	};
	const int32_t ratio{minorUnitPrecisionFactor(this->iso4217Code_)};
	UnitStorage maxMajor{static_cast<UnitStorage>(rawMaxMajor / ratio)};
	return maxMajor;
}

UnitStorage Money::minimumMajorValue() const
{
	const auto maxMinorValue{this->maximumMinorValue()};
	UnitStorage rawMinMajor{
		static_cast<UnitStorage>(numeric_limits<UnitStorage>::lowest() + maxMinorValue)
	};
	const auto minorPrecisionRatio{minorUnitPrecisionFactor(this->iso4217Code_)};
	UnitStorage minMajor{static_cast<UnitStorage>(rawMinMajor / minorPrecisionRatio)};
	return minMajor;
}

MinorUnit Money::maximumMinorValue() const
{
	const int32_t digits{minorUnitDigits(this->iso4217Code_)};
	const MinorUnit max{static_cast<MinorUnit>(pow(10, digits + precision) - 1.0)};
	return max;
}

UnitStorage Money::maximumAmountValue() const
{
	const int32_t unitRatio{minorUnitPrecisionFactor(this->iso4217Code_)};
	const UnitStorage max{
		static_cast<UnitStorage>(
			(this->maximumMajorValue() * unitRatio) + this->maximumMinorValue()
		)
	};
	return max;
}

UnitStorage Money::minimumAmountValue() const
{
	const int32_t unitRatio{minorUnitPrecisionFactor(this->iso4217Code_)};
	const auto minMajorValue{this->minimumMajorValue()};
	const auto maxMinorValue{this->maximumMinorValue()};
	const UnitStorage min{
		static_cast<UnitStorage>((minMajorValue * unitRatio) - maxMinorValue)
	};
	return min;
}

Money::Money() :
	Money{Iso4217Codes::XXX}
{}

Money::Money(const Iso4217Codes& code) :
	Money{0, 0, code}
{}

Money::Money(const MinorSign& sign, const MinorUnit& minor, const Iso4217Codes& code) :
	Money{0, minor, code}
{
	assert(
		(minor > 0 || (minor == 0 && sign == MinorSign::Neither))
		&& "The value of zero has no sign."
	);

	if (sign == MinorSign::Negative)
		this->amount_ *= -1;
}

Money::Money(const UnitStorage& major, const MinorUnit& minor, const Iso4217Codes& code) :
	amount_{0},
	iso4217Code_{code}
{
	assert(
		pow(10, minorUnitDigits(code) + precision) - 1.0 <= numeric_limits<MinorUnit>::max()
		&& "The number of digits with precision is too large and cannot fit inside the minor type."
	);
	const int32_t unitRatio{minorUnitPrecisionFactor(code)};

	if (major > this->maximumMajorValue())
		throw overflow_error{
			"The major money value, " + std::to_string(major)
				+ ", is too large to store within the currency."
		};

	if (major < this->minimumMajorValue())
		throw overflow_error{
			"The major money value, " + std::to_string(major)
				+ ", is too small to store within the currency."
		};

	if (minor > this->maximumMinorValue())
		throw overflow_error{
			"The minor money value, " + std::to_string(minor)
				+ ", is too large to store within the currency."
		};

	if (major >= 0)
		this->amount_ = (major * unitRatio) + minor;
	else
		this->amount_ = (major * unitRatio) - minor;
}

Money::Money(const FloatingPointBase& value, const Iso4217Codes& code) :
	Money{
		value,
		code,
		[] (const FloatingPoint& number, const uint8_t&)
		{
			return number;
		}
	}
{}

Money::Money(
	const FloatingPointBase& value,
	const Iso4217Codes& code,
	const FloatingPointRounderFunction& rounder
) :
	Money{
		fromFloatingPointMajor(value, code, precision, rounder),
		fromFloatingPointMinor(value, code, precision, rounder),
		code
	}
{
	if (
		this->amount_ == 0
		&& ! FloatingPoint{value}.isEqual(
			0.0,
			static_cast<uint8_t>(minorUnitDigits(code) + precision + extraFloatingPointPrecision)
		)
	)
		throw underflow_error{
			"The value " + std::to_string(value)
				+ " is too small to store within the supplied precision."
		};
}

Money::operator FloatingPointBase() const
{
	return this->toFloatingPoint();
}

Money Money::operator-() const
{
	Money m{*this};
	m.amount_ *= -1;
	return m;
}

Money Money::operator+(const Money& other) const
{
	Money m{*this};
	m += other;
	return m;
}

Money Money::operator-(const Money& other) const
{
	Money m{*this};
	m -= other;
	return m;
}

Money Money::operator*(const int8_t value) const
{
	Money m{*this};
	m *= static_cast<int64_t>(value);
	return m;
}

Money Money::operator*(const int16_t value) const
{
	Money m{*this};
	m *= static_cast<int64_t>(value);
	return m;
}

Money Money::operator*(const int32_t value) const
{
	Money m{*this};
	m *= static_cast<int64_t>(value);
	return m;
}

Money Money::operator*(const int64_t value) const
{
	Money m{*this};
	m *= value;
	return m;
}

Money Money::operator*(const FloatingPointBase& value) const
{
	Money m{*this};
	m *= value;
	return m;
}

FloatingPointBase Money::operator/(const Money& other) const
{
	if (other.amount_ == 0)
		throw domain_error{
			"The division operation of " + std::to_string(this->amount_) + " and "
				+ std::to_string(other.amount_) + " result is undefined."
		};
	const FloatingPoint normalisedAmount{normaliseAmount(other, this->iso4217Code_)};
	return (FloatingPoint{this->amount_} / normalisedAmount).underlying();
}

Money Money::operator/(const int8_t value) const
{
	Money m{*this};
	m /= static_cast<int64_t>(value);
	return m;
}

Money Money::operator/(const int16_t value) const
{
	Money m{*this};
	m /= static_cast<int64_t>(value);
	return m;
}

Money Money::operator/(const int32_t value) const
{
	Money m{*this};
	m /= static_cast<int64_t>(value);
	return m;
}

Money Money::operator/(const int64_t value) const
{
	Money m{*this};
	m /= value;
	return m;
}

Money Money::operator/(const FloatingPointBase& value) const
{
	Money m{*this};
	m /= value;
	return m;
}

UnitStorage Money::operator%(const UnitStorage& value) const
{
	if (value == 0)
		throw domain_error{
			"The modulus operation of " + std::to_string(this->amount_) + " and "
			+ std::to_string(value) + " result is undefined."
		};
	return this->major() % value;
}

Money& Money::operator=(const Money& other)
{
	if (this != &other)
	{
		const UnitStorage normalisedAmount{normaliseAmount(other, this->iso4217Code_)};
		const auto maximumAmountValue{this->maximumAmountValue()};
		const auto minimumAmountValue{this->minimumAmountValue()};

		if (normalisedAmount <= maximumAmountValue && normalisedAmount >= minimumAmountValue)
			this->amount_ = normalisedAmount;
		else
			throw overflow_error{
				"The assignment operation of " + std::to_string(other.amount_) + " to "
					+ std::to_string(this->amount_) + " is too large to fit within the precision."
			};
	}
	return *this;
}

Money& Money::operator=(Money&& other)
{
	if (this != &other)
	{
		const UnitStorage normalisedAmount{normaliseAmount(other, this->iso4217Code_)};
		const auto maximumAmountValue{this->maximumAmountValue()};
		const auto minimumAmountValue{this->minimumAmountValue()};

		if (normalisedAmount <= maximumAmountValue && normalisedAmount >= minimumAmountValue)
			this->amount_ = normalisedAmount;
		else
			throw overflow_error{
				"The assignment operation of " + std::to_string(other.amount_) + " to "
					+ std::to_string(this->amount_) + " is too large to fit within the precision."
			};
	}
	return *this;
}

bool Money::operator<(const Money& other) const
{
	try
	{
		const UnitStorage normalisedAmount{normaliseAmount(other, this->iso4217Code_)};
		return this->amount_ < normalisedAmount;
	}
	catch (const logic_error&)
	{
		throw;
	}
	catch (const exception&)
	{
		if (other.amount_ > 0) // other.amount_ is too large positive.
			return true;

		if (other.amount_ < 0) // other.amount_ is too large negative.
			return false;
		throw; // Something strange has occurred.
	}
}

bool Money::operator<(const int8_t value) const
{
	return this->amount_ < static_cast<int64_t>(value);
}

bool Money::operator<(const int16_t value) const
{
	return this->amount_ < static_cast<int64_t>(value);
}

bool Money::operator<(const int32_t value) const
{
	return this->amount_ < static_cast<int64_t>(value);
}

bool Money::operator<(const int64_t value) const
{
	return this->amount_ < value;
}

bool Money::operator<=(const Money& other) const
{
	return (*this < other) || (*this == other);
}

bool Money::operator<=(const int8_t value) const
{
	return this->amount_ <= static_cast<int64_t>(value);
}

bool Money::operator<=(const int16_t value) const
{
	return this->amount_ <= static_cast<int64_t>(value);
}

bool Money::operator<=(const int32_t value) const
{
	return this->amount_ <= static_cast<int64_t>(value);
}

bool Money::operator<=(const int64_t value) const
{
	return this->amount_ <= value;
}

bool Money::operator>(const Money& other) const
{
	try
	{
		const UnitStorage normalisedAmount{normaliseAmount(other, this->iso4217Code_)};
		return this->amount_ > normalisedAmount;
	}
	catch (const logic_error&)
	{
		throw;
	}
	catch (const exception&)
	{
		if (other.amount_ > 0) // other.amount_ is too large positive.
			return false;

		if (other.amount_ < 0) // other.amount_ is too large negative.
			return true;
		throw; // Something strange has occurred.
	}
}

bool Money::operator>(const int8_t value) const
{
	return this->amount_ > static_cast<int8_t>(value);
}

bool Money::operator>(const int16_t value) const
{
	return this->amount_ > static_cast<int64_t>(value);
}

bool Money::operator>(const int32_t value) const
{
	return this->amount_ > static_cast<int64_t>(value);
}

bool Money::operator>(const int64_t value) const
{
	return this->amount_ > value;
}

bool Money::operator>=(const Money& other) const
{
	return (*this > other) || (*this == other);
}

bool Money::operator>=(const int8_t value) const
{
	return this->amount_ >= static_cast<int64_t>(value);
}

bool Money::operator>=(const int16_t value) const
{
	return this->amount_ >= static_cast<int64_t>(value);
}

bool Money::operator>=(const int32_t value) const
{
	return this->amount_ >= static_cast<int64_t>(value);
}

bool Money::operator>=(const int64_t value) const
{
	return this->amount_ >= value;
}

bool Money::operator==(const Money& other) const
{
	try
	{
		const UnitStorage normalisedAmount{normaliseAmount(other, this->iso4217Code_)};
		return this->amount_ == normalisedAmount;
	}
	catch (const exception&)
	{
		// Doesn't matter why we threw, e.g. overflow, underflow, different codes, the error itself
		// is enough to know that the two values are not equal.
		return false;
	}
}

bool Money::operator==(const int8_t value) const
{
	return this->amount_ == static_cast<int8_t>(value);
}

bool Money::operator==(const int16_t value) const
{
	return this->amount_ == static_cast<int16_t>(value);
}

bool Money::operator==(const int32_t value) const
{
	return this->amount_ == static_cast<int64_t>(value);
}

bool Money::operator==(const int64_t value) const
{
	return this->amount_ == value;
}

bool Money::operator!=(const Money& other) const
{
	return ! (*this == other);
}

bool Money::operator!=(const int8_t other) const
{
	return ! (*this == static_cast<int64_t>(other));
}

bool Money::operator!=(const int16_t other) const
{
	return ! (*this == static_cast<int64_t>(other));
}

bool Money::operator!=(const int32_t other) const
{
	return ! (*this == static_cast<int64_t>(other));
}

bool Money::operator!=(const int64_t other) const
{
	return ! (*this == other);
}

Money& Money::operator+=(const Money& other)
{
	const UnitStorage normalisedAmount{normaliseAmount(other, this->iso4217Code_)};
	const VerifiedValue verified{
		verifyAdditionFits(
			this->amount_,
			normalisedAmount,
			this->maximumAmountValue(),
			this->minimumAmountValue()
		)
	};
	switch (verified)
	{
	case VerifiedValue::Fits:
		this->amount_ += normalisedAmount;
		return *this;
	case VerifiedValue::Overflows:
		throw overflow_error{
			"The addition operation of " + std::to_string(this->amount_) + ' '
				+ toStdString(this->iso4217Code_) + " and " + std::to_string(other.amount_) + ' '
				+ toStdString(other.iso4217Code_) + " cannot be safely stored."
		};
	case VerifiedValue::Underflows:
		throw underflow_error{
			"The addition operation of " + std::to_string(this->amount_) + ' '
				+ toStdString(this->iso4217Code_) + " and " + std::to_string(other.amount_) + ' '
				+ toStdString(other.iso4217Code_) + " cannot be safely stored."
		};
	case VerifiedValue::Undefined:
	default:
		throw logic_error{"Incorrect addition verification result."};
	};
}

Money& Money::operator-=(const Money& other)
{
	const UnitStorage normalisedAmount{normaliseAmount(other, this->iso4217Code_)};
	const VerifiedValue verified{
		verifySubtractionFits(
			this->amount_,
			normalisedAmount,
			this->maximumAmountValue(),
			this->minimumAmountValue()
		)
	};
	switch (verified)
	{
	case VerifiedValue::Fits:
		this->amount_ -= normalisedAmount;
		return *this;
	case VerifiedValue::Overflows:
		throw overflow_error{
			"The subtraction operation of " + std::to_string(this->amount_) + ' '
				+ toStdString(this->iso4217Code_) + " and " + std::to_string(other.amount_) + ' '
				+ toStdString(other.iso4217Code_) + " cannot be safely stored."
		};
	case VerifiedValue::Underflows:
		throw underflow_error{
			"The subtraction operation of " + std::to_string(this->amount_) + ' '
				+ toStdString(this->iso4217Code_) + " and " + std::to_string(other.amount_) + ' '
				+ toStdString(other.iso4217Code_) + " cannot be safely stored."
		};
	case VerifiedValue::Undefined:
	default:
		throw logic_error{"Incorrect subtraction verification result."};
	};
}

Money& Money::operator*=(const int8_t value)
{
	return *this *= static_cast<int64_t>(value);
}

Money& Money::operator*=(const int16_t value)
{
	return *this *= static_cast<int64_t>(value);
}

Money& Money::operator*=(const int32_t value)
{
	return *this *= static_cast<int64_t>(value);
}

Money& Money::operator*=(const int64_t value)
{
	const VerifiedValue verified{
		verifyMultiplicationFits(
			this->amount_,
			value,
			this->maximumAmountValue(),
			this->minimumAmountValue()
		)
	};

	switch (verified)
	{
	case VerifiedValue::Fits:
		this->amount_ = this->amount_ * value;
		return *this;
	case VerifiedValue::Overflows:
		throw overflow_error{
			"The multiplication operation of " + std::to_string(this->amount_) + " and "
			+ std::to_string(value) + " cannot be safely stored."
		};
	case VerifiedValue::Underflows:
		throw underflow_error{
			"The multiplication operation of " + std::to_string(this->amount_) + " and "
			+ std::to_string(value) + " cannot be safely stored."
		};
	case VerifiedValue::Undefined:
	default:
		throw logic_error{"Incorrect multiplication verification result."};
	};
}

Money& Money::operator*=(const FloatingPointBase& value)
{
	// Need to verify before adjustment and during the multiplication.
	const VerifiedValue verified{
		verifyMultiplicationFits(
			this->amount_,
			value,
			this->maximumAmountValue(),
			this->minimumAmountValue(),
			this->maximumMinorValue(),
			static_cast<uint8_t>(
				minorUnitDigits(this->iso4217Code_) + precision + extraFloatingPointPrecision
			)
		)
	};

	switch (verified)
	{
	case VerifiedValue::Fits:
		break;
	case VerifiedValue::Overflows:
		throw overflow_error{
			"The multiplication operation of " + std::to_string(this->amount_) + " and "
			+ std::to_string(value) + " cannot be safely stored."
		};
	case VerifiedValue::Underflows:
		throw underflow_error{
			"The multiplication operation of " + std::to_string(this->amount_) + " and "
			+ std::to_string(value) + " cannot be safely stored."
		};
	case VerifiedValue::Undefined:
	default:
		throw logic_error{"Incorrect multiplication verification result."};
	};
	const int32_t unitRatio{
		minorUnitPrecisionFactor(this->iso4217Code_)
			* static_cast<int32_t>(pow(10, extraFloatingPointPrecision))
	};
	const auto adjustedValue{FloatingPoint{value} * FloatingPoint{unitRatio}};
	*this *= static_cast<int64_t>(adjustedValue.underlying());
	this->amount_ /= unitRatio;
	return *this;
}

Money& Money::operator/=(const int8_t value)
{
	return *this /= static_cast<int64_t>(value);
}

Money& Money::operator/=(const int16_t value)
{
	return *this /= static_cast<int64_t>(value);
}

Money& Money::operator/=(const int32_t value)
{
	return *this /= static_cast<int64_t>(value);
}

Money& Money::operator/=(const int64_t value)
{
	const VerifiedValue verified{
		verifyDivisionFits(
			this->amount_,
			value,
			static_cast<uint8_t>(minorUnitDigits(this->iso4217Code_) + precision)
		)
	};

	switch (verified)
	{
	case VerifiedValue::Fits:
		this->amount_ = this->amount_ / value;
		return *this;
	case VerifiedValue::Overflows:
		throw overflow_error{
			"The division operation of " + std::to_string(this->amount_) + " and "
				+ std::to_string(value) + " cannot be safely stored."
		};
	case VerifiedValue::Undefined:
		throw domain_error{
			"The division operation of " + std::to_string(this->amount_) + " and "
				+ std::to_string(value) + " result is undefined."
		};
	case VerifiedValue::Underflows:
		throw underflow_error{
			"The division operation of " + std::to_string(this->amount_) + " and "
				+ std::to_string(value) + " cannot be safely stored."
		};
	default:
		throw logic_error{"Incorrect division verification result."};
	};
}

Money& Money::operator/=(const FloatingPointBase& value)
{
	// Need to verify before adjustment, during the multiplication, and division.
	const VerifiedValue preVerified{
		verifyDivisionFits(
			this->amount_,
			value,
			this->maximumAmountValue(),
			this->minimumAmountValue(),
			this->maximumMinorValue(),
			static_cast<uint8_t>(
				minorUnitDigits(this->iso4217Code_) + precision + extraFloatingPointPrecision
			)
		)
	};

	switch (preVerified)
	{
	case VerifiedValue::Fits:
		break;
	case VerifiedValue::Overflows:
		throw overflow_error{
			"The division operation of " + std::to_string(this->amount_) + " and "
				+ std::to_string(value) + " cannot be safely stored."
		};
	case VerifiedValue::Undefined:
		throw domain_error{
			"The division operation of " + std::to_string(this->amount_) + " and "
				+ std::to_string(value) + " result is undefined."
		};
	case VerifiedValue::Underflows:
		throw underflow_error{
			"The division operation of " + std::to_string(this->amount_) + " and "
				+ std::to_string(value) + " cannot be safely stored."
		};
	default:
		throw logic_error{"Incorrect division verification result."};
	};
	const int64_t unitRatio{
		minorUnitPrecisionFactor(this->iso4217Code_)
			* static_cast<int64_t>(pow(10, extraFloatingPointPrecision))
	};
	const VerifiedValue verifiedMultiplication{
		verifyMultiplicationFits(
			this->amount_,
			unitRatio,
			this->maximumAmountValue(),
			this->minimumAmountValue()
		)
	};

	switch (verifiedMultiplication)
	{
	case VerifiedValue::Fits:
		break;
	case VerifiedValue::Overflows:
		throw overflow_error{
			"The multiplication operation of " + std::to_string(this->amount_) + " and "
				+ std::to_string(value) + " cannot be safely stored during division."
		};
	case VerifiedValue::Underflows:
		throw underflow_error{
			"The multiplication operation of " + std::to_string(this->amount_) + " and "
				+ std::to_string(value) + " cannot be safely stored during division."
		};
	case VerifiedValue::Undefined:
	default:
		throw logic_error{"Incorrect multiplication verification result during division."};
	};
	const auto adjustedValue{
		static_cast<UnitStorage>(
			(FloatingPoint{value} * FloatingPoint{unitRatio}).underlying()
		)
	};
	const auto adjustedAmount{this->amount_ * unitRatio};
	const auto preAmount{this->amount_};

	try
	{
		this->amount_ = adjustedAmount;
		*this /= adjustedValue;
		return *this;
	}
	catch (const exception&)
	{
		this->amount_ = preAmount;
		throw;
	}
}

const UnitStorage& Money::amount() const noexcept
{
	return this->amount_;
}

UnitStorage Money::major() const
{
	return this->amount_ / minorUnitPrecisionFactor(this->iso4217Code_);
}

MinorUnit Money::minor() const
{
	return static_cast<MinorUnit>(
		abs(this->amount_ % minorUnitPrecisionFactor(this->iso4217Code_))
	);
}

const Iso4217Codes& Money::code() const
{
	return this->iso4217Code_;
}

bool Money::isPositive() const // cppcheck-suppress unusedFunction
{
	return this->amount_ > 0;
}

FloatingPointBase Money::toFloatingPoint(const RounderFunction& rounder, const uint8_t digits) const
{
	assert(rounder != nullptr && "The money rounder must be a function.");
	const auto minorDigits{
		static_cast<uint8_t>(minorUnitDigits(this->iso4217Code_) + precision)
	};
	const FloatingPoint rounded{rounder(this->amount_, minorDigits, digits)};
	const FloatingPoint ratio{minorUnitPrecisionFactor(this->iso4217Code_)};
	const FloatingPoint converted{rounded / ratio};
	return converted.underlying();
}

Money& Money::round(const RounderFunction& rounder, const uint8_t digits)
{
	assert(rounder != nullptr && "The money rounder must be a function.");
	const auto minorDigits{
		static_cast<uint8_t>(minorUnitDigits(this->iso4217Code_) + precision)
	};
	const auto roundedAmount{rounder(this->amount_, minorDigits, digits)};
	const auto maximumAmountValue{this->maximumAmountValue()};
	const auto minimumAmountValue{this->minimumAmountValue()};

	if (roundedAmount <= maximumAmountValue && roundedAmount >= minimumAmountValue)
		this->amount_ = roundedAmount;
	else
		throw overflow_error{
			"The result of the rounding operation " + std::to_string(roundedAmount) + " from "
				+ std::to_string(this->amount_) + " is too large to fit within the precision."
		};
	return *this;
}

bool Money::hasAmount() const // cppcheck-suppress unusedFunction
{
	return this->amount_ != 0 && this->iso4217Code_ != Iso4217Codes::XXX;
}

void Money::setCode(const pecunia::currency::Iso4217Codes& code)
{
	this->iso4217Code_ = code;
}

pair<UnitStorage, MinorUnit> Money::maximum(const Iso4217Codes& code)
{
	const auto ratio{minorUnitPrecisionFactor(code)};
	const Money test{code};
	const auto value{test.maximumAmountValue()};
	const UnitStorage max{value / ratio};
	const auto minorValue{static_cast<MinorUnit>(value % ratio)};
	return {max, minorValue};
}

pair<UnitStorage, MinorUnit> Money::minimum(const Iso4217Codes& code)
{
	const auto ratio{minorUnitPrecisionFactor(code)};
	const Money test{code};
	const auto value{test.minimumAmountValue()};
	const UnitStorage min{value / ratio};
	const auto minorValue{static_cast<MinorUnit>(-value % ratio)};
	return {min, minorValue};
}

void Money::zeroOut() // cppcheck-suppress unusedFunction
{
	this->amount_ = 0;
}

Money pecunia::currency::operator*(const int8_t lhs, const Money& rhs)
{
	return rhs * lhs;
}

Money pecunia::currency::operator*(const int16_t lhs, const Money& rhs)
{
	return rhs * lhs;
}

Money pecunia::currency::operator*(const int32_t lhs, const Money& rhs)
{
	return rhs * lhs;
}

Money pecunia::currency::operator*(const int64_t lhs, const Money& rhs)
{
	return rhs * lhs;
}

Money pecunia::currency::operator*(const FloatingPointBase& lhs, const Money& rhs)
{
	return rhs * lhs;
}

ostream& pecunia::currency::operator<<(ostream& stream, const Money& m)
{
	const bool isSymbolUsed{stream.iword(pecunia::currency::useSymbolIndex()) == 1};
	const string currencyMarker{
		isSymbolUsed ? currencySymbol(m.iso4217Code_) : toStdString(m.iso4217Code_)
	};
	stream.exceptions(istream::failbit | istream::badbit);
	const bool isCountryLeading{stream.iword(countryLeadingIndex()) == 1};
	const bool isSpaced{stream.iword(pecunia::currency::spacedIndex()) == 1};
	stream << (isCountryLeading ? currencyMarker : "")
			<< (isSpaced && isCountryLeading ? " " : "")
			<< m.major()
			<< use_facet<moneypunct<char>>(stream.getloc()).decimal_point()
			<< std::setw(minorUnitDigits(m.iso4217Code_) + precision) << std::setfill('0')
			<< m.minor()
			<< (isSpaced && ! isCountryLeading ? " " : "")
			<< ( ! isCountryLeading ? currencyMarker : "");
	return stream;
}

istream& pecunia::currency::operator>>(istream& stream, Money& m)
{
	UnitStorage major;
	MinorUnit minor;
	char separator;
	string rawCode;
	stream.exceptions(istream::failbit | istream::badbit);
	const bool isCountryLeading{stream.iword(countryLeadingIndex()) == 1};

	if (isCountryLeading)
		stream >> rawCode >> major >> separator >> minor ;
	else
		stream >> major >> separator >> minor >> rawCode;

	if (separator != use_facet<moneypunct<char>>(stream.getloc()).decimal_point())
		throw runtime_error{"The currency is incorrectly formatted."};
	m.iso4217Code_ = toIso4217Code(rawCode);
	m = Money{major, minor, m.iso4217Code_};
	return stream;
}

string pecunia::currency::to_string(const Money& m)
{
	stringstream ss;
	ss << pecunia::currency::spaced << pecunia::currency::trail << m;
	return ss.str();
}

void pecunia::currency::swap(Money& lhs, Money& rhs) noexcept // cppcheck-suppress unusedFunction
{
	const Money temp{lhs};
	lhs.amount_ = rhs.amount_;
	lhs.iso4217Code_ = rhs.iso4217Code_;
	rhs.amount_ = temp.amount_;
	rhs.iso4217Code_ = temp.iso4217Code_;
}
