//
// LiDIA - a library for computational number theory
//   Copyright (c) 1995 by the LiDIA Group
//
// File        : bil_alg3.c
// Author      : Werner Backes (WB), Thorsten Lauer (TL) 
// Last change : WB/TL, Oct 06 1996, initial version
//

#if defined(HAVE_MAC_DIRS) || defined(__MWERKS__)
#include <LiDIA:bigrational.h>
#include <LiDIA:bigfloat_matrix.h>
#include <LiDIA:bigfloat_lattice.h>
#include <LiDIA:bigint_lattice.h>
#else
#include <LiDIA/bigrational.h>
#include <LiDIA/bigfloat_matrix.h>
#include <LiDIA/bigfloat_lattice.h>
#include <LiDIA/bigint_lattice.h>
#endif

void quad_erg(math_matrix<bigrational> &, const math_matrix<bigint> &);
void diag_min(bigint &, const math_matrix <bigint> &);
long special_diag_min(base_matrix <bigrational> &);
void sort_columns(base_matrix <bigint> &, base_matrix <bigfloat> &);
void auszaehlen_jurk(bigrational &, math_matrix<bigint> &, const math_matrix<bigint> &, const math_matrix<bigrational> &, const math_matrix<bigint> &);
void cho_from_quad(base_matrix<bigfloat> &, const base_matrix<bigrational> &);
int is_null(const math_vector <bigint> &);

#ifdef __GNUG__

template <class T>
struct disp_v {
	unsigned char f;
	T *d;
	lidia_size_t l;
	lidia_size_t c;
};

#define VGET_P(a,b,t) (a=((struct disp_v <t> *)(&(b)))->d)
#define VGET_L(a,b,t) (a=((struct disp_v <t> *)(&(b)))->l)
#define VGET_C(a,b,t) (a=((struct disp_v <t> *)(&(b)))->c)

template <class T>
struct disp_m {
	lidia_size_t r;
	lidia_size_t c;
	T **v;
};

#define MGET_R(a,b,t) (a=((struct disp_m <t> *)(&(b)))->r)
#define MGET_C(a,b,t) (a=((struct disp_m <t> *)(&(b)))->c)
#define MGET_P(a,b,t) (a=((struct disp_m <t> *)(&(b)))->v)

#else

#define VGET_P(a,b,t) (a=(b).get_data_address())
#define VGET_L(a,b,t) (a=(b).size())
#define VGET_C(a,b,t) (a=(b).capacity())

#define MGET_R(a,b,t) (a=(b).get_no_of_rows())
#define MGET_C(a,b,t) (a=(b).get_no_of_columns())
#define MGET_P(a,b,t) (a=(b).get_data_address())

#endif

// Algorithmus 1 berechnet die Matrix Q fuer die quadratisch Form 
// aus einer positiv definiten Matrix A (obere Dreiecksmatrix)

void
quad_erg(math_matrix<bigrational> &Q, const math_matrix<bigint> &A)
{ 
	debug_handler("bigint_lattice", "in function quad_erg(Q, A)");

	lidia_size_t n;
	MGET_R(n,Q,bigrational);

	if (n != Q.get_no_of_columns() || n != A.get_no_of_rows() || n != A.get_no_of_columns())
		lidia_error_handler("bigint_lattice","function quad_erg(Q, A) - "
				"Matrices have incorrect number of rows or columns");

	lidia_size_t i, j, l;
	bigrational diag, **q;
	MGET_P(q,Q,bigrational);
	
	for (i = 0; i < n; i++)
		for(j = i; j < n; j++)
			q[i][j].assign(A.member(i,j));

	for (i = 0; i < n; i++) {
		diag.assign(q[i][i]);                  //diag = Q[i][i]

		for (j = i + 1; j < n; j++) { 
			q[j][i].assign(q[i][j]);           //Q[j][i] = Q[i][j]
			divide(q[i][j],q[i][j],diag);      //Q[i][j] = Q[i][j] / Q[i][i]
		}

		for (j = i + 1; j < n; j++) {
			for (l = j; l < n; l++) { 
				multiply(diag,q[j][i],q[i][l]);
				subtract(q[j][l],q[j][l],diag); //Q[j][l] = Q[j][l] - Q[j][i] * Q[i][l]
			}
		}
	} 
}



void
diag_min(bigint &erg, const math_matrix <bigint> &A)
{
	debug_handler("bigint_lattice", "in function diag_min(erg, A)");

	long j, rows, columns, tmp = 0;
	bigint **a;
	MGET_R(rows,A,bigint);
	MGET_C(columns,A,bigint);
	MGET_P(a,A,bigint);

	if(rows < columns)
		j = rows;
	else
		j = columns;

	for(register long i=1; i<j; i++) {
		if(a[tmp][tmp].compare(a[i][i]) > 0) {
			tmp = i;
		}
	}
	erg.assign(a[tmp][tmp]);
}


// kleinstes l bestimmen, so dass A[0][0] <= A[j][j] fuer alle j
// mit l <= j <= k, wobei l >= 1
long 
special_diag_min(base_matrix <bigrational> &A)
{
	debug_handler("bigint_lattice", "in function special_diag_min(A)");

	long j, rows;
	bigrational **a;
	MGET_R(rows,A,bigrational);
	MGET_P(a,A,bigrational);
	
	j = rows;

	while(--j > 0)
		if(a[0][0].compare(a[j][j]) >= 0)
			return (j+1);
	return 1;
}



void
cho_from_quad(base_matrix<bigfloat> &R, const base_matrix<bigrational> &Q)
{
	long n;
	MGET_R(n,Q,bigrational);

	if (n != Q.get_no_of_columns() || n != R.get_no_of_rows() || n != R.get_no_of_columns())
		lidia_error_handler("bigint_lattice","function cho_from_quad(R,Q) - "
				"Matrices have incorrect number of rows or columns");
	long i,j;
	bigfloat **r;
	bigrational **q;

	MGET_P(q,Q,bigrational);
	MGET_P(r,R,bigfloat);
	
	for(i=0;i<n;i++) {
		for(j=0;j<i;j++)
			r[i][j].assign_zero();
		
		sqrt(r[i][i],q[i][i]);
		
		for(j=i+1;j<n;j++)
			multiply(r[i][j],r[i][i],q[i][j]);
	}
}



//B sei Nullmatrix
void 
sort_columns(base_matrix <bigint> &B, base_matrix <bigfloat> &R)
{
	debug_handler("bigint_lattice", "in function sort_columns(R)");

	const long columns = R.get_no_of_columns();

	B.resize(columns,columns);       // Anpassen von Zeilen und Spalten, falls noetig

	struct sort {
		long pos;
		bigfloat lenght;
	};
 
	sort *A=new sort[columns];  // Anzahl der Spalten
	bigfloat len;
	math_vector <bigfloat> tmp1;
	tmp1.set_capacity(columns);
  
	long i = 0, j, akt;

	while(i < columns) {
		j = i;
		akt = i-1;
		R.column_vector(tmp1,i); 
		len = sum_of_squares(tmp1);

		while(akt >= 0) {
			if((A[akt].lenght).compare(len) < 0) {
				(A[j].lenght).assign(A[akt].lenght);
				A[j].pos = A[akt].pos;
				akt--;
				j--;
			} else 
				break;
		}
		A[j].pos = i;
		(A[j].lenght).assign(len);
		i++;
	}
	for(i=0; i<columns; i++)
		B.sto(A[i].pos,i,1);
}
 

void auszaehlen_jurk(bigrational &c, math_matrix<bigint> &RES, const math_matrix<bigint> &C, const math_matrix<bigrational> &Q, const math_matrix<bigint> &B)
{
	debug_handler("bigint_lattice", "auszaehlen_jurk(c,RES,C,Q,B)");
	lidia_size_t k,cr,br;
	MGET_R(k,Q,bigrational);
	MGET_R(cr,C,bigint);
	MGET_R(br,B,bigint);
	long i = k-1, erg, j, anzahl = 0;
	int  flag = 1;
	bigint ganz, ganz2, *O, *Z, *Y, *X;
	bigfloat z, tmpfloat;
	bigrational helpf, tmp, length, **q, *S, *T;
	math_vector <bigrational> Stmp(k,k), Ttmp(k,k);
	math_vector <bigint> Otmp(k,k), Ztmp(k,k), Ytmp(cr,cr), Xtmp(br,br);

	VGET_P(S,Stmp,bigrational);
	VGET_P(T,Ttmp,bigrational);
	VGET_P(O,Otmp,bigint);
	VGET_P(Z,Ztmp,bigint);
	VGET_P(X,Xtmp,bigint);
	VGET_P(Y,Ytmp,bigint);
	
	RES.set_no_of_columns(cr);

	MGET_P(q,Q,bigrational);

	length.assign(c);

	do {             // 1.do-while-Schleife
		do {             // 2.do-while-Schleife
			if(flag) {       // flag gleich 1: (2) fortfahren
				subtract(helpf,length,T[i]);
				if(helpf.is_lt_zero())
					i++;
				else {
					divide(tmp,helpf,q[i][i]);
					z.assign(tmp);
					sqrt(tmpfloat,z);
					ceil(z,tmpfloat);
					z.bigintify(ganz);
					ceil(ganz2,(bigfloat)S[i]); // MM
					add(ganz2,ganz,ganz2);
					inc(ganz2);
					negate(Z[i],ganz2);        // untere Schranke der i-ten Komponente ist erster Wert
		
					subtract(O[i],ganz,floor(S[i]));         // obere Schranke der i-ten Komponente
				} // else
			}// if(flag)

			do {       // 3.do-while-Schleife
				inc(Z[i]);
				if(O[i].compare(Z[i]) >= 0) 
					break; 
				else 
					i++;
			} while(1); // Ende der 3.do-while-Schleife

			if(i == 0) 
				break;   // (7)-->(10)
	  
			flag = 1;
			erg = i--;                 // naechstkleinere Komponente

			tmp.assign_zero();

			for(j=erg; j<k; j++) {      // S berechnen
				multiply(helpf,q[i][j],Z[j]);
				add(tmp,tmp,helpf);
			}
			S[i].assign(tmp);
			
			add(helpf,Z[erg],S[erg]);       // T[i] berechnen
			square(helpf,helpf);
			multiply(helpf,helpf,q[erg][erg]);
			add(T[i],helpf,T[erg]);
		} while(1); // Ende der 2.do-while-Schleife

		if(is_null(Ztmp)) {    // (10) Ausgabe + Ende
			c.assign(length);
			return;
		} else {         // kein Nullvektor der Koeffizienten
			add(helpf,Z[0],S[0]); // T berechnen
			square(helpf,helpf);

			multiply(helpf,helpf,q[0][0]);
			add(helpf,helpf,T[0]);

			erg = helpf.compare(length);
			if(erg < 1) {
				multiply(Ytmp,C,Ztmp); // Loesung transformieren
				multiply(Xtmp,B,Ytmp); // Vektor bzgl. Ausgangsbasis ermitteln

				if(erg < 0) {    // neuer Vektor kleiner
					length.assign(helpf);   // neue kuerzeste Laenge
					anzahl = 1;
					RES.set_no_of_rows(anzahl);
					RES.sto_row_vector(Ytmp,Ytmp.size(),0);
				} else {   // erg == 0: weitere Loesung bei gleicher Laenge
					RES.set_no_of_rows(anzahl+1);
					RES.sto_row_vector(Ytmp,Ytmp.size(),anzahl);
					anzahl++;
				}
			}//if(erg<1)
		} //else
		flag = 0; // flag gleich 0: (5) fortfahren
	} while(1); // Ende der 1.do-while-Schleife
}


int is_null(const math_vector <bigint> &V)
{
	lidia_size_t l;
	bigint *v;
	VGET_L(l,V,bigint);
	VGET_P(v,V,bigint);

	while(--l >= 0)
		if(!(v[l].is_zero()))
			return 0;
	
	return 1;
}


lidia_size_t
bigint_lattice::shortest_vector(math_matrix <bigint> & RES, lidia_size_t prec)
{
	debug_handler("bigint_lattice", "shortest_vector(RES, T, prec)");
	if(check_basis()) { 
		long dim, old_prec, i, j;
		int l;
		bool mtrans,gf;

		bigint len_int;
		bigrational len_rat;
		bigfloat dummy;

		math_matrix <bigint> A(columns,columns), T(columns,columns);
		math_matrix <bigint> B(rows,columns), M(*this);
		math_matrix <bigrational> Q(columns,columns);

		mtrans = chk_trans();
			if(mtrans)
			M.trans();
	
		gf=check_gram();
		if(gf) {
			for(i=0; i<columns; i++)
				T.sto(i,i,1);

			A.assign(M);
		} else {
			// LLL-Reduktion
			bigint_lattice L(M);
			L.lll(T,0.99);

			::multiply(B, M, T);
			// Erstellen der quadratischen Form
			::multiply(A,::trans(B),B);
		}
		
		diag_min(len_int,A);
		
		// Cholesky-Zerlegung
		quad_erg(Q,A);

		dim = special_diag_min(Q);

		if(dim == 1) {              // fertig 
			T.set_no_of_columns(1);
			::multiply(RES,M,T);
			if(mtrans)
				RES.trans();
			Q[0][0].intify(l);
			return l;
		}

		// weitere Deklarationen
		bigint_matrix U(dim,dim), S(dim,dim);
		math_matrix <bigfloat> R(dim,dim), INV(dim,dim);

		old_prec = dummy.get_precision();
		dummy.precision(prec);
  
		Q.set_no_of_rows(dim);
		Q.set_no_of_columns(dim);

		cho_from_quad(R,Q);

		invert(INV,R);
		
		::trans(INV,INV);

		bigfloat_lattice F(INV);
		F.set_basis_flag();
		F.lll(U,0.99); // INV ist lll-reduziert

		for(i=0; i<dim; i++)
			for(j=0; j<dim; j++)
				R[i][j].assign(U[i][j]);
		
		::multiply(INV,INV,R);

		sort_columns(S,INV);
		
		::multiply(U,U,S);

		S.adj(U); // S: Inverse von U
  
		::trans(U,S);

		T.set_no_of_columns(dim);

		::multiply(T,T,U); //T: Produkt aller Transformationsmatrizen bzgl B
		::multiply(B,M,T);

		// Erstellen der quadratischen Form
		::multiply(A,::trans(B),B);

		// Berechnung der quadratischen Ergaenzung
		quad_erg(Q,A);

		dim = special_diag_min(Q);

		if(dim == 1) {             // fertig
			T.set_no_of_columns(1);
			::multiply(RES,M,T);
			if(mtrans)
				RES.trans();
			Q[0][0].intify(l);
			return l;
		}
		
		Q.set_no_of_rows(dim);
		Q.set_no_of_columns(dim);
		T.set_no_of_columns(dim);

		// Auszaehlen
		len_rat.assign(len_int);
		auszaehlen_jurk(len_rat,RES,T,Q,M);
		
		if(gf) {
			RES.resize(1,1);
			RES.sto(0,0,0);
		} else {
			::multiply(RES,M,::trans(RES));
			if(mtrans)
				RES.trans();
		}
  
		len_rat.intify(l);

		dummy.precision(old_prec);
  
		return l;
	}
}


lidia_size_t 
shortest_vector(const bigint_lattice & L, math_matrix <bigint> & RES, lidia_size_t prec)
{
	bigint_lattice X(L);
	return X.shortest_vector(RES, prec);
}

