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

#include <cstdint>
#include <functional>
#include <iosfwd>
#include <string>
#include <utility>

#include "Codes.h"
#include "MoneyTypes.h"


namespace pecunia
{

class FloatingPoint;

namespace currency
{

class Money;

}
namespace math
{

PECUNIA_EXPORT currency::Money absoluteValue(const currency::Money& money);

}
namespace currency
{
namespace tests
{

struct MoneyUT;

}

/**
 * @breif A representation of a country's currency supporting basic arithmetic usage and
 * guaranteeing all operations succeed without a loss of precision, overflow, or underflow.
 */
class PECUNIA_EXPORT Money
{
	friend struct pecunia::currency::tests::MoneyUT;
	friend PECUNIA_EXPORT Money math::absoluteValue(const Money&);
	friend PECUNIA_EXPORT Money operator*(const std::int8_t lhs, const Money& rhs);
	friend PECUNIA_EXPORT Money operator*(const std::int16_t lhs, const Money& rhs);
	friend PECUNIA_EXPORT Money operator*(const std::int32_t lhs, const Money& rhs);
	friend PECUNIA_EXPORT Money operator*(const std::int64_t lhs, const Money& rhs);
	friend PECUNIA_EXPORT Money operator*(const FloatingPointBase& lhs, const Money& rhs);
	friend PECUNIA_EXPORT std::ostream& operator<<(std::ostream& stream, const Money& m);
	friend PECUNIA_EXPORT std::istream& operator>>(std::istream& stream, Money& m);
	friend PECUNIA_EXPORT void swap(Money& lhs, Money& rhs) noexcept;

	/**
	 * @breif Calculates the largest storable monetary major value.
	 */
	UnitStorage maximumMajorValue() const;
	/**
	 * @breif Calculates the smallest storable monetary major value.
	 */
	UnitStorage minimumMajorValue() const;
	/**
	 * @breif Calculates the largest storable monetary minor value.
	 */
	MinorUnit maximumMinorValue() const;
	/**
	 * @breif Calculates the largest storable monetary amount value.
	 */
	UnitStorage maximumAmountValue() const;
	/**
	 * @breif Calculates the smallest storable monetary amount value.
	 */
	UnitStorage minimumAmountValue() const;

	/// The entire representation of the monetary amount using the currency's minor unit, e.g. cents
	/// for USD, plus any extra digits of precision.
	UnitStorage amount_;
	/// The ISO 4217 currency code the monetary amount represents.
	Iso4217Codes iso4217Code_;

public:
	/**
	 * @breif Constructor for a fully formed object that initialises the stored amount to zero
	 * unknown currency.
	 */
	Money();
	/**
	 * @breif  Constructor for a fully formed object that initialises the stored amount to zero.
	 *
	 * @param[in] code The ISO 4217 currency code the amount represents.
	 */
	explicit Money(const Iso4217Codes& code);
	/**
	 * @breif Constructor for setting an exact minor monetary value whose major value is zero. The
	 * minor value is expected to be adjusted for the level of precision the object is expecting,
	 * e.g. U.S.A. dollars with a precision of two would enter twelve cents as 1200.
	 *
	 * @pre When the minor value is zero, the sign must be Sign::Neither.
	 *
	 * @exception overflow_error When the minor unit is too large to be stored.
	 *
	 * @param[in] minor The amount of minor currency to store, e.g. U.S.A. cents.
	 * @param[in] sign The associated signedness of the minor value, e.g. negative.
	 * @param[in] code The ISO 4217 currency code the amount represents.
	 */
	Money(
		const MinorSign& sign,
		const MinorUnit& minor,
		const Iso4217Codes& code = Iso4217Codes::XXX
	);
	/**
	 * @breif Constructor for setting an exact monetary value of a fully formed object. The minor
	 * value is expected to be adjusted for the level of precision the object is expecting,
	 * e.g. U.S.A. dollars with a precision of two would enter twelve cents as 1200.
	 *
	 * @pre The maximum minor value must fit within the MinorUnit.
	 *
	 * @exception overflow_error When the major unit is too large to be stored in a positive
	 * direction.
	 * @exception overflow_error When the major unit is too small to be stored in a negative
	 * direction.
	 * @exception overflow_error When the minor unit is too large to be stored.
	 *
	 * @param[in] major The amount of major currency to store, e.g. U.S.A. dollars.
	 * @param[in] minor The amount of minor currency to store, e.g. U.S.A. cents.
	 * @param[in] code The ISO 4217 currency code the amount represents.
	 */
	Money(
		const UnitStorage& major,
		const MinorUnit& minor,
		const Iso4217Codes& code = Iso4217Codes::XXX
	);
	/**
	 * @breif Constructor for setting a monetary value using the native floating-point type without
	 * any rounding performed.
	 *
	 * @note The minor units in a floating-point value cannot be verified and will always fit within
	 * the currency digits and precision specified.
	 *
	 * @exception overflow_error When the major unit is too large to be stored in a positive
	 * direction.
	 * @exception overflow_error When the major unit is too small to be stored in a negative
	 * direction.
	 * @exception domain_error When the conversion results in the floating-point value dividing by
	 * zero.
	 * @exception underflow_error When the floating-point value is too small to fit within the
	 * FloatingPoint.
	 * @exception overflow_error When the floating-point value is too large to fit within the
	 * FloatingPoint.
	 * @exception domain_error When the conversion results in the floating-point value not being
	 * valid.
	 *
	 * @param[in] value The floating-point value to convert.
	 * @param[in] code The ISO 4217 currency code the amount represents.
	 */
	Money(const FloatingPointBase& value, const Iso4217Codes& code);
	/**
	 * @breif Constructor for setting a monetary value using the native floating-point type.
	 *
	 * @note The minor units in a floating-point value cannot be verified and will always fit within
	 * the currency digits and precision specified, e.g. U.S.A. dollar value with a precision of 0
	 * will store the value 1.555 as 1.55 when no rounder is supplied.
	 *
	 * @exception overflow_error When the major unit is too large to be stored in a positive
	 * direction.
	 * @exception overflow_error When the major unit is too small to be stored in a negative
	 * direction.
	 * @exception domain_error When the conversion results in the floating-point value dividing by
	 * zero.
	 * @exception underflow_error When the floating-point value is too small to fit within the
	 * FloatingPoint.
	 * @exception overflow_error When the floating-point value is too large to fit within the
	 * FloatingPoint.
	 * @exception domain_error When the conversion results in the floating-point value not being
	 * valid.
	 *
	 * @param[in] value The floating-point value to convert.
	 * @param[in] code The ISO 4217 currency code the amount represents.
	 * @param[in] rounder The method for rounding the number when converting the floating-point
	 * number.
	 */
	Money(
		const FloatingPointBase& value,
		const Iso4217Codes& code,
		const FloatingPointRounderFunction& rounder
	);
	Money(const Money&) =default;
	Money(Money&&) noexcept =default;
	/**
	 * @breif Conversion operator to a native floating-point type.
	 *
	 * @exception domain_error When the conversion results in the floating-point value dividing by
	 * zero.
	 * @exception underflow_error When the floating-point value is too small to fit within the
	 * FloatingPoint.
	 * @exception overflow_error When the floating-point value is too large to fit within the
	 * FloatingPoint.
	 * @exception domain_error When the conversion results in the floating-point value not being
	 * valid.
	 */
	explicit operator FloatingPointBase() const;
	~Money() =default;
	/**
	 * @breif Negation operator without any loss of precision during the conversion.
	 *
	 * @return The negated value of the current object.
	 */
	Money operator-() const;
	/**
	 * @breif Additive operator. When the supplied monetary object is of a different currency, the
	 * amount is converted into the current object's currency using the pecunia::currency::converter
	 * function.
	 *
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception underflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] other The additional value to add to the current object's value.
	 *
	 * @return A new object containing the summation of the two values.
	 */
	Money operator+(const Money& other) const;
	/**
	 * @breif Subtractive operator. When the supplied monetary object is of a different currency,
	 * the amount is converted into the current object's currency using the
	 * pecunia::currency::converter function.
	 *
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception underflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] other The additional value to subtract from the current object's value.
	 *
	 * @return A new object containing the difference of the two values.
	 */
	Money operator-(const Money& other) const;
	Money operator*(const Money&) const =delete;
	/**
	 * @breif Multiplicative operator without any loss of precision.
	 *
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception underflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] value The signed 8-bit integer value to multiply to the current object's value.
	 *
	 * @return A new object containing the product of the two values.
	 */
	Money operator*(const std::int8_t value) const;
	/**
	 * @breif Multiplicative operator without any loss of precision.
	 *
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception underflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] value The signed 16-bit integer value to multiply to the current object's value.
	 *
	 * @return A new object containing the product of the two values.
	 */
	Money operator*(const std::int16_t value) const;
	/**
	 * @breif Multiplicative operator without any loss of precision.
	 *
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception underflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] value The signed 32-bit integer value to multiply to the current object's value.
	 *
	 * @return A new object containing the product of the two values.
	 */
	Money operator*(const std::int32_t value) const;
	/**
	 * @breif Multiplicative operator without any loss of precision.
	 *
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception underflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] value The signed 64-bit integer value to multiply to the current object's value.
	 *
	 * @return A new object containing the product of the two values.
	 */
	Money operator*(const std::int64_t value) const;
	/**
	 * @breif Multiplicative operator without any loss of precision. This only supports as many
	 * digits as there are minor digits.
	 *
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception underflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] value The additional value to multiply to the current object's value.
	 *
	 * @return A new object containing the product of the two values.
	 */
	Money operator*(const FloatingPointBase& value) const;
	/**
	 * @breif Division operator for calculating ratios.
	 *
	 * @exception domain_error When the result of the operation would result in a division by zero.
	 *
	 * @param[in] other The value to divide by the current object's value.
	 *
	 * @return The result of the division between the two, a ratio.
	 */
	FloatingPointBase operator/(const Money& other) const;
	/**
	 * @breif Division operator without any loss of precision.
	 *
	 * @exception domain_error When the result of the operation would result in a division by zero.
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception underflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] value The additional signed 8-bit integer value to divide by the current object's
	 * value.
	 *
	 * @return A new object containing the division of the two values.
	 */
	Money operator/(const std::int8_t value) const;
	/**
	 * @breif Division operator without any loss of precision.
	 *
	 * @exception domain_error When the result of the operation would result in a division by zero.
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception underflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] value The additional signed 16-bit integer value to divide by the current object's
	 * value.
	 *
	 * @return A new object containing the division of the two values.
	 */
	Money operator/(const std::int16_t value) const;
	/**
	 * @breif Division operator without any loss of precision.
	 *
	 * @exception domain_error When the result of the operation would result in a division by zero.
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception underflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] value The additional signed 32-bit integer value to divide by the current object's
	 * value.
	 *
	 * @return A new object containing the division of the two values.
	 */
	Money operator/(const std::int32_t value) const;
	/**
	 * @breif Division operator without any loss of precision.
	 *
	 * @exception domain_error When the result of the operation would result in a division by zero.
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception underflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] value The additional signed 64-bit integer value to divide by the current object's
	 * value.
	 *
	 * @return A new object containing the division of the two values.
	 */
	Money operator/(const std::int64_t value) const;
	/**
	 * @breif Division operator without any loss of precision. This only supports as many digits as
	 * there are minor digits.
	 *
	 * @exception domain_error When the result of the operation would result in a division by zero.
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception underflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] value The additional value to divide by the current object's value.
	 *
	 * @return A new object containing the division of the two values.
	 */
	Money operator/(const FloatingPointBase& value) const;
	Money operator%(const Money&) const =delete;
	/**
	 * @breif Modulus operator performed upon the major units of the object's current value.
	 *
	 * @exception domain_error When the result of the operation would result in a division by zero.
	 *
	 * @param[in] value The additional value to perform a "mod" on the current object's value.
	 *
	 * @return The value of the modulus operation of the two values.
	 */
	UnitStorage operator%(const UnitStorage& value) const;
	/**
	 * @breif Assignment Operator with copy. When the supplied monetary object is of a different
	 * currency, the amount is converted into the current object's currency using the
	 * pecunia::currency::converter function. Note that only the amount is assigned, not the
	 * currency.
	 *
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception overflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] other The object whose amount is to be assigned into the current object.
	 *
	 * @return The current object with the new amount.
	 */
	Money& operator=(const Money& other);
	/**
	 * @breif Assignment Operator with move. When the supplied monetary object is of a different
	 * currency, the amount is converted into the current object's currency using the
	 * pecunia::currency::converter function. Note that only the amount is assigned, not the
	 * currency.
	 *
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception overflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] other The object whose amount is to be assigned into the current object.
	 *
	 * @return The current object with the new amount.
	 */
	Money& operator=(Money&& other);
	/**
	 * @breif Less-than Relational Operator. When the supplied monetary object is of a different
	 * currency, the amount is converted into the current object's currency using the
	 * pecunia::currency::converter function.
	 *
	 * @param[in] other The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is less than the supplied object,
	 * false else-wise.
	 */
	bool operator<(const Money& other) const;
	/**
	 * @breif Less-than Relational Operator for an 8-bit signed integer. The supplied value is
	 * expected to be expressed in terms of the current object's issued currency, in the
	 * form MMMMmmpp.
	 *
	 * @param[in] value The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is less than the supplied object,
	 * false else-wise.
	 */
	bool operator<(const std::int8_t value) const;
	/**
	 * @breif Less-than Relational Operator for an 16-bit signed integer. The supplied value is
	 * expected to be expressed in terms of the current object's issued currency, in the
	 * form MMMMmmpp.
	 *
	 * @param[in] value The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is less than the supplied object,
	 * false else-wise.
	 */
	bool operator<(const std::int16_t value) const;
	/**
	 * @breif Less-than Relational Operator for an 32-bit signed integer. The supplied value is
	 * expected to be expressed in terms of the current object's issued currency, in the
	 * form MMMMmmpp.
	 *
	 * @param[in] value The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is less than the supplied object,
	 * false else-wise.
	 */
	bool operator<(const std::int32_t value) const;
	/**
	 * @breif Less-than Relational Operator for an 64-bit signed integer. The supplied value is
	 * expected to be expressed in terms of the current object's issued currency, in the
	 * form MMMMmmpp.
	 *
	 * @param[in] value The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is less than the supplied object,
	 * false else-wise.
	 */
	bool operator<(const std::int64_t value) const;
	/**
	 * @breif Less-than-equal Relational Operator. When the supplied monetary object is of a
	 * different currency, the amount is converted into the current object's currency using the
	 * pecunia::currency::converter function.
	 *
	 * @param[in] other The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is less than or equal to the supplied object,
	 * false else-wise.
	 */
	bool operator<=(const Money& other) const;
	/**
	 * @breif Less-than-equal Relational Operator for an 8-bit signed integer. The supplied value is
	 * expected to be expressed in terms of the current object's issued currency, in the
	 * form MMMMmmpp.
	 *
	 * @param[in] value The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is less than or equal to the supplied object,
	 * false else-wise.
	 */
	bool operator<=(const std::int8_t value) const;
	/**
	 * @breif Less-than-equal Relational Operator for an 16-bit signed integer. The supplied value
	 * is expected to be expressed in terms of the current object's issued currency, in the
	 * form MMMMmmpp.
	 *
	 * @param[in] value The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is less than or equal to the supplied object,
	 * false else-wise.
	 */
	bool operator<=(const std::int16_t value) const;
	/**
	 * @breif Less-than-equal Relational Operator for an 32-bit signed integer. The supplied value
	 * is expected to be expressed in terms of the current object's issued currency, in the
	 * form MMMMmmpp.
	 *
	 * @param[in] value The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is less than or equal to the supplied object,
	 * false else-wise.
	 */
	bool operator<=(const std::int32_t value) const;
	/**
	 * @breif Less-than-equal Relational Operator for an 64-bit signed integer. The supplied value
	 * is expected to be expressed in terms of the current object's issued currency, in the
	 * form MMMMmmpp.
	 *
	 * @param[in] value The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is less than or equal to the supplied object,
	 * false else-wise.
	 */
	bool operator<=(const std::int64_t value) const;
	/**
	 * @breif Greater-than Relational Operator. When the supplied monetary object is of a different
	 * currency, the amount is converted into the current object's currency using the
	 * pecunia::currency::converter function.
	 *
	 * @param[in] other The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is greater than the supplied object,
	 * false else-wise.
	 */
	bool operator>(const Money& other) const;
	/**
	 * @breif Greater-than Relational Operator for an 8-bit signed integer. The supplied value is
	 * expected to be expressed in terms of the current object's issued currency, in the
	 * form MMMMmmpp.
	 *
	 * @param[in] value The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is greater than the supplied object,
	 * false else-wise.
	 */
	bool operator>(const std::int8_t value) const;
	/**
	 * @breif Greater-than Relational Operator for an 16-bit signed integer. The supplied value is
	 * expected to be expressed in terms of the current object's issued currency, in the
	 * form MMMMmmpp.
	 *
	 * @param[in] value The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is greater than the supplied object,
	 * false else-wise.
	 */
	bool operator>(const std::int16_t value) const;
	/**
	 * @breif Greater-than Relational Operator for an 32-bit signed integer. The supplied value is
	 * expected to be expressed in terms of the current object's issued currency, in the
	 * form MMMMmmpp.
	 *
	 * @param[in] value The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is greater than the supplied object,
	 * false else-wise.
	 */
	bool operator>(const std::int32_t value) const;
	/**
	 * @breif Greater-than Relational Operator for an 64-bit signed integer. The supplied value is
	 * expected to be expressed in terms of the current object's issued currency, in the
	 * form MMMMmmpp.
	 *
	 * @param[in] value The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is greater than the supplied object,
	 * false else-wise.
	 */
	bool operator>(const std::int64_t value) const;
	/**
	 * @breif Greater-than-equal Relational Operator. When the supplied monetary object is of a
	 * different currency, the amount is converted into the current object's currency using the
	 * pecunia::currency::converter function.
	 *
	 * @param[in] other The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is greater than or equal to the supplied
	 * object, false else-wise.
	 */
	bool operator>=(const Money& other) const;
	/**
	 * @breif Greater-than-equal Relational Operator for an 8-bit signed integer. The supplied value
	 * is expected to be expressed in terms of the current object's issued currency, in the
	 * form MMMMmmpp.
	 *
	 * @param[in] value The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is greater than or equal to the supplied
	 * object, false else-wise.
	 */
	bool operator>=(const std::int8_t value) const;
	/**
	 * @breif Greater-than-equal Relational Operator for an 16-bit signed integer. The supplied
	 * value is expected to be expressed in terms of the current object's issued currency, in the
	 * form MMMMmmpp.
	 *
	 * @param[in] value The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is greater than or equal to the supplied
	 * object, false else-wise.
	 */
	bool operator>=(const std::int16_t value) const;
	/**
	 * @breif Greater-than-equal Relational Operator for an 32-bit signed integer. The supplied
	 * value is expected to be expressed in terms of the current object's issued currency, in the
	 * form MMMMmmpp.
	 *
	 * @param[in] value The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is greater than or equal to the supplied
	 * object, false else-wise.
	 */
	bool operator>=(const std::int32_t value) const;
	/**
	 * @breif Greater-than-equal Relational Operator for an 64-bit signed integer. The supplied
	 * value is expected to be expressed in terms of the current object's issued currency, in the
	 * form MMMMmmpp.
	 *
	 * @param[in] value The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is greater than or equal to the supplied
	 * object, false else-wise.
	 */
	bool operator>=(const std::int64_t value) const;
	/**
	 * @breif Equality Operator. When the supplied monetary object is of a different currency,
	 * the amount is converted into the current object's currency using the
	 * pecunia::currency::converter function.
	 *
	 * @param[in] other The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is equal to the supplied object,
	 * false else-wise.
	 */
	bool operator==(const Money& other) const;
	/**
	 * @breif Equality Operator for an 8-bit signed integer. The supplied value is expected to be
	 * expressed in terms of the current object's issued currency, in the form MMMMmmpp.
	 *
	 * @param[in] value The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is equal to the supplied value within, the
	 * expected level of precision, false else-wise.
	 */
	bool operator==(const std::int8_t value) const;
	/**
	 * @breif Equality Operator for an 16-bit signed integer. The supplied value is expected to be
	 * expressed in terms of the current object's issued currency, in the form MMMMmmpp.
	 *
	 * @param[in] value The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is equal to the supplied value within, the
	 * expected level of precision, false else-wise.
	 */
	bool operator==(const std::int16_t value) const;
	/**
	 * @breif Equality Operator for an 32-bit signed integer. The supplied value is expected to be
	 * expressed in terms of the current object's issued currency, in the form MMMMmmpp.
	 *
	 * @param[in] value The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is equal to the supplied value within, the
	 * expected level of precision, false else-wise.
	 */
	bool operator==(const std::int32_t value) const;
	/**
	 * @breif Equality Operator for an 64-bit signed integer. The supplied value is expected to be
	 * expressed in terms of the current object's issued currency, in the form MMMMmmpp.
	 *
	 * @param[in] value The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is equal to the supplied value within, the
	 * expected level of precision, false else-wise.
	 */
	bool operator==(const std::int64_t value) const;
	/**
	 * @breif Inequality Operator. When the supplied monetary object is of a different currency,
	 * the amount is converted into the current object's currency using the
	 * pecunia::currency::converter function.
	 *
	 * @param[in] other The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is not equal to the supplied object,
	 * false else-wise.
	 */
	bool operator!=(const Money& other) const;
	/**
	 * @breif Inequality Operator for an 8-bit signed integer. The supplied value is expected to be
	 * expressed in terms of the current object's issued currency, in the form MMMMmmpp.
	 *
	 * @param[in] value The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is not equal to the supplied value, within
	 * the expected level of precision, false else-wise.
	 */
	bool operator!=(const std::int8_t value) const;
	/**
	 * @breif Inequality Operator for an 16-bit signed integer. The supplied value is expected to be
	 * expressed in terms of the current object's issued currency, in the form MMMMmmpp.
	 *
	 * @param[in] value The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is not equal to the supplied value, within
	 * the expected level of precision, false else-wise.
	 */
	bool operator!=(const std::int16_t value) const;
	/**
	 * @breif Inequality Operator for an 32-bit signed integer. The supplied value is expected to be
	 * expressed in terms of the current object's issued currency, in the form MMMMmmpp.
	 *
	 * @param[in] value The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is not equal to the supplied value, within
	 * the expected level of precision, false else-wise.
	 */
	bool operator!=(const std::int32_t value) const;
	/**
	 * @breif Inequality Operator for an 64-bit signed integer. The supplied value is expected to be
	 * expressed in terms of the current object's issued currency, in the form MMMMmmpp.
	 *
	 * @param[in] value The other object whose value is to be compared against.
	 *
	 * @return A value of true with the current object is not equal to the supplied value, within
	 * the expected level of precision, false else-wise.
	 */
	bool operator!=(const std::int64_t value) const;
	/**
	 * @breif Additive assignment operator. When the supplied monetary object is of a different
	 * currency, the amount is converted into the current object's currency using the
	 * pecunia::currency::converter function.
	 *
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception underflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] other The additional value to add to the current object.
	 *
	 * @return The current object containing the summation of the two values.
	 */
	Money& operator+=(const Money& other);
	/**
	 * @breif Subtractive assignment operator. When the supplied monetary object is of a different
	 * currency, the amount is converted into the current object's currency using the
	 * pecunia::currency::converter function.
	 *
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception underflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] other The additional value to subtract from the current object.
	 *
	 * @return The current object containing the difference of the two values.
	 */
	Money& operator-=(const Money& other);
	/**
	 * @breif Multiplicative assignment operator without any loss of precision.
	 *
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception underflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] value The additional signed 8-bit integer value to multiply to the current
	 * object.
	 *
	 * @return The current object containing the product of the two values.
	 */
	Money& operator*=(const std::int8_t value);
	/**
	 * @breif Multiplicative assignment operator without any loss of precision.
	 *
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception underflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] value The additional signed 16-bit integer value to multiply to the current
	 * object.
	 *
	 * @return The current object containing the product of the two values.
	 */
	Money& operator*=(const std::int16_t value);
	/**
	 * @breif Multiplicative assignment operator without any loss of precision.
	 *
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception underflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] value The additional signed 8-bit integer value to multiply to the current
	 * object.
	 *
	 * @return The current object containing the product of the two values.
	 */
	Money& operator*=(const std::int32_t value);
	/**
	 * @breif Multiplicative assignment operator without any loss of precision.
	 *
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception underflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] value The additional signed 64-bit integer value to multiply to the current
	 * object.
	 *
	 * @return The current object containing the product of the two values.
	 */
	Money& operator*=(const std::int64_t value);
	/**
	 * @breif Multiplicative assignment operator without any loss of precision. This only supports
	 * as many digits as there are minor digits.
	 *
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception underflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] value The additional value to multiply to the current object.
	 *
	 * @return The current object containing the product of the two values.
	 */
	Money& operator*=(const FloatingPointBase& value);
	/**
	 * @breif Division assignment operator without any loss of precision.
	 *
	 * @exception domain_error When the result of the operation would result in a division by zero.
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception underflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] value The signed 8-bit integer additional value to divide by the current object's
	 * value.
	 *
	 * @return The current object containing the division of the two values.
	 */
	Money& operator/=(const std::int8_t value);
	/**
	 * @breif Division assignment operator without any loss of precision.
	 *
	 * @exception domain_error When the result of the operation would result in a division by zero.
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception underflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] value The signed 16-bit integer additional value to divide by the current object's
	 * value.
	 *
	 * @return The current object containing the division of the two values.
	 */
	Money& operator/=(const std::int16_t value);
	/**
	 * @breif Division assignment operator without any loss of precision.
	 *
	 * @exception domain_error When the result of the operation would result in a division by zero.
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception underflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] value The signed 32-bit integer additional value to divide by the current object's
	 * value.
	 *
	 * @return The current object containing the division of the two values.
	 */
	Money& operator/=(const std::int32_t value);
	/**
	 * @breif Division assignment operator without any loss of precision.
	 *
	 * @exception domain_error When the result of the operation would result in a division by zero.
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception underflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] value The signed 64-bit integer additional value to divide by the current object's
	 * value.
	 *
	 * @return The current object containing the division of the two values.
	 */
	Money& operator/=(const std::int64_t value);
	/**
	 * @breif Division assignment operator without any loss of precision. This only supports as many
	 * digits as there are minor digits.
	 *
	 * @exception domain_error When the result of the operation would result in a division by zero.
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception underflow_error When the result of the operation would be too small to store.
	 *
	 * @param[in] value The additional value to divide by the current object's value.
	 *
	 * @return The current object containing the division of the two values.
	 */
	Money& operator/=(const FloatingPointBase& value);
	/**
	 * @brief The entire representation of the monetary amount using the currency's minor unit,
	 * e.g. cents for USD, plus any extra digits of precision.
	 */
	const UnitStorage& amount() const noexcept;
	/**
	 * @breif Accessor to the major monetary value, e.g. only the U.S.A. dollars.
	 */
	UnitStorage major() const;
	/**
	 * @breif Accessor to the minor monetary value, e.g. only the U.S.A. cents.
	 */
	MinorUnit minor() const;
	/**
	 * @breif Accessor to the code the currency amount represents.
	 */
	const Iso4217Codes& code() const;
	/**
	 * @breif Performs a test to determine if the stored value is positive.
	 *
	 * @return True when the value is greater than zero.
	 */
	bool isPositive() const;
	/**
	 * @brief Converts the monetary value into a native floating-point type using a supplied
	 * rounding method. When a rounding function is not supplied, no rounding will occur.
	 *
	 * @param[in] rounder The rounding function to apply to the monetary value.
	 * @param[in] digits  The number of digits after the major unit.
	 *
	 * @exception domain_error When the conversion results in the floating-point value dividing by
	 * zero.
	 * @exception underflow_error When the floating-point value is too small to fit within the
	 * FloatingPoint.
	 * @exception overflow_error When the floating-point value is too large to fit within the
	 * FloatingPoint.
	 * @exception domain_error When the conversion results in the floating-point value not being
	 * valid.
	 */
	FloatingPointBase toFloatingPoint(
		const RounderFunction& rounder =
			[] (const UnitStorage& amount, const MinorUnit&, const std::uint8_t)
			{
				return amount;
			},
		const std::uint8_t digits = precision
	) const;
	/**
	 * @brief Performs a rounding operation upon the monetary value.
	 *
	 * @exception overflow_error When the result of the operation would be too large to store.
	 * @exception overflow_error When the result of the operation would be too small to store.
	 * @exception domain_error When the conversion results in the floating-point value dividing by
	 * zero.
	 * @exception underflow_error When the floating-point value is too small to fit within the
	 * FloatingPoint.
	 * @exception overflow_error When the floating-point value is too large to fit within the
	 * FloatingPoint.
	 * @exception domain_error When the conversion results in the floating-point value not being
	 * valid.
	 *
	 * @param rounder The rounding function to apply to the monetary value.
	 * @param digits The number of digits after the major unit to round to.
	 *
	 * @return The current object containing the rounded value.
	 */
	Money& round(const RounderFunction& rounder, const std::uint8_t digits);
	/**
	 * @brief Determines if the instance has an amount set. For example, an amount of 2.34 USD would
	 * be set, but 0.00 XXX would not be.
	 *
	 * @return When the major, minor, and code are all set gives true, false else-wise.
	 */
	bool hasAmount() const;
	/**
	 * @brief Changes the ISO 4217 currency code without performing any conversions.
	 *
	 * @param[in] code The new ISO 4217 currency code the amount represents.
	 */
	void setCode(const pecunia::currency::Iso4217Codes& code);
	/**
	 * @brief Calculates the largest monetary value that can be used for a given currency.
	 *
	 * @param code The currency code to calculate the monetary value in.
	 *
	 * @return The monetary value pair with the first element being the major unit, and second
	 * element being the minor unit.
	 */
	static std::pair<UnitStorage, MinorUnit> maximum(const pecunia::currency::Iso4217Codes& code);
	/**
	 * @brief Calculates the smallest monetary value that can be used for a given currency.
	 *
	 * @param code The currency code to calculate the monetary value in.
	 *
	 * @return The monetary value pair with the first element being the major unit, and second
	 * element being the minor unit.
	 */
	static std::pair<UnitStorage, MinorUnit> minimum(const pecunia::currency::Iso4217Codes& code);
	/**
	 * @brief Resets the monetary value to zero, without changing the currency.
	 */
	void zeroOut();
};

/**
 * @breif Multiplicative operator without any loss of precision.
 *
 * @exception overflow_error When the result of the operation would be too large to store.
 * @exception underflow_error When the result of the operation would be too small to store.
 *
 * @param[in] lhs The signed 8-bit integer amount by which to multiply the supplied object's
 * value.
 * @param[in] rhs The object that is to be multiplied.
 *
 * @return A new object containing the product of the two values.
 */
PECUNIA_EXPORT Money operator*(const std::int8_t lhs, const Money& rhs);
/**
 * @breif Multiplicative operator without any loss of precision.
 *
 * @exception overflow_error When the result of the operation would be too large to store.
 * @exception underflow_error When the result of the operation would be too small to store.
 *
 * @param[in] lhs The signed 16-bit integer amount by which to multiply the supplied object's
 * value.
 * @param[in] rhs The object that is to be multiplied.
 *
 * @return A new object containing the product of the two values.
 */
PECUNIA_EXPORT Money operator*(const std::int16_t lhs, const Money& rhs);
/**
 * @breif Multiplicative operator without any loss of precision.
 *
 * @exception overflow_error When the result of the operation would be too large to store.
 * @exception underflow_error When the result of the operation would be too small to store.
 *
 * @param[in] lhs The signed 32-bit integer amount by which to multiply the supplied object's
 * value.
 * @param[in] rhs The object that is to be multiplied.
 *
 * @return A new object containing the product of the two values.
 */
PECUNIA_EXPORT Money operator*(const std::int32_t lhs, const Money& rhs);
/**
 * @breif Multiplicative operator without any loss of precision.
 *
 * @exception overflow_error When the result of the operation would be too large to store.
 * @exception underflow_error When the result of the operation would be too small to store.
 *
 * @param[in] lhs The signed 64-bit integer amount by which to multiply the supplied object's
 * value.
 * @param[in] rhs The object that is to be multiplied.
 *
 * @return A new object containing the product of the two values.
 */
PECUNIA_EXPORT Money operator*(const std::int64_t lhs, const Money& rhs);
/**
 * @breif Multiplicative operator without any loss of precision. This only supports as many digits
 * as there are minor digits.
 *
 * @exception overflow_error When the result of the operation would be too large to store.
 * @exception underflow_error When the result of the operation would be too small to store.
 *
 * @param[in] lhs The amount by which to multiply the supplied object's value.
 * @param[in] rhs The object that is to be multiplied.
 *
 * @return A new object containing the product of the two values.
 */
PECUNIA_EXPORT Money operator*(const FloatingPointBase& lhs, const Money& rhs);
/**
 * @breif Stream Insertion Operator. By default the form will be the locale's monetary format with
 * no spaces, the ISO code, and trailing the value, e.g. a U.S.A. locale with value of one dollar
 * and twenty-three cents would appear as 1.23USD.
 *
 * @exception system_error When the stream fails to be written.
 *
 * @param[in,out] stream The destination source for the contents of the supplied money object.
 * @param[in] m The object whose values are to placed into the destination source.
 *
 * @return The modified source stream object filled with the contents of the money object.
 */
PECUNIA_EXPORT std::ostream& operator<<(std::ostream& stream, const Money& m);
/**
 * @breif Stream Extraction Operator. The monetary amount will be read in using the locale's setting
 * for reading in money. The currency code is required and must be in all upper-case letters.
 *
 * @pre The locale of the supplied stream must be set to get the money formatted in readable
 * manner.
 *
 * @exception system_error When the stream fails to be read.
 * @exception runtime_error When the currency is incorrectly formatted.
 *
 * @param[in,out] stream The extraction source for the contents of the supplied money object.
 * @param[out] m The object whose values are to be filled by the contents of the stream.
 *
 * @return The modified source stream object emptied of a money entry.
 */
PECUNIA_EXPORT std::istream& operator>>(std::istream& stream, Money& m);
/**
 * @brief Converts the monetary representation into a string from. The format of the string
 * representation is the amount followed by a space and the currency code.
 *
 * @param[out] m The object whose values are to be converted into a string form.
 *
 * @return The string representation.
 */
PECUNIA_EXPORT std::string to_string(const Money& m);
/**
 * @brief Exchanges two monetary objects without any conversion operation performed.
 *
 * @param lhs The object to swap to the right.
 * @param rhs The object to swap to the left.
 */
PECUNIA_EXPORT void swap(Money& lhs, Money& rhs) noexcept;

}}

#endif
