/*---------------------------------------------------------------
	FILE: RandomGenerator.h
	USE: See above.

   Part of the MRPT Library
   ISA - Universidad de Malaga - http://www.isa.uma.es
  ---------------------------------------------------------------*/
#ifndef RandomGenerator_H
#define RandomGenerator_H

#include <MRPT/UTILS/utils_defs.h>
#include <MRPT/UTILS/CMatrixTemplateNumeric.h>

namespace UTILS
{
	class CMatrix;
	class CMatrixD;

	/** A namespace of pseudo-random numbers genrators of diferent distributions.
	 */
	namespace RandomGenerator
	{
		/** Generate a normalized normally distributed pseudo-random number.
		 *  \param likelihood If desired, pass a pointer to a double which will receive the likelihood of the given sample to have been obtained, that is, the value of the normal pdf at the sample value.
		 */
		float  normalizedGaussian( double *likelihood = NULL);

		/** Generate a normally distributed pseudo-random number.
		 * \param mean The mean value of desired normal distribution
		 * \param std  The standard deviation value of desired normal distribution
		 */
		float  RandomNormal( float mean = 0, float std = 1.0f);

		/** Generate a uniformly distributed pseudo-random number.
		 */
		float  RandomUni( float min, float max);

		/** Generate a uniformly distributed pseudo-random number.
		 */
		double  RandomUni( double min, double max);

		/** Randomize the generators.
		 *   A seed can be providen, or a current-time based seed can be used (default)
		 */
		void	 Randomize(long seed);
		void	 Randomize();


		/** Generate multidimensional random samples according to a given covariance matrix.
		 * \exception std::exception On invalid covariance matrix
		 * \sa randomNormalMultiDimensionalMany
		 */
		template <class T>
		void  randomNormalMultiDimensional(
			const CMatrixTemplateNumeric<T>		&cov,
			std::vector<T>						&out_result)
		{
			ASSERT_(cov.getRowCount() == cov.getColCount() );

			size_t						dim = cov.getColCount();
			CMatrixTemplateNumeric<T>	Z,D;

			MRPT_TRY_START;

			// Set size of output vector:
			out_result.resize(dim,0);

			/** Computes the eigenvalues/eigenvector decomposition of this matrix,
			*    so that: M = Z · D · Z<sup>T</sup>, where columns in Z are the
			*	  eigenvectors and the diagonal matrix D contains the eigenvalues
			*    as diagonal elements, sorted in <i>ascending</i> order.
			*/
			cov.eigenVectors( Z, D );

			// Scale eigenvectors with eigenvalues:
			D.Sqrt();
			Z = Z * D;

			for (size_t i=0;i<dim;i++)
			{
				float	rnd = normalizedGaussian();

				for (size_t d=0;d<dim;d++)
				{
					// Debug:
					ASSERT_(!MRPT_OS::isNaN(Z(d,i)));
					out_result[d]+= ( Z(d,i)*rnd );
				}
			}

			MRPT_TRY_END_WITH_CLEAN_UP( \
				printf("\nEXCEPTION: Dumping variables for debuging:\n"); \
				std::cout << "Z:\n" << Z << "D:\n" << D << "Cov:\n" << cov; \
				try \
				{ \
					cov.eigenVectors(Z,D); \
					std::cout << "Original Z:" << Z << "Original D:" << D; \
				} \
				catch(...)  {}; \
				);

		}


		/** Generate a given number of multidimensional random samples according to a given covariance matrix.
		 * \param cov The covariance matrix where to draw the samples from.
		 * \param desiredSamples The number of samples to generate.
		 * \param samplesLikelihoods If desired, set to a valid pointer to a vector, where it will be stored the likelihoods of having obtained each sample: the product of the gaussian-pdf for each independent variable.
		 * \param ret The output list of samples
		 *
		 * \exception std::exception On invalid covariance matrix
		 *
		 * \sa randomNormalMultiDimensional
		 */
		void  randomNormalMultiDimensionalMany(
			const CMatrixD				&cov,
			size_t						desiredSamples,
			std::vector<vector_double>	&ret,
			vector_double				*samplesLikelihoods = NULL);

		/** Generate a given number of multidimensional random samples according to a given covariance matrix.
		 * \param cov The covariance matrix where to draw the samples from.
		 * \param desiredSamples The number of samples to generate.
		 * \param samplesLikelihoods If desired, set to a valid pointer to a vector, where it will be stored the likelihoods of having obtained each sample: the product of the gaussian-pdf for each independent variable.
		 * \param ret The output list of samples
		 *
		 * \exception std::exception On invalid covariance matrix
		 *
		 * \sa randomNormalMultiDimensional
		 */
		void  randomNormalMultiDimensionalMany(
			const CMatrix				&cov,
			size_t						desiredSamples,
			std::vector<vector_float>	&ret,
			vector_double				*samplesLikelihoods = NULL);

	}

} // End of namespace

#endif
