#include <v_lib.h>

/*
**	Epsilon for decimal -> fraction conversion.
*/
#define	RATIO_EPSILON	1e-3

/*
**	TODO: rewrite it, it performs task by BRUTE FORCE !
*/
double	V_LOCAL
FindQuotent(double d)
{
	int i;
	double q=1,n,diff,dl,du,min,min_q;
	min=MAXDOUBLE;
	/* DUMB !!! */
	for(i=0;i<1000000;i++)	/* How about P6-class CPU ? */
	{
		n=d*q;
		dl=fabs(n-floor(n));
		du=fabs(n-ceil(n));
		diff=dl < du ? dl : du;
		if(diff * q <= RATIO_EPSILON)
			return q;
		if(diff<min)
		{
			min=diff;
			min_q=q;
		}
		q+=1;
	}
	return min_q;
}

void	V_GLOBAL
umDecimalToNormal(vec_t ratio,vec_t *numerator,vec_t *quotent)
{
	double n,q;
	double sign;
	double r;
	if(ratio<0)
	{
		r=-(double)(ratio);
		sign=-1;
	}
	else
	{
		r=(double)ratio;
		sign=1;
	}
	q=FindQuotent(r);
	n=(r * q);
	*numerator=(vec_t)(sign * n);
	*quotent=(vec_t)q;
}

/*
**	Timelimit of creation of objects with random numbers.
*/
#define		RANDMAT_TIMELIMIT				10000

/*
**	Random seed. Initialized once per process.
*/
V_LOCAL		unsigned	Seed=0;

/*
**	Returns random integer number in range [Lower;Upper] (with linear
**	probability distrubution).
*/
vec_t	V_GLOBAL
umGetRandom(int Lower,int Upper,vboolean ExcludeZero)
{
	int num,range;
	if(Lower>Upper)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"umGetRandom(%d,%d,%d): incorrect bounds.\n",
			Lower,Upper,ExcludeZero);
#endif	/* CHECKED_BUILD */
		return (vec_t)Lower;
	}
	range=Upper-Lower;
	switch(range)
	{
		case 0:
			return (vec_t)Lower;
		case 1:
			if(ExcludeZero)
			{
				if(!Lower)
					return (vec_t)Upper;
				if(!Upper)
					return (vec_t)Lower;
			}
			break;
		default:
			break;
	}
	if(!Seed)
	{
		timer_t t;
		timerStart(&t);
		Seed=t.time_start;	/* hack; todo: introduce timeGetOSTime() */
		srand(Seed);
	}
	range++;
	do
	{
		num=Lower + (rand() % range);
	}	while(ExcludeZero && !num);
	return (vec_t)num;
}

/*
**	Returns vector with random elements.
*/
vboolean	V_GLOBAL
umMakeRandomVector(vector_t *dest,int Lower,int Upper,
	vboolean ExcludeZeros)
{
	u32_t i;
	if(!dest /* || dest->magic!=VECTOR_MAGIC; PARANOJA */ )
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"umMakeRandomVector(%X,%d,%d): incorrect dest matrix.\n",
			dest,Lower,Upper);
#endif	/* CHECKED_BUILD */
		return false;
	}
	if(Lower>Upper)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"umMakeRandomVector(%X,%d,%d): incorrect bounds.\n",
			dest,Lower,Upper);
#endif	/* CHECKED_BUILD */
		return false;
	}
	for(i=0;i<dest->size;i++)
		dest->elements[i]=umGetRandom(Lower,Upper,ExcludeZeros);
	return true;
}

/*
**	Returns matrix with random elements. If ExcludeSingular, then
**	dest granted to be non-singular.
*/
vboolean	V_GLOBAL
umMakeRandomMatrix(matrix_t *dest,int Lower,int Upper,
	vboolean ExcludeZeros,vboolean ExcludeSingular)
{
	u32_t r,c;
	timer_t watchdog_timer;
	if(!dest /* || dest->magic!=MATRIX_MAGIC */ )
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"umMakeRandomMatrix(%X,%d,%d,%d): incorrect dest matrix.\n",
			dest,Lower,Upper,ExcludeSingular);
#endif	/* CHECKED_BUILD */
		return false;
	}
	if(Lower>Upper)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"umMakeRandomMatrix(%X,%d,%d,%d): incorrect bounds.\n",
			dest,Lower,Upper,ExcludeSingular);
#endif	/* CHECKED_BUILD */
		return false;
	}
	timerStart(&watchdog_timer);
	do
	{
		if(timerMilliseconds(&watchdog_timer)>=RANDMAT_TIMELIMIT)
		{
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_ERROR,"umMakeRandomMatrix(%X,%d,%d,%d):"
				" RANDMAT_TIMELIMIT reached, no acceptable solution.\n",
				dest,Lower,Upper,ExcludeSingular);
#endif		/* CHECKED_BUILD */
			return false;
		}
		for(r=0;r<dest->rows;r++)
			for(c=0;c<dest->cols;c++)
				dest->cell[r][c]=umGetRandom(Lower,Upper,ExcludeZeros);
	}	while(ExcludeSingular && IsZero(matDeterm(dest,true)));
	return true;
}

/*
**	Compares two vectors.
**	Assumes all args are valid.
*/
vboolean	V_LOCAL
vecIsEqual(vector_t *v1,vector_t *v2)
{
	u32_t i;
	for(i=0;i<v1->size;i++)
		if(!IsZero(v1->elements[i]-v2->elements[i]))
			return false;
	return true;
}

/*
**	Compares two matrices.
**	Assumes all args are valid.
*/
vboolean	V_LOCAL
matIsEqual(matrix_t *m1,matrix_t *m2)
{
	u32_t r,c;
	for(r=0;r<m1->rows;r++)
		for(c=0;c<m1->cols;c++)
			if(!IsZero(m1->cell[r][c]-m2->cell[r][c]))
				return false;
	return true;
}

/*
**	Destroys all vectors in array.
*/
void	V_GLOBAL
umDestroyRandomVectorSet(vector_t **arr,u32_t NumVectors)
{
	u32_t i;
	if(!arr)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"umDestroyRandomVectorSet(): NULL argument.\n");
#endif	/* CHECKED_BUILD */
		return;
	}
	for(i=0;i<NumVectors;i++)
		if(arr[i])
			free(arr[i]);
	free(arr);
}

/*
**	Destroys all matrices in array.
*/
void	V_GLOBAL
umDestroyRandomMatrixSet(matrix_t **arr,u32_t NumMatrices)
{
	u32_t i;
	if(!arr)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"umDestroyRandomMatrixSet(): NULL argument.\n");
#endif	/* CHECKED_BUILD */
		return;
	}
	for(i=0;i<NumMatrices;i++)
		if(arr[i])
			free(arr[i]);
	free(arr);
}

/*
**	Returns set of random vectors.
*/
vboolean	V_GLOBAL
umCreateRandomVectorSet(vector_t ***vSet,u32_t NumVectors,u32_t Dimension,
	int Lower,int Upper,vboolean ExcludeZeros)
{
	u32_t i,j;
	vector_t **dest;
	timer_t watchdog_timer;
	if(!vSet || !NumVectors || !Dimension || Lower>Upper)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"umCreateRandomVectorSet(%X,%u,%u,%d,%d): "
			"incorrect parameter(s).\n",vSet,NumVectors,Dimension,Lower,Upper);
#endif	/* CHECKED_BUILD */
		return false;
	}
	dest=(vector_t**)malloc(NumVectors*sizeof(vector_t*));
	v_zero(dest,NumVectors*sizeof(vector_t*));
	for(i=0;i<NumVectors;i++)
		dest[i]=vecAlloc(Dimension);
	timerStart(&watchdog_timer);
	for(i=0;i<NumVectors;)
	{
		if(timerMilliseconds(&watchdog_timer)>=RANDMAT_TIMELIMIT)
		{
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_ERROR,"umCreateRandomVectorSet(%X,%u,%u,%d,%d): "
				"RANDMAT_TIMELIMIT reached, no acceptable solution.\n",vSet,
				NumVectors,Dimension,Lower,Upper);
#endif		/* CHECKED_BUILD */
			umDestroyRandomVectorSet(dest,NumVectors);
			return false;
		}
		/* generate next vector */
		if(!umMakeRandomVector(dest[i],Lower,Upper,ExcludeZeros))
		{
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_ERROR,
				"umCreateRandomVectorSet(%X,%u,%u,%d,%d): "
				"umMakeRandomVector failed at pos %d.\n",
				vSet,NumVectors,Dimension,Lower,Upper,i);
#endif		/* CHECKED_BUILD */
			umDestroyRandomVectorSet(dest,NumVectors);
			return false;
		}
		/* compare it with previous vectors */
		for(j=0;j<i;j++)
			if(vecIsEqual(dest[i],dest[j]))
				break;
		/* if all different - continue with next vector */
		if(i==j)
			i++;
	}	/* for */
	*vSet=dest;
}

/*
**	Returns set of random matrices.
*/
vboolean	V_GLOBAL
umCreateRandomMatrixSet(matrix_t ***mSet,u32_t NumMatrices,u32_t M,u32_t N,
	int Lower,int Upper,vboolean ExcludeZeros,vboolean ExcludeSingular)
{
	u32_t i,j;
	matrix_t **dest;
	timer_t watchdog_timer;
	if(!mSet || !NumMatrices || !M || !N || Lower>Upper)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"umCreateRandomMatrixSet(%X,%u,%u,%u,%d,%d): "
			"incorrect parameter(s).\n",mSet,NumMatrices,M,N,Lower,Upper);
#endif	/* CHECKED_BUILD */
		return false;
	}
	dest=(matrix_t**)malloc(NumMatrices*sizeof(matrix_t*));
	v_zero(dest,NumMatrices*sizeof(matrix_t*));
	for(i=0;i<NumMatrices;i++)
		dest[i]=matAlloc(M,N);
	timerStart(&watchdog_timer);
	for(i=0;i<NumMatrices;)
	{
		if(timerMilliseconds(&watchdog_timer)>=RANDMAT_TIMELIMIT)
		{
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_ERROR,
				"umCreateRandomMatrixSet(%X,%u,%u,%u,%d,%d): "
				"RANDMAT_TIMELIMIT reached, no acceptable solution.\n",
				mSet,NumMatrices,M,N,Lower,Upper);
#endif		/* CHECKED_BUILD */
			umDestroyRandomMatrixSet(dest,NumMatrices);
			return false;
		}
		/* generate next matrix */
		if(!umMakeRandomMatrix(dest[i],Lower,Upper,ExcludeZeros,
			ExcludeSingular))
		{
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_ERROR,
				"umCreateRandomMatrixSet(%X,%u,%u,%u,%d,%d): "
				"umMakeRandomMatrix() failed at pos %d.\n",
				mSet,NumMatrices,M,N,Lower,Upper,i);
#endif		/* CHECKED_BUILD */
			umDestroyRandomMatrixSet(dest,NumMatrices);
			return false;
		}
		/* compare it with previous matrices */
		for(j=0;j<i;j++)
			if(matIsEqual(dest[i],dest[j]))
				break;
		/* if all different - continue with next vector */
		if(i==j)
			i++;
	}	/* for */
	*mSet=dest;
}

/*
**	Converts float into string representation
*/
char_p	V_LOCAL
FormatFloat(char *dest,char *float_fmt,vec_t v,vboolean prepend_sign,
	vboolean out_as_fraction)
{
	vec_t n,q,av;
	av=(vec_t)fabs(v);
	if(av<1e-6)
	{
		if(prepend_sign)
			strcpy(dest," 0");
		else
			strcpy(dest,"0");
		return dest;
	}
	if(out_as_fraction)
	{
		char fmt[256];
		strcpy(fmt,float_fmt);
		strcat(fmt,"/");
		strcat(fmt,float_fmt);
		umDecimalToNormal(av,&n,&q);
		sprintf(dest,fmt,n,q);
	}
	else
	{
		sprintf(dest,float_fmt,av);
	}
	if(v<0)
	{
		memmove(dest+1,dest,strlen(dest)+1);
		dest[0]='-';
	}
	else 
		if(prepend_sign)
		{
			memmove(dest+1,dest,strlen(dest)+1);
			dest[0]='+';
		}
	return dest;
}


/*
**	Dumps array of floats into file.
**	Assumes all args valid (lead,in_between and trail CAN be NULL).
**	Outputs either decimals or fractions.
*/
void	V_LOCAL
umDumpFloatArray(file_t out,vec_t *floats,u32_t num_floats,
	char *float_fmt,char *lead,char *in_between,char *trail,
	vboolean prepend_sign,vboolean out_as_fraction)
{
	u32_t i;
	char stub=0;
	char ds[256];
	/* if(strlen(float_fmt>32)) error - incorrect float format */
	if(!lead)
		lead=&stub;
	if(!in_between)
		in_between=&stub;
	if(!trail)
		trail=&stub;
	fileWrite(out,lead,strlen(lead));
	num_floats--;
	for(i=0;i<num_floats;i++)
	{
		FormatFloat(ds,float_fmt,floats[i],prepend_sign,out_as_fraction);
		filePrintf(out,"%s",ds);
		fileWrite(out,in_between,strlen(in_between));
	}
	FormatFloat(ds,float_fmt,floats[i],prepend_sign,out_as_fraction);
	filePrintf(out,"%s",ds);
	fileWrite(out,trail,strlen(trail));
}

/*
**	Dumps contents of vector vector into file
*/
void	V_LOCAL
umDumpVectorEx(file_t out,vector_t *v,
	char *lead,char *in_between,char *trail,
	vboolean prepend_sign,vboolean out_as_fraction)
{
	if(!out || !v)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"umDumpVector(%u,%X,%s,%s,%s): "
			"incorrect dest or source.\n",
			out,v,strSafe(lead),strSafe(in_between),strSafe(trail));
#endif	/* CHECKED_BUILD */
		return;
	}
	umDumpFloatArray(out,v->elements,v->size,"%g",
		lead,in_between,trail,prepend_sign,out_as_fraction);
}

void	V_GLOBAL
umDumpVector(file_t out,vector_t *v,
	char *lead,char *in_between,char *trail,vboolean prepend_sign)
{
	umDumpVectorEx(out,v,lead,in_between,trail,prepend_sign,false);
}

void	V_GLOBAL
umDumpVectorAsFractions(file_t out,vector_t *v,
	char *lead,char *in_between,char *trail,vboolean prepend_sign)
{
	umDumpVectorEx(out,v,lead,in_between,trail,prepend_sign,true);
}


/*
**	Dumps matrix into file either as decimals or as fractions
*/
void	V_LOCAL
umDumpMatrixEx(file_t out,matrix_t *m,
	char *row_lead,char *row_in_between,char *row_trail,
	char *lead,char *in_between,char *trail,vboolean prepend_sign,
	vboolean out_as_fractions)
{
	u32_t r,num;
	char stub=0;
	if(!out || !m)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"umDumpMatrix(%u,%X,%s,%s,%s,%s,%s,%s): "
			"incorrect dest or source.\n",
			out,m,strSafe(row_lead),strSafe(row_in_between),strSafe(row_trail),
			strSafe(lead),strSafe(in_between),strSafe(trail));
#endif	/* CHECKED_BUILD */
		return;
	}
	if(!row_lead)
		row_lead=&stub;
	if(!row_in_between)
		row_in_between=&stub;
	if(!row_trail)
		row_trail=&stub;
	fileWrite(out,row_lead,strlen(row_lead));
	num=m->rows-1;
	for(r=0;r<num;r++)
	{
		umDumpFloatArray(out,m->cell[r],m->cols,"%g",
			lead,in_between,trail,prepend_sign,out_as_fractions);
		fileWrite(out,row_in_between,strlen(row_in_between));
	}
	umDumpFloatArray(out,m->cell[r],m->cols,"%g",
		lead,in_between,trail,prepend_sign,out_as_fractions);
	fileWrite(out,row_trail,strlen(row_trail));
}

/*
**	Dumps contents of matrix into file
*/
void	V_GLOBAL
umDumpMatrix(file_t out,matrix_t *m,
	char *row_lead,char *row_in_between,char *row_trail,
	char *lead,char *in_between,char *trail,vboolean prepend_sign)
{
	umDumpMatrixEx(out,m,row_lead,row_in_between,row_trail,
		lead,in_between,trail,prepend_sign,false);
}

/*
**	Same as above, but output numbers as fractions
*/
void	V_GLOBAL
umDumpMatrixAsFractions(file_t out,matrix_t *m,
	char *row_lead,char *row_in_between,char *row_trail,
	char *lead,char *in_between,char *trail,vboolean prepend_sign)
{
	umDumpMatrixEx(out,m,row_lead,row_in_between,row_trail,
		lead,in_between,trail,prepend_sign,true);
}

#if 0
/*
**	DEBUG
*/

int main()
{
	file_t f;

	/* matrix_t *m; */

	/*
	m=matAlloc(8,8);
	umMakeRandomMatrix(m,0,1,false,true);
	f=fileOpenWrite("CON");
	umDumpMatrix(f,m,"Dump:\n",NULL,"complete.\n","\t|\t","\t","\t|\n",false);
	fileClose(f);
	printf("Determ: %g\n",matDeterm(m,true));
	*/
	
	/*
	{
		int i;
		f=fileOpenWrite("dets.txt");
		for(i=0;i<10000;i++)
		{
			umMakeRandomMatrix(m,0,1,false,true);
			filePrintf(f,"%g\n",matDeterm(m,true));
		}
		fileClose(f);
	}
	free(m);
	*/

	matrix_t **vs;
	u32_t i,num=27;

	f=fileOpenWrite("CON");
	logStartConsoleLog();
	logUpperLevel(LOG_TRACE);

	if(!umCreateRandomMatrixSet(&vs,num,3,3,0,1,false,true))
	{
		puts("umCreateRandomMatrixSet() failed");
		return -1;
	}
	for(i=0;i<num;i++)
		umDumpMatrix(f,vs[i],NULL,NULL,"============\n",NULL,"\t","\n",false);
		
	umDestroyRandomMatrixSet(vs,num);
	return 0;
}

#endif
