/*******************************************************************************
***  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_ALGORITHM_HPP_
#define PECUNIA_ALGORITHM_HPP_

#include <functional>
#include <type_traits>

#include "Money.h"


namespace pecunia
{

/**
 * @brief Sorts the container using a less-than comparison operator. The sort is not stable.
 *
 * @tparam ContainerType The type of container holding the monetary values to sort. The container
 * must have random access iterator support. The contained type must support the operators
 * less-than, greater-than, and have a swap function.
 *
 * @param container The container holding the monetary values to sort.
 * @param swaperator A function that will swap the contained values using no monetary conversion in
 * the process.
 */
template<typename ContainerType>
void sort(
	ContainerType& container,
	const std::function<
		void(typename ContainerType::value_type&, typename ContainerType::value_type&)
	>& swaperator = pecunia::currency::swap
);

namespace internal
{

template<typename RandomIteratorType, typename ContainerValueType>
void quicksort(
	RandomIteratorType low,
	RandomIteratorType high,
	const std::function<void(ContainerValueType&, ContainerValueType&)>& swaperator,
	const bool isLowBegin
);
template<typename RandomIteratorType, typename ContainerValueType>
RandomIteratorType partition(
	RandomIteratorType low,
	RandomIteratorType high,
	const std::function<void(ContainerValueType&, ContainerValueType&)>& swaperator,
	const bool isLowBegin
);

}}

template<typename ContainerType>
void pecunia::sort(
	ContainerType& container,
	const std::function<
		void(typename ContainerType::value_type&, typename ContainerType::value_type&)
	>& swaperator
)
{
	const auto size{container.size()};

	if (size > 2)
		internal::quicksort(container.begin(), std::prev(container.end()), swaperator, true);
	else if (size == 2)
	{
		auto lhs{container.begin()};
		auto rhs(std::next(lhs));

		if (*rhs < *lhs)
			swaperator(*lhs, *rhs);
	}

}

template<typename RandomIteratorType, typename ContainerValueType>
void pecunia::internal::quicksort(
	RandomIteratorType low,
	RandomIteratorType high,
	const std::function<void(ContainerValueType&, ContainerValueType&)>& swaperator,
	const bool isLowBegin
)
{
	if (low < high)
	{
		const auto pivot{partition(low, high, swaperator, isLowBegin)};
		quicksort(low, pivot, swaperator, isLowBegin);
		quicksort(std::next(pivot), high, swaperator, false);
	}
}

template<typename RandomIteratorType, typename ContainerValueType>
RandomIteratorType pecunia::internal::partition(
	RandomIteratorType low,
	RandomIteratorType high,
	const std::function<void(ContainerValueType&, ContainerValueType&)>& swaperator,
	const bool isLowBegin
)
{
	const auto pivotValue{*std::next(low, std::distance(low, high) / 2)};
	// N.B. Cannot use the previous iterator if it's the begining of the container.
	auto lowIndex{isLowBegin ? low : std::prev(low)};
	auto highIndex{std::next(high)};
	bool handledLowBegin{ ! isLowBegin};

	do
	{
		do
			// Only advance when the we are not at the begining since it's not been evaluated yet.
			if (handledLowBegin)
				std::advance(lowIndex, 1);
			else
				handledLowBegin = true;
		while (*lowIndex < pivotValue);

		do
			std::advance(highIndex, -1);
		while (*highIndex > pivotValue);

		if (lowIndex >= highIndex)
			return highIndex;
		swaperator(*lowIndex, *highIndex);
	}
	while (true);
}

#endif
