#pragma once

#include <psemek/random/uniform_int.hpp>
#include <psemek/random/uniform_real.hpp>

#include <psemek/math/constants.hpp>
#include <psemek/math/box.hpp>

#include <psemek/util/exception.hpp>

#include <type_traits>
#include <initializer_list>

namespace psemek::random
{

	template <typename T>
	using uniform_distribution = std::conditional_t<std::is_floating_point_v<T>, uniform_real_distribution<T>, uniform_int_distribution<T>>;

	template <typename T, typename RNG>
	T uniform(RNG && rng, math::interval<T> const & range)
	{
		return uniform_distribution<T>{range}(rng);
	}

	template <typename T, typename RNG>
	T uniform(RNG && rng, T min, T max)
	{
		return uniform<T>(rng, {min, max});
	}

	template <typename T, typename RNG>
	T uniform(RNG && rng)
	{
		if constexpr (std::is_same_v<T, bool>)
		{
			return uniform<char>(rng, 0, 1) == 1;
		}
		else if constexpr (std::is_floating_point_v<T>)
		{
			return uniform<T>(rng, T{0}, T{1});
		}
		else if constexpr (std::is_integral_v<T>)
		{
			return uniform<T>(rng, std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
		}
	}

	template <typename T, std::size_t N, typename RNG>
	math::point<T, N> uniform(RNG && rng, math::box<T, N> const & box)
	{
		math::point<T, N> result;
		for (std::size_t i = 0; i < N; ++i)
			result[i] = uniform<T>(rng, box[i]);
		return result;
	}

	template <typename T, typename RNG>
	T uniform_angle(RNG && rng)
	{
		return uniform(rng, T{0}, T{2 * math::pi});
	}

	template <typename RNG, typename Container>
	decltype(auto) uniform_from(RNG && rng, Container && container)
	{
		using std::size;
		using std::begin;
		if (size(container) == 0)
			throw util::exception("Cannot sample from empty container");
		return *std::next(begin(container), uniform<std::size_t>(rng, 0, size(container) - 1));
	}

	template <typename RNG, typename T>
	decltype(auto) uniform_from(RNG && rng, std::initializer_list<T> const & list)
	{
		return *(list.begin() + uniform<std::size_t>(rng, 0, list.size() - 1));
	}

}
