/*******************************************************************************
***  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_FLOATING_POINT_H_
#define PECUNIA_FLOATING_POINT_H_

#include <string>

#include <MoneySize.hpp> // cppcheck-suppress missingInclude
#include <pecunia_export.h> // cppcheck-suppress missingInclude


namespace pecunia
{

struct FloatingPointParts;

/**
 * @brief Represents a safe type for working with floating-point operations.
 */
class PECUNIA_EXPORT FloatingPoint
{
	FloatingPointBase value_;

public:
	/**
	 * @brief Fully initialises the value to zero.
	 */
	FloatingPoint();
	/**
	 * @brief Fully initialises the value.
	 *
	 * @param value The value to set the floating-point to.
	 */
	FloatingPoint(const FloatingPointBase& value); // cppcheck-suppress noExplicitConstructor
	FloatingPoint(const std::int32_t& value); // cppcheck-suppress noExplicitConstructor
	FloatingPoint(const std::int64_t& value); // cppcheck-suppress noExplicitConstructor
	FloatingPoint(const FloatingPoint&) = default;
	FloatingPoint(FloatingPoint&&) noexcept = default;
	FloatingPoint& operator=(const FloatingPoint&) = default;
	FloatingPoint& operator=( FloatingPoint&&) noexcept = default;
	FloatingPoint operator+(const FloatingPoint& other) const;
	FloatingPoint operator-(const FloatingPoint& other) const;
	FloatingPoint operator*(const FloatingPoint& other) const;
	FloatingPoint operator/(const FloatingPoint& other) const;
	bool operator==(const FloatingPoint& other) const = delete;
	bool operator==(const FloatingPointBase& other) const = delete;
	bool operator<(const FloatingPoint& other) const;
	bool operator<=(const FloatingPoint& other) const = delete;
	bool operator>(const FloatingPoint& other) const;
	bool operator>=(const FloatingPoint& other) const = delete;
	bool operator<(const FloatingPointBase& other) const;
	bool operator<=(const FloatingPointBase& other) const = delete;
	bool operator>(const FloatingPointBase& other) const;
	bool operator>=(const FloatingPointBase& other) const = delete;
	FloatingPoint operator-() const;
	/**
	 * @brief The unsafe type represented by the floating-point value.
	 */
	const FloatingPointBase& underlying() const noexcept;
	/**
	 * @brief Breaks a floating-point number into its whole and decimal components.
	 *
	 * @param digits The number of digits of precision to have in the decimal portion.
	 *
	 * @return The split floating-point number.
	 */
	FloatingPointParts decompose(const std::uint8_t digits) const;
	FloatingPoint floor() const;
	FloatingPoint ceil() const;
	FloatingPoint abs() const;
	FloatingPoint modulo(const FloatingPoint& other) const;
	FloatingPoint pow(const std::int64_t& exp) const;
	FloatingPoint pow(const FloatingPoint& exp) const;
	/**
	 * @brief Determines if the supplied number is equal to within a specific number of digits.
	 *
	 * @param other The other floating-point value to compare the current instance to.
	 * @param digits The number of digits of precision to compare the two numbers to.
	 *
	 * @return True when all numbers before the precision digit is equal, false else-wise. Any
	 * number at or beyond the precision digit is ignored.
	 */
	bool isEqual(const FloatingPoint& other, const std::uint8_t digits) const;
	/**
	 * @brief Calculates the reciprocal of the current instance's value.
	 *
	 * @return The value of 1 / value.
	 */
	FloatingPoint reciprocal() const;
	/**
	 * @brief Converts the float-point value into a string.
	 *
	 * @return Human readable form of the number.
	 */
	std::string toString() const;
};
struct PECUNIA_EXPORT FloatingPointParts
{
	FloatingPointParts();
	FloatingPointParts(const FloatingPointBase& whole, const FloatingPointBase& decimal);
	FloatingPointParts(const FloatingPoint& whole, const FloatingPoint& decimal);

	FloatingPoint whole_;
	FloatingPoint decimal_;
};

PECUNIA_EXPORT bool operator<(const FloatingPointBase lhs, const FloatingPoint& rhs);
PECUNIA_EXPORT bool operator>(const FloatingPointBase lhs, const FloatingPoint& rhs);
PECUNIA_EXPORT FloatingPoint operator/(const FloatingPointBase numerator, const FloatingPoint& denominator);

namespace literals
{

PECUNIA_EXPORT pecunia::FloatingPoint operator""_fp(long double value);

}}

#endif
