/*
	This file is part of solidity.

	solidity is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	solidity 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 General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with solidity.  If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0

#include <tools/yulPhaser/PairSelections.h>

#include <tools/yulPhaser/Selections.h>
#include <tools/yulPhaser/SimulationRNG.h>

#include <cmath>

using namespace std;
using namespace solidity::phaser;

vector<tuple<size_t, size_t>> RandomPairSelection::materialise(size_t _poolSize) const
{
	if (_poolSize < 2)
		return {};

	auto count = static_cast<size_t>(round(double(_poolSize) * m_selectionSize));

	vector<tuple<size_t, size_t>> selection;
	for (size_t i = 0; i < count; ++i)
	{
		size_t index1 = SimulationRNG::uniformInt(0, _poolSize - 1);
		size_t index2;
		do
		{
			index2 = SimulationRNG::uniformInt(0, _poolSize - 1);
		} while (index1 == index2);

		selection.emplace_back(index1, index2);
	}

	return selection;
}

vector<tuple<size_t, size_t>> PairsFromRandomSubset::materialise(size_t _poolSize) const
{
	vector<size_t> selectedIndices = RandomSubset(m_selectionChance).materialise(_poolSize);

	if (selectedIndices.size() % 2 != 0)
	{
		if (selectedIndices.size() < _poolSize && SimulationRNG::bernoulliTrial(0.5))
		{
			do
			{
				size_t extraIndex = SimulationRNG::uniformInt(0, selectedIndices.size() - 1);
				if (find(selectedIndices.begin(), selectedIndices.end(), extraIndex) == selectedIndices.end())
					selectedIndices.push_back(extraIndex);
			} while (selectedIndices.size() % 2 != 0);
		}
		else
			selectedIndices.erase(
				selectedIndices.begin() +
				static_cast<ptrdiff_t>(SimulationRNG::uniformInt(0, selectedIndices.size() - 1))
			);
	}
	assert(selectedIndices.size() % 2 == 0);

	vector<tuple<size_t, size_t>> selectedPairs;
	for (size_t i = selectedIndices.size() / 2; i > 0; --i)
	{
		size_t position1 = SimulationRNG::uniformInt(0, selectedIndices.size() - 1);
		size_t value1 = selectedIndices[position1];
		selectedIndices.erase(selectedIndices.begin() + static_cast<ptrdiff_t>(position1));
		size_t position2 = SimulationRNG::uniformInt(0, selectedIndices.size() - 1);
		size_t value2 = selectedIndices[position2];
		selectedIndices.erase(selectedIndices.begin() + static_cast<ptrdiff_t>(position2));

		selectedPairs.emplace_back(value1, value2);
	}
	assert(selectedIndices.empty());

	return selectedPairs;
}

vector<tuple<size_t, size_t>> PairMosaicSelection::materialise(size_t _poolSize) const
{
	if (_poolSize < 2)
		return {};

	size_t count = static_cast<size_t>(round(double(_poolSize) * m_selectionSize));

	vector<tuple<size_t, size_t>> selection;
	for (size_t i = 0; i < count; ++i)
	{
		tuple<size_t, size_t> pair = m_pattern[i % m_pattern.size()];
		selection.emplace_back(min(get<0>(pair), _poolSize - 1), min(get<1>(pair), _poolSize - 1));
	}

	return selection;
}