#pragma once

#include "globalincs/pstypes.h"
#include "parse/parselo.h"

#include <random>
#include <type_traits>
#include <limits>

namespace util {

/**
 * @defgroup randomUtils Random Utilities
 *
 * Utility functions for handling random values
 */

namespace {

/**
 * @brief Parses a generic list of numbers
 * @param list The array where the numbers should be stored.
 * @return The amount of parsed numbers
 *
 * @ingroup randomUtils
 */
template<typename T, size_t N>
size_t parse_number_list(T (& list)[N]) {
	ignore_white_space();

	if (*Mp != '(')
	{
		// Probably one a single value so stuff that and don't parse a list. This makes it easier to specify single values
		float val;
		stuff_float(&val);

		list[0] = static_cast<T>(val);
		return 1;
	}

	float helpList[N];
	auto num = stuff_float_list(helpList, N);

	for (size_t i = 0; i < num; ++i) {
		list[i] = static_cast<T>(helpList[i]);
	}

	return num;
}
}

/**
 * @brief Generic class for generating numbers in a specific range
 *
 * This allows to use a generic value, distribution and generator type. It's valid to only use one value for the range
 * in which case the returned value is constant.
 *
 * @ingroup randomUtils
 */
template<typename Value, typename Distribution, typename Generator>
class RandomRange {
 public:
	typedef Distribution DistributionType;
	typedef Generator GeneratorType;
	typedef Value ValueType;

 private:
	mutable GeneratorType m_generator;
	mutable DistributionType m_distribution;

	bool m_constant;
	ValueType m_minValue;
	ValueType m_maxValue;

 public:
	template<typename... Ts>
	RandomRange(ValueType param1, ValueType param2, Ts&& ... distributionParameters) :
		m_generator(std::random_device()()),
		m_distribution(param1, param2, distributionParameters...) {
		m_minValue = static_cast<ValueType>(param1);
		m_maxValue = static_cast<ValueType>(param2);
		m_constant = false;
	}

	explicit RandomRange(const ValueType& val) : RandomRange() {
		m_minValue = val;
		m_maxValue = val;
		m_constant = true;
	}

	RandomRange() :
		m_generator(std::random_device()()),
		m_distribution() {
		m_minValue = static_cast<ValueType>(0.0);
		m_maxValue = static_cast<ValueType>(0.0);
		m_constant = true;
	}

	/**
	 * @brief Determines the next random number of this range
	 * @return The random number
	 */
	ValueType next() const {
		if (m_constant) {
			return m_minValue;
		}

		return m_distribution(m_generator);
	}

	/**
	 * @brief Gets the minimum value that may be returned by this random range
	 *
	 * @warning This is not valid for normal distribution ranges since those do not have a definite minimum value.
	 *
	 * @return The minimum value
	 */
	ValueType min() const {
		return m_minValue;
	}

	/**
	 * @brief Gets the maximum value that may be returned by this random range
	 *
	 * @warning This is not valid for normal distribution ranges since those do not have a definite maximum value.
	 *
	 * @return The maximum value
	 */
	ValueType max() {
		return m_maxValue;
	}
};

/**
 * @brief A random range with a normal distribution
 *
 * The range parameters are passed directly to std::normal_distribution
 *
 * @ingroup randomUtils
 */
template<typename Value>
using NormalRange = RandomRange<Value,
								std::normal_distribution<Value>,
								std::minstd_rand>;

/**
 * @brief A normal range which uses floats
 *
 * @ingroup randomUtils
 */
typedef NormalRange<float> NormalFloatRange;

/**
 * @brief A function for parsing a normal range
 * @return The parsed normal range
 *
 * @ingroup randomUtils
 */
template<typename Value>
NormalRange<Value> parseNormalRange() {
	Value valueList[2];
	auto num = parse_number_list(valueList);

	if (num == 0) {
		error_display(0, "Need at least one value to form a random range!");
		return NormalRange<Value>();
	}
	else if (num == 1) {
		return NormalRange<Value>(valueList[0]);
	}

	return NormalRange<Value>(valueList[0], valueList[1]);
}

/**
 * @brief A generic random range which uses a uniform distribution
 *
 * @ingroup randomUtils
 */
template<typename Value>
using UniformRange = RandomRange<Value,
								 typename std::conditional<std::is_integral<Value>::value,
														   std::uniform_int_distribution<Value>,
														   std::uniform_real_distribution<Value>>::type,
								 std::minstd_rand>;

/**
 * @brief A uniform range which uses floats
 *
 * @ingroup randomUtils
 */
typedef UniformRange<float> UniformFloatRange;

/**
 * @brief A uniform range which uses ints
 *
 * @ingroup randomUtils
 */
typedef UniformRange<int> UniformIntRange;

/**
 * @brief A uniform range which uses uints
 *
 * @ingroup randomUtils
 */
typedef UniformRange<uint> UniformUIntRange;

/**
 * @brief Parses a generic uniform range
 * @param allowNegativ If @c true, negative values will be allowed
 * @param min The minimum value the random range may return
 * @param max The maximum value the random range may return
 *
 * @return The parsed uniform range
 *
 * @ingroup randomUtils
 */
template<typename Value>
UniformRange<Value> parseUniformRange(Value min = std::numeric_limits<Value>::min(),
									  Value max = std::numeric_limits<Value>::max()) {
	Assertion(min <= max, "Invalid min-max values specified!");

	Value valueList[2];
	auto num = parse_number_list(valueList);

	if (num == 0) {
		error_display(0, "Need at least one value to form a random range!");
		return UniformRange<Value>();
	}
	else if (num == 1) {
		return UniformRange<Value>(valueList[0]);
	}

	if (valueList[0] > valueList[1]) {
		error_display(0, "Minimum value %f is more than maximum value %f!", (float) valueList[0], (float) valueList[1]);
		std::swap(valueList[0], valueList[1]);
	}

	if (valueList[0] < min) {
		error_display(0, "First value (%f) is less than the minimum %f!", (float) valueList[0], (float) min);
		valueList[0] = min;
	}
	if (valueList[0] > max) {
		error_display(0, "First value (%f) is greater than the maximum %f!", (float) valueList[0], (float) max);
		valueList[0] = max;
	}

	if (valueList[1] < min) {
		error_display(0, "Second value (%f) is less than the minimum %f!", (float) valueList[1], (float) min);
		valueList[1] = min;
	}
	if (valueList[1] > max) {
		error_display(0, "Second value (%f) is greater than the maximum %f!", (float) valueList[1], (float) max);
		valueList[1] = max;
	}

	if (valueList[0] == valueList[1]) {
		// If the two values are equal then this is slightly more efficient
		return UniformRange<Value>(valueList[0]);
	} else {
		return UniformRange<Value>(valueList[0], valueList[1]);
	}
}
}
