/*******************************************************************************
***  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_MATH_H_
#define PECUNIA_MATH_H_

#include <functional>
#include <map>
#include <type_traits>
#include <vector>

#include "Codes.h"
#include "Money.h"
#include <pecunia_export.h> // cppcheck-suppress missingInclude


namespace pecunia
{
namespace math
{
/**
 * @breif Calculates the absolute value of a supplied amount of money.
 *
 * @param money The money whose monetary amount should be an absolute value.
 *
 * @return The absolute value of the monetary amount.
 */
PECUNIA_EXPORT currency::Money absoluteValue(const currency::Money& money);
/**
 * @brief Accumulates the total of all the supplied monies with the fewest possible number of
 * conversions.
 *
 * @tparam Container The type of container holding the monies.
 * @tparam ValueType This must be the Money type.
 * @tparam Allocator The allocator used by the container.
 *
 * @param accumulateCurrency The currency to accumulate the total in.
 * @param monies The container of the monies to total.
 * @param adjustor When supplied, a functor to apply to each element in the container of monies
 * prior to final summation.
 *
 * @return The sum of all the monies in the requested currency.
 */
template<template<typename, typename> class Container, typename ValueType, typename Allocator>
currency::Money sum(
	const currency::Iso4217Codes& accumulateCurrency,
	const Container<ValueType, Allocator>& monies,
	const std::function<
		currency::Money(const currency::Money& money)
	>& adjustor = nullptr
);
/**
 * @brief Accumulates the total of all the supplied monies with the fewest possible number of
 * conversions.
 *
 * @tparam AssociativeContainer The type of associative container holding the monies.
 * @tparam KeyType The type the monies are keyed against, not used.
 * @tparam ValueType This must be the Money type.
 * @tparam Allocator The allocator used by the container.
 *
 * @param accumulateCurrency The currency to accumulate the total in.
 * @param monies The container of the monies to total.
 * @param adjustor When supplied, a functor to apply to each element in the container of monies
 * prior to final summation.
 *
 * @return The sum of all the monies in the requested currency.
 */
template<
	template<typename, typename, typename, typename> class AssociativeContainer,
	class KeyType,
	class ValueType,
	class Compare = std::less<KeyType>,
	class Allocator = std::allocator<std::pair<const KeyType, ValueType>>
>
currency::Money sum(
	const currency::Iso4217Codes& accumulateCurrency,
	const AssociativeContainer<KeyType, ValueType, Compare, Allocator>& monies,
	const std::function<
		currency::Money(const currency::Money& money)
	>& adjustor = nullptr
);

namespace internal
{

PECUNIA_EXPORT void addToRunningTotals(
	std::map<currency::Iso4217Codes, currency::Money>& runningTotals,
	const currency::Money& amount,
	const std::function<
		currency::Money(const currency::Money& money)
	>& adjustor
);
PECUNIA_EXPORT currency::Money accumulateRunningTotals(
	const currency::Iso4217Codes& accumulateCurrency,
	const std::map<currency::Iso4217Codes, currency::Money>& totals
);

}}}

template<template<typename, typename> class Container, typename ValueType, typename Allocator>
pecunia::currency::Money pecunia::math::sum(
	const pecunia::currency::Iso4217Codes& accumulateCurrency,
	const Container<ValueType, Allocator>& monies,
	const std::function<
		pecunia::currency::Money(const pecunia::currency::Money& money)
	>& adjustor
)
{
	static_assert(
		std::is_same<ValueType, pecunia::currency::Money>::value,
		"The contained type must be Money."
	);
	std::map<pecunia::currency::Iso4217Codes, pecunia::currency::Money> runningTotals{};

	for (const auto& money : monies)
		internal::addToRunningTotals(runningTotals, money, adjustor);
	return internal::accumulateRunningTotals(accumulateCurrency, runningTotals);
}

template<
	template<typename, typename, typename, typename> class AssociativeContainer,
	class KeyType,
	class ValueType,
	class Compare,
	class Allocator
>
pecunia::currency::Money pecunia::math::sum(
	const pecunia::currency::Iso4217Codes& accumulateCurrency,
	const AssociativeContainer<KeyType, ValueType, Compare, Allocator>& monies,
	const std::function<
		pecunia::currency::Money(const pecunia::currency::Money& money)
	>& adjustor
)
{
	static_assert(
		std::is_same<ValueType, pecunia::currency::Money>::value,
		"The contained type must be Money."
	);
	std::map<pecunia::currency::Iso4217Codes, pecunia::currency::Money> runningTotals{};

	for (const auto& keyMoney : monies)
		internal::addToRunningTotals(runningTotals, keyMoney.second, adjustor);
	return internal::accumulateRunningTotals(accumulateCurrency, runningTotals);
}

#endif
