/*
 * Copyright (C) Volition, Inc. 1999.  All rights reserved.
 *
 * All source code herein is the property of Volition, Inc. You may not sell 
 * or otherwise commercially exploit the source or things you created based on the 
 * source.
 *
*/ 

#include "math/staticrand.h"
#include "math/vecmat.h"
#include "utils/Random.h"

bool Semirand_inited = false;
unsigned int Semirand[SEMIRAND_MAX];

/**
 * @brief Initialize Semirand array. Doesn't have to be called.
 */
void init_semirand()
{
	Semirand_inited = true;

	// Originally this made a 30-bit rand by sticking two 15-bit rands from myrand() together. Instead we trim Random::next() down to size.
	for (auto & number : Semirand)
		number = (unsigned) (util::Random::next() & STATIC_RAND_MAX);
}

// TODO: figure out what to do with these
/**
 * @brief Return a pseudo random 32 bit value given a reasonably small number.
 *
 * @param num Seed input number
 * @return Pseudo random 32 bit value
 */
int static_rand(int num)
{
	if (num < 0)
		num *= -1;

	if (!Semirand_inited)
		init_semirand();

	const unsigned int num_unsigned = num;
	auto a = num_unsigned & (SEMIRAND_MAX - 1);
	auto b = (num_unsigned >> SEMIRAND_MAX_LOG) & (SEMIRAND_MAX - 1);
	auto c = (num_unsigned >> (2 * SEMIRAND_MAX_LOG)) & (SEMIRAND_MAX - 1);

	return Semirand[a] ^ Semirand[b] ^ Semirand[c];
}

/**
 * @brief Return a random float in 0.0f .. 1.0f- (ie, it will never return 1.0f).
 *
 * @param num Seed input number
 * @return Random value in 0.0f .. 1.0f- (ie, it will never return 1.0f).
 */
float static_randf(int num)
{
	unsigned int	a = static_rand(num);
	return (a & 0xffff) / 65536.0f;
}

/**
 * @brief Return a random integer within a range. Note: min and max are inclusive
 *
 * @param num Seed input number
 * @param min Minimum range integer to return
 * @param max Maximum range integer to return
 * @return Random integer within the range
 */
int static_rand_range(int num, int min, int max)
{
	int	rval = static_rand(num);
	rval = (rval % (max - min + 1)) + min;
	CLAMP(rval, min, max);
	return rval;
}

/**
 * @brief Return a random float within a range.
 * Note: min and max are inclusive
 *
 * @param num Seed input number
 * @param min Minimum range float to return
 * @param max Maximum range float to return
 * @return Random float within the range
 */
float static_randf_range(int num, float min, float max)
{
	float rval = static_randf(num);
	rval = rval * (max - min) + min;

	return rval;
}

/**
 * @brief Create a random, normalized vector in unit sphere
 *
 * @param num Seed input number
 * @param vp Vector
 */
void static_randvec(int num, vec3d *vp)
{
	vp->xyz.x = static_randf(num) - 0.5f;
	vp->xyz.y = static_randf(num+1) - 0.5f;
	vp->xyz.z = static_randf(num+2) - 0.5f;

	vm_vec_normalize_quick(vp);
}

/**
 *
 * @brief Create a random, unnormalized vector in the (half) unit cube
 *
 * @param num Seed input vector
 * @param vp Vector
 */
void static_randvec_unnormalized(int num, vec3d* vp)
{
	vp->xyz.x = static_randf(num) - 0.5f;
	vp->xyz.y = static_randf(num + 1) - 0.5f;
	vp->xyz.z = static_randf(num + 2) - 0.5f;
}

/**
 * @brief Randomly perturb a vector around a given (normalized vector) or optional orientation matrix.
 *
 * @param num
 * @param out
 * @param in
 * @param max_angle
 * @param orient
 */
void static_rand_cone(int num, vec3d *out, const vec3d* const in, float max_angle, const matrix* const orient)
{
	vec3d t1, t2;
	const matrix *rot;
	matrix m;

	// get an orientation matrix
	if(orient != nullptr){
		rot = orient;
	} else {
		Assertion(in != nullptr && vm_vec_is_normalized(in), "input vector must be normalized!");
		vm_vector_2_matrix_norm(&m, in, nullptr, nullptr);
		rot = &m;
	}
	
	// axis 1
	vm_rot_point_around_line(&t1, in, fl_radians(static_randf_range(num,-max_angle, max_angle)), &vmd_zero_vector, &rot->vec.fvec);
	
	// axis 2
	vm_rot_point_around_line(&t2, &t1, fl_radians(static_randf_range(num+1,-max_angle, max_angle)), &vmd_zero_vector, &rot->vec.rvec);

	// axis 3
	vm_rot_point_around_line(out, &t2, fl_radians(static_randf_range(num+2,-max_angle, max_angle)), &vmd_zero_vector, &rot->vec.uvec);
}

//generates a random vector in a cone, with a min amd max angle. 
//Clone of vm_vec_random_cone overload of the same function, adapted to use static_randf_range
void static_rand_cone(int num, vec3d* out, const vec3d* const in, float min_angle, float max_angle, const matrix* const orient)
{
	vec3d temp;
	const matrix* rot;
	matrix m;

	if (max_angle < min_angle) {
		std::swap(min_angle, max_angle);
	}

	// get an orientation matrix
	if (orient != nullptr) {
		rot = orient;
	} else {
		Assertion(in != nullptr && vm_vec_is_normalized(in), "input vector must be normalized!");
		vm_vector_2_matrix_norm(&m, in, nullptr, nullptr);
		rot = &m;
	}

	// Get properly distributed spherical coordinates (DahBlount)
	// This might not seem intuitive, but the min_angle is the angle that will have a larger z coordinate
	float z = static_randf_range(num, cosf(fl_radians(max_angle)), cosf(fl_radians(min_angle))); // Take a 2-sphere slice
	float phi = static_randf_range(num+1, 0.0f, PI2);
	vm_vec_make(&temp, sqrtf(1.0f - z * z) * cosf(phi), sqrtf(1.0f - z * z) * sinf(phi), z); // Using the z-vec as the starting point

	vm_vec_unrotate(out, &temp, rot); // We find the final vector by rotating temp to the correct orientation
}


/////////////////////////////////////////////////////////////////////
// Alternate random number generator, that doesn't affect rand() sequence
/////////////////////////////////////////////////////////////////////
constexpr unsigned int RND_MASK = 0x6000;
constexpr int RND_MAX = 0x7fff;
unsigned int Rnd_seed = 1;

/**
 * @brief Seed the alternative random number generator.
 * Doesn't have to be called.
 *
 * @param seed Seed input number
 */
void init_static_rand_alt(int seed)
{
	Rnd_seed = seed;
}

/**
 * @brief Get a random integer between 1 and RND_MAX.
 *
 * @return Random integer between 1 and RND_MAX
 */
int static_rand_alt()
{
	static unsigned int x=Rnd_seed;
	unsigned int old_x = x;
	x >>= 1u;
	if ( old_x & 1u ) {
		x ^= RND_MASK;
	}
	return x;
}

/**
 * @brief Get a random integer between 0 and 1.0.
 *
 * @return Random float between 0 and 1.0
 */
float static_randf_alt()
{
	int r = static_rand_alt();
	return i2fl(r)/RND_MAX;
}
