/*******************************************************************************
***  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 "Adjustments.h"
using std::function;
using std::int32_t;
using std::uint8_t;
using pecunia::converter;
using pecunia::precision;
using pecunia::MinorUnit;
using pecunia::FloatingPointRounderFunction;
using pecunia::UnitStorage;

#include <cassert>
// using assert;
#include <cmath>
using std::abs;
#include <stdexcept>
using std::overflow_error;

#include <MoneySize.hpp> // cppcheck-suppress missingInclude
using pecunia::internal::extraFloatingPointPrecision;
#include <pecunia/Codes.h>
using pecunia::currency::Iso4217Codes;
#include <pecunia/FloatingPoint.h>
using pecunia::FloatingPoint;
#include <pecunia/Money.h>
using pecunia::currency::Money;
using pecunia::currency::to_string;


UnitStorage pecunia::currency::internal::fromFloatingPointMajor(
	const FloatingPoint& value,
	const Iso4217Codes& code,
	const uint8_t currencyPrecision,
	const FloatingPointRounderFunction& rounder
)
{
	assert(rounder != nullptr && "The floating point rounder must be a function.");
	const uint8_t totalDigits{
		static_cast<uint8_t>(
			minorUnitDigits(code) + currencyPrecision + extraFloatingPointPrecision
		)
	};
	const int32_t ratio{minorUnitPrecisionFactor(code)};
	const FloatingPoint rounded{rounder(value, totalDigits)};
	const UnitStorage major{static_cast<UnitStorage>(((rounded * ratio) / ratio).underlying())};
	return major;
}

MinorUnit pecunia::currency::internal::fromFloatingPointMinor(
	const FloatingPoint& value,
	const Iso4217Codes& code,
	const uint8_t currencyPrecision,
	const FloatingPointRounderFunction& rounder
)
{
	assert(rounder != nullptr && "The floating point rounder must be a function.");
	const uint8_t totalDigits{
		static_cast<uint8_t>(
			minorUnitDigits(code) + currencyPrecision + extraFloatingPointPrecision
		)
	};
	const int32_t ratio{
		minorUnitPrecisionFactor(code)
			* static_cast<int32_t>(pow(10, extraFloatingPointPrecision))
	};
	const FloatingPoint rounded{rounder(value, totalDigits)};
	const UnitStorage adjustedValue{static_cast<UnitStorage>((rounded * ratio).underlying())};
	const auto minorAdjusted{abs(adjustedValue) % ratio};
	const auto extraRatio{pow(10.0, extraFloatingPointPrecision)};
	const auto removedExtra{minorAdjusted / static_cast<MinorUnit>(extraRatio)};
	return static_cast<MinorUnit>(removedExtra);
}

UnitStorage pecunia::currency::internal::normaliseAmount(const Money& from, const Iso4217Codes& to)
{
	assert(converter != nullptr && "The currency conversion function is not set.");
	const auto& fromCode{from.code()};
	const FloatingPoint conversionFactor{converter(fromCode, to)};

	if (conversionFactor.isEqual(1.0, static_cast<uint8_t>(minorUnitDigits(fromCode) + precision)))
		return from.amount();
	const auto convertedAmount{FloatingPoint{from.amount()} * conversionFactor};
	return static_cast<UnitStorage>(convertedAmount.underlying());
}
