//==============================================================================================
//
//	This file is part of LiDIA --- a library for computational number theory
//
//	Copyright (c) 1994--2001 the LiDIA Group.  All rights reserved.
//
//	See http://www.informatik.tu-darmstadt.de/TI/LiDIA/
//
//----------------------------------------------------------------------------------------------
//
//	$Id$
//
//	Author	: Anja Steih (AS), Stefan Neis (SN)
//	Changes	: See CVS log
//
//==============================================================================================


#ifdef HAVE_CONFIG_H
# include	"config.h"
#endif
#include	"LiDIA/bigint_matrix.h"
#include	"LiDIA/alg_number.h"
#include	"LiDIA/prime_ideal.h"
#include	"LiDIA/Fp_polynomial.h"



#ifdef LIDIA_NAMESPACE
namespace LiDIA {
#endif



#define lidia_debug_handler_l debug_handler_l
#define lidia_debug_handler_c debug_handler_c

//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// Notes:
// arithmetic in the ring (O/pO, +, *) with respect ot a given basis
// {w(1) + pO, w(2) + pO, ..., w(n) + pO)
//
// This is part of a package which realizes the arithmetic in maximal
// orders modulo a principal ideal generated by a prime number, so that
//                                    n               n
// the addition is the same as in (Fp) , that is ((Fp) , +) = (O/pO, +).
// The multiplication is given by a multiplication table w[i][j][k]
// such that wi * wj = sum w[i][j][k] * wk.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Declarations

// dimension is the degree of the number field over Q

static lidia_size_t dimension; // = current_order->degree();
static bigint MODUL;



//-----------------------------------------------------------------------------

class order_mod_p : public base_vector< bigint > {

// This class is the description of an element in O/pO:
//
// An element of O/pO can be interpreted as vector in Fp
// so we inherit the memory management, the vector space
// operations and the I/O from the LiDIA class math_vector.

public:
	order_mod_p() : base_vector< bigint > () {}
	order_mod_p(lidia_size_t n) : base_vector< bigint > (n, n) {}
	order_mod_p(const base_vector< bigint > & v): base_vector< bigint > (v) {}
	~order_mod_p() {}
}; // End of class order_mod_p



// ***************************************************************************

class ideal_mod_p : public bigmod_matrix {
// An ideal in O/pO is generated by k elements of O/pO. These elements
// are given by the coefficients with respect to the basis w1, ..., wn.
// The coefficients are the columns in the Matrix M, so that the matrix
// has dimension n x k.

public:
	ideal_mod_p() : bigmod_matrix(dimension, 1, MODUL) {};
	//creates an ideal_mod_p with one generator
	ideal_mod_p(lidia_size_t k) : bigmod_matrix(dimension, k, MODUL) {}
	//creates an ideal_mod_p with k generators
	ideal_mod_p(const bigmod_matrix &A): bigmod_matrix(A) {}
	~ideal_mod_p() {}
};
// End of class ideal_mod_p



//-----------------------------------------------------------------------------
//
//This is the dimension of the extension field over Q.

static bigint ***w_;
// Pointer to the multiplication table of an order.

static lidia_size_t mutab_already_initialized = 0;
// mutab_already_initialized shows if the multiplication table has been
// initialized.

//-----------------------------------------------------------------------------

// ****************************************************************************
// Removing of order_mod_p
// ****************************************************************************

//-----------------------------------------------------------------------------
// Name:        remove_mult_tab
// Input:	none
// Output:	none
// This function removes the multiplication table of an order.

static void remove_mult_tab()
{
	if (mutab_already_initialized == 1) {
		for (lidia_size_t j = 0; j < dimension; j++) {
			for (lidia_size_t k = 0; k < dimension; k++) {
				delete [] w_[j][k];
			} // End of inner loop
			delete [] w_[j];
		} // End of outer loop
		delete [] w_;
		mutab_already_initialized = 0;
	} // End of if
}



// ****************************************************************************
// Initializations of order_mod_p
// ****************************************************************************

//-----------------------------------------------------------------------------
// Name:        init_mult_tab
// Input:       an Order and a prime number
// Output:      Multiplication table
// This function initializes an order. If another order and a multiplication
// table already have been initialized, order_init deletes this order.
// Then the multiplication table is initialized from Stefan Neis' order.

static void init_mult_tab(const order & Order, const bigint & prime)
{
	MODUL = prime;

	lidia_debug_handler_l("alg_factor.c", "function init_mult_tab 2", 1);
	if (mutab_already_initialized == 1)
		remove_mult_tab();

	dimension = Order.degree();
	mutab_already_initialized = 1;

	w_ = new bigint**[dimension];

	for (lidia_size_t j = 0; j < dimension; j++) {
		w_[j] = new bigint*[dimension];
		for (lidia_size_t k = 0; k < dimension; k++) {
			w_[j][k] = new bigint[dimension];
			for (lidia_size_t l = 0; l < dimension; l++)
				remainder(w_[j][k][l], ((order *)&Order)->MT(j+1, k+1, l+1), prime);
		} // End of for (2)
	} // End of for (1)
}
// End of init_mult_tab



//-----------------------------------------------------------------------------
// Name:     operator +
// Input:    two elements of O/pO
// Output:   sum of two elements of O/pO
//

static order_mod_p operator +(const order_mod_p & a, const order_mod_p & b)
{
	lidia_debug_handler_l("alg_factor.c", "operator + order_mod_p", 1);

	order_mod_p c(dimension);
	lidia_size_t i;
	if (a.size() == b.size()) {
		for (i = 0; i < b.size(); i++) {
			add(c[i], a[i], b[i]);
			if (c[i] >= MODUL) subtract(c[i], c[i], MODUL);
		}
	}
	else
		lidia_error_handler("order_mod_p",
				    "operator+ applied on non-matching arguments");
	return c;
}
// End of operator +
//-----------------------------------------------------------------------------



//-----------------------------------------------------------------------------
static order_mod_p operator -(const order_mod_p & a, const order_mod_p & b)
{
	lidia_debug_handler_l("alg_factor.c", "operator - oder_mod_p", 1);

	order_mod_p c(dimension);
	lidia_size_t i;
	if (a.size() == b.size()) {
		for (i = 0; i < b.size(); i++) {
			subtract(c[i], a[i], b[i]);
			if (c[i] < 0) add(c[i], c[i], MODUL);
		}
	} // End of if
	else
		lidia_error_handler("order_mod_p",
				    "operator+ applied on non-matching arguments");
	return c;
}
// End of operator -
//-----------------------------------------------------------------------------



//-----------------------------------------------------------------------------
// Name:        operator *
// Input:       two elements of an order
// Output:      product of the two elements

static order_mod_p operator * (const order_mod_p & a, const order_mod_p & b)
{

        lidia_debug_handler_l("alg_factor.c", "operator * order-mod_p", 1);

        lidia_size_t i, j, k;
        order_mod_p c(dimension);
        // ***w is a pointer to the multiplication table
        if (!mutab_already_initialized)
		lidia_error_handler("alg_factor.c",
				    "Internal error: mutab not initialized!");
        for (k = 0; k < dimension; k++) {
		for (i = 0; i < dimension; i++) {
			for (j = 0; j < dimension; j++)
				add(c[k], c[k], ((a[i] * b[j]) % MODUL * w_[i][j][k]) % MODUL);
			remainder(c[k], c[k], MODUL);
			if (c[k].is_negative()) add(c[k], c[k], MODUL);
		} // End of for (2)
        } // End of for (1)
        return c;
}
// End of operator *
//-----------------------------------------------------------------------------



//-----------------------------------------------------------------------------
// Name:                operator *
// Input:       ideal I and ideal J
// Output:      the ideal generated by I and J
// This function returns the ideal generated by the ideals I and J.
//

static ideal_mod_p operator *(const ideal_mod_p & I, const ideal_mod_p & J)
{
	debug_handler_l("alg_factor.c", "operator * ideal_mod_p", 3);

	lidia_size_t c0, c1, i, j, k = 0;
	order_mod_p a, b, c;
	base_vector< bigint > col(dimension, dimension);
	c0 = I.get_no_of_columns();
	c1 = J.get_no_of_columns();
	if ((c0 == 1 && I.is_matrix_zero()) ||
	    (c1 == 1 && J.is_matrix_zero()))
		return ideal_mod_p();
	ideal_mod_p K(c0*c1);
	for (i = 0; i < c0; i++) {
		for (j = 0; j < c1; j++) {
			a = I.get_column_vector(i);
			b = J.get_column_vector(j);
			c = a * b;
			// Since c is an order_mod_p and hence reduced,
			// we can simply use sto_column_vector:
			K.sto_column_vector(c, c.get_size(), k);
			k++;
		} // End of for (2)
	} // End of for (1)
	K.image(K);
	return K;
}
// End of operator*
//-----------------------------------------------------------------------------



//-----------------------------------------------------------------------------
// Name:        power
// Input:       element of O/pO a, bigint b
// Output:      element of O/pO
//

static void power(order_mod_p & c, const order_mod_p & a, const bigint & b)
{
	if (b <= 0)
		lidia_error_handler("power(order_mod_p &, const order_mod_p &, bigint)",
				    "Exponent must be positive.");

	c = a;

	if (!b.is_one()) {
		order_mod_p multiplier = a;

		// Note: The bitlength is at least 2, since b == 1
		// is handled above. The first bit of the bits
		// [0,...,bit_length()-1] is used up by
		// the assignment c = a, so the next bit to handle
		// will be the one with index bit_length()-2.

		unsigned int i;

		for (i = b.bit_length()-2; i > 0; i--) {
			c = c * c;
			if (b.bit(i))
				c = c * multiplier;
		}

		// i == 0

		c = c * c;
		if (b.is_odd())
			c = c * multiplier;
	}
}



//-----------------------------------------------------------------------------
// Name:    frobenius
// Input:   element of O/pO
// Output:  element of O/pO
// Frobenius-automorphismus

static order_mod_p frobenius(const order_mod_p & a0)
{
        lidia_debug_handler_l("alg_factor.c", "function_frobenius", 1);
	order_mod_p a;
	power(a, a0, MODUL);
        return (a - a0);
}
// End of frobenius
//-----------------------------------------------------------------------------



static Fp_polynomial minimal_polynomial(base_vector< bigint > & alpha_v,
					const bigmod_matrix & O, lidia_size_t l, order_mod_p & alpha_e)
{

        lidia_debug_handler_l("alg_factor.c", "function minimal_polynomial", 2);

        lidia_size_t n = dimension;

        order_mod_p alpha(n), alpha_k(n);

        bigmod_matrix A_phi(l, l+1, MODUL);
        base_vector< bigint > x(n, n);
        bigint e;

        lidia_size_t *pi;
        //                                              _ _
        // remembers, which standard basis vectors span O/I
        pi = new lidia_size_t[l];


        lidia_size_t j;

        for (j = n-l; j < n; j++) {
                lidia_size_t c = O.get_no_of_rows();
                lidia_size_t i = 0;
                while (i < c && O.member(i, j).is_zero()) i++;
                pi[j-(n-l)] = i;
        } // End of for (1)
	lidia_debug_handler_c("alg_factor.c", "in minimal_polynomial(...)", 2,
			      std::cout << "Die pi's : ";
			      for (j = 0; j < l; j++) std::cout << pi[j] << " " << std::flush;
			      std::cout << "\n");

        // The second column of A_phi is alpha
        for (j = 0; j < l; j++) {
                e = alpha_v.member(j);
                A_phi.sto(j, 1, e);
                alpha[pi[j]] = e;
        } // End of for
	delete[] pi;

        alpha_e = alpha;
	lidia_debug_handler_c("alg_factor.c", "in minimal_polynomial(...)", 2,
			      std::cout << "alpha_e = alpha = " << alpha << std::endl);
	// x is zero right now...

        x[0].assign(1);
        //                                            _ _
        // x is the number 1, we must represent it in O/I

        lidia_size_t g = O.get_no_of_rows();
        lidia_size_t f = O.get_no_of_columns();
        bigmod_matrix MAT(g, f, MODUL);
        {
		bigint tmp_fact;
		MAT.solve(O, x, tmp_fact);
        }
        for (j = n-l; j < n; j++)
		A_phi.sto(j-(n-l), 0, MAT.member(j, MAT.get_no_of_columns()-1));

        alpha_k = alpha;

        for (lidia_size_t l1 = 2; l1 <= l; l1++) {
                alpha_k = alpha_k * alpha;
                bigmod_matrix MAT(g, f, MODUL);
                {
			bigint tmp_fact;
			MAT.solve(O, alpha_k, tmp_fact);
                }
                for (j = n-l; j < n; j++)
			A_phi.sto(j-(n-l), l1,
				  MAT.member(j, MAT.get_no_of_columns()-1));
        } // End of for (1)
	lidia_debug_handler_c("alg_factor.c", "in minimal_polynomial(...)", 2,
			      std::cout << "A_phi = \n" << A_phi << std::endl);

        bigmod_matrix Kern_A_phi;
        Kern_A_phi.kernel(A_phi);
	lidia_debug_handler_c("alg_factor.c", "in minimal_polynomial(...)", 2,
			      std::cout << "before col_step_...Kern_A_phi = \n";
			      std::cout << Kern_A_phi << std::endl);
        Kern_A_phi.image(Kern_A_phi);
	lidia_debug_handler_c("alg_factor.c", "in minimal_polynomial(...)", 2,
			      std::cout << "after col_step_...Kern_A_phi = \n";
			      std::cout << Kern_A_phi << std::endl);

        Fp_polynomial p1;
        // the minimal polynomial has degree <= 1
        p1.set_modulus(MODUL);

        j = l + 1;
        Fp_polynomial p;
        p.set_modulus(MODUL);
        p.set_max_degree(l+1);

        j--;
        while (j >= 0) {
		if ((e = Kern_A_phi.member(j, 0)) != 0 && p.degree() == l+1)
			p.set_max_degree(j);
		p.set_coefficient(e, j);
		j--;
        } // End of while

        p /= p.lead_coeff();
	lidia_debug_handler_c("alg_factor.c", "in minimal_polynomial(...)", 2,
			      std::cout << "MINPOL-(F" << MODUL << ") -->returning: ";
			      std::cout << p << std::endl);
        return p;
}
// End of minimal polynomial
//-----------------------------------------------------------------------------



//-----------------------------------------------------------------------------
static ideal_mod_p the_whole_ring()
{
	lidia_debug_handler_l("alg_factor.c", "function the_whole_ring", 1);

	bigmod_matrix B1(dimension, dimension, MODUL);
	B1.diag(1, 0);
	return ideal_mod_p(B1);
}
// End of the_whole_ring
//-----------------------------------------------------------------------------



//-----------------------------------------------------------------------------
// Name:        operator ==
// Input:       the ideals I and J
// Output:      bool
//

static bool operator == (const ideal_mod_p & I, const ideal_mod_p & J)
{

	lidia_debug_handler_l("alg_factor.c", "operator == ", 1);

	return equal(I, J);
}
// End of operator ==
//-----------------------------------------------------------------------------



//-----------------------------------------------------------------------------
// Name:                operator !=
// Input:       the ideals I and J
// Output:      bool
//

static bool operator != (const ideal_mod_p & I, const ideal_mod_p & J)
{
        lidia_debug_handler_l("alg_factor.c", "operator != ", 1);

        return unequal(I, J);
}
// End of operator !=
//-----------------------------------------------------------------------------



//-----------------------------------------------------------------------------
// Name:    empty

// Input:   Ideal in O/pO
// Output:  bool (true: empty, false: not empty)
// Check, if empty

static bool empty (const ideal_mod_p & I)
{
	if (I.is_matrix_zero()) return true;
	else return false;
}
//-----------------------------------------------------------------------------



//-----------------------------------------------------------------------------
static bigmod_matrix basis_completion(bigmod_matrix const & V)
{
	lidia_size_t n = V.get_no_of_rows(),
		k = V.get_no_of_columns(),
		l = rank(V);

	if (l == n) return V;

	if (l == 0) {
		bigmod_matrix A1(n, n, MODUL);
		A1.diag(1, 0);
		return A1;
	}

	bigmod_matrix A2(n, n, MODUL);
	lidia_size_t j2 = 0;
	for (lidia_size_t j1 = 0; j1 < k; j1++)
		if (!V.is_column_zero(j1)) {
			bigint *a2 = V.get_column(j1);
			A2.sto_column(a2, n, j2);
			delete[] a2;
			j2++;
		}
	bigmod_matrix W = image(V);
	lidia_size_t g2 = W.get_no_of_columns();

	// First handle the first column of W.
	// Matrix W is an image, thus does not contain 0-columns,
	// since (rank == 0) already returned.

	lidia_size_t b = n-1;
	while (b >= 0 && W.member(b, 0) == 0) b--;
	for (j2 = 0; j2 < b; j2++) {
		A2.sto(j2, l, 1);
		l++;
	}

	// Vergleich des i-ten und des (i+1)-ten Spaltenvektors bzgl.
	// ihrer letzten Eintraege != 0 und evtl. Erzeugen des
	// entsprechenden standard_basis_vectors.
	for (lidia_size_t i = 0; i < g2-1; i++) {
		lidia_size_t c = b;
		b = n-1;
		while (b >= 0 && W.member(b, i+1) == 0) b--;
		for (c++; c < b; c++) {
			A2.sto(c, l, 1);
			l++;
		}
	}
	for (b++; b < n; b++) {
		A2.sto(b, l, 1);
		l++;
	}
	return A2;
}
//-----------------------------------------------------------------------------



//-----------------------------------------------------------------------------
// Name:                maximality_test
// Input:       ideal I and a vector v
// Output:      returns 1 in the case of maximality
//                      returns 0 otherwise
//
// We handle the following two special cases:
// (1)  dim I = n, which means, that I = O
// (2)  dim I = n-1, which means that I is maximal and therefore prime

static bool maximality_test(const ideal_mod_p & I, base_vector< bigint > & v)
{
	lidia_debug_handler_l("alg_factor.c", "function maximality_test(...)", 2);

	lidia_size_t l, i, k;

	bigmod_matrix O(dimension, dimension, MODUL);

	order_mod_p a, im_a;

	l = I.get_no_of_columns();
	if (l == 1 && I.is_column_zero(0)) l = 0;

	lidia_debug_handler_c("alg_factor.c", "in maximality_test(...)", 2,
			      std::cout << "l = " << l << std::endl);

	if (l >= dimension-1) return true;

	bigmod_matrix M_phi(dimension-l, dimension-l, MODUL),
		K(dimension-l, dimension-l, MODUL);

	O = basis_completion(I);
	lidia_debug_handler_c("alg_factor.c", "in maximality_test(...)", 2,
			      std::cout << "O = " << O << std::endl);

	lidia_size_t h_a = O.get_no_of_rows();
	lidia_size_t h_b = O.get_no_of_columns();
	//find the matrix of the frobenius-homomorphismus
	for (k = l; k < dimension; k++) {
		a = O.get_column_vector(k);
		im_a = frobenius(a);
		bigmod_matrix MAT(h_a, h_b, MODUL);
		{
			bigint tmp_fact;
			MAT.solve(O, im_a, tmp_fact);
		}
		for (i = l; i < dimension; i++)
			M_phi.sto(i-l, k-l, MAT.member(i, MAT.get_no_of_columns()-1));
	} // End of for (1)
	lidia_debug_handler_c("alg_factor.c", "in maximality_test(...)", 2,
			      std::cout << "M_phi = " << M_phi << std::endl);
	K.kernel(M_phi);
	lidia_debug_handler_c("alg_factor.c", "in maximality_test(...)", 2,
			      std::cout << "Kernel = " << K << std::endl);
	if (K.get_no_of_columns() == 1) return true;
	v = K.get_column_vector(1);
	return false;
}
// End of maximality_test
//-----------------------------------------------------------------------------



//-----------------------------------------------------------------------------
// compute the matrix of the map
//              _ _            _ _
//              O/I ---------> O/I
//               a  |--------> ga
//                              _ _                                  _
//     where g is an element of O/I, but represented by the basis of O
//
// The integer l is chosen as follows (the b's are the columns of O):
//  b ,...,b   ,b     ,...,b
//   1      n-l  n-l+1      n
// - - - - - -  *************
//  Basis of I   Basis of O/I
//
// this means, that O/I has dimension l over Fp
//

static bigmod_matrix mult_map(const order_mod_p& g, const bigmod_matrix& O, int l)
{
	int j, k, n = dimension;

	bigmod_matrix M(l, l, MODUL); // endomorphism in O/I

	for (j = 0; j < l; j++) {
		order_mod_p a = O.get_column_vector(n-l+j); // take basis element no. j of O/I
		order_mod_p im_a = g*a; // compute the image
		bigmod_matrix MAT;
		bigint tmp_fact;
		MAT.solve(O, im_a, tmp_fact); // represent it in O/I
		for (k = 0; k < l; k++)                       // and store it in the matrix
			M.sto(k, j, MAT.member(n-l+k, MAT.get_no_of_columns()-1));
	}
	return M;
}
//-----------------------------------------------------------------------------



//-----------------------------------------------------------------------------
// Name:    factor
// Input:   ideal I
// Output:     nontrivial decomposition I = I1 * I2 or I1 = I and I2 = empty.
//              In the latter case I is either prime or the unit ideal.
// factoring a squarefree ideal I

static void factor(const ideal_mod_p & I, ideal_mod_p & I1, ideal_mod_p & I2)
{
	lidia_debug_handler_l("alg_factor.c", "function factor", 3);

	lidia_size_t l, i, n, k;
	n = dimension;
	bigint i0;
	l = I.get_no_of_columns();
	if (l == 1 && I.is_column_zero(0)) l = 0;
	base_vector< bigint > alpha_v(n-l, n-l);
	order_mod_p alpha;
	Fp_polynomial p;
	lidia_size_t noc;

	if (maximality_test(I, alpha_v)) {
		lidia_debug_handler_c("alg_factor.c", "in factor(...)", 3,
				      std::cout << "Ideal is maximal" << std::endl);
		I1 = I;
		I2 = ideal_mod_p();
		return;
	} // End of if
	lidia_debug_handler_c("alg_factor.c", "in factor(...)", 3,
			      std::cout << "alpha_v = " << alpha_v << std::endl);
	bigmod_matrix M(n-l, n-l, MODUL), gA(n-l, n-l, MODUL);
	bigmod_matrix Ann_g(n-l, n-l, MODUL);
	bigmod_matrix O(n, n, MODUL);

	// the last n-l columns of O form a basis of O/I
	O = basis_completion(I);
	lidia_debug_handler_c("alg_factor.c", "in factor(...)", 3,
			      std::cout << "O = " << O << std::endl);
	// find a linear factor of the minimal polynomial
	p = minimal_polynomial(alpha_v, O, n-l, alpha);
	lidia_debug_handler_c("alg_factor.c", "in factor(...)", 3,
			      std::cout << "alpha = " << alpha << std::endl);
	i0 = find_root(p);
	lidia_debug_handler_c("alg_factor.c", "in factor(...)", 3,
			      std::cout << "i0 = " << i0 << std::endl);
	subtract(alpha[0], alpha[0], i0);
	if (alpha[0] < 0) add(alpha[0], alpha[0], MODUL);
	// alpha is a zero divisor in O/I
	lidia_debug_handler_c("alg_factor.c", "in factor(...)", 3,
			      std::cout << "alpha = " << alpha << std::endl);
	M = mult_map(alpha, O, n-l);
	lidia_debug_handler_c("alg_factor.c", "in factor(...)", 3,
			      std::cout << "M = " << M << std::endl);
	gA.image(M);
	Ann_g.kernel(M);
	lidia_debug_handler_c("alg_factor.c", "in factor(...)", 3,
			      std::cout << "gA = \n" << gA;
			      std::cout << "Ann_g = \n" << Ann_g);
	noc = gA.get_no_of_columns();

	bigmod_matrix M_psi(n-l, n, MODUL);

	// psi is the linear map:
	//      _             _ _
	// psi: O       ----> O/I
	//      sum x w ----> sum y b
	//       i  i i        j  j j
	//             _
	// w    Basis of O
	//             _ _
	// b    Basis of O/I

        bigmod_matrix B0(n, noc+l, MODUL);
        base_vector< bigint > std_bas(n, n);
        base_vector< bigint > image_psi(n-l, n-l);

	lidia_size_t j;

        for (j = 0; j < n; std_bas[j++] = 0) {
		std_bas[j] = 1; // Generate e_j
		lidia_size_t h_a, h_b;
		h_a = O.get_no_of_rows();
		h_b = O.get_no_of_columns();
		bigmod_matrix MAT(h_a, h_b, MODUL);
		bigint tmp_fact;
		MAT.solve(O, std_bas, tmp_fact);
		for (k = l; k < n; k++)
			M_psi.sto(k-l, j, MAT.member(k, MAT.get_no_of_columns()-1));
        } // End of for
	lidia_debug_handler_c("alg_factor.c", "in factor(...)", 3,
			      std::cout << "M_psi:\n" << M_psi << std::endl);

	for (j = 0; j < noc; j++) {
		for (i = l; i < n; i++)
			image_psi[i-l] = gA.member(i-l, j);
		bigmod_matrix MAT(1, 1, MODUL);
		bigint tmp_fact;
		MAT.solve(M_psi, image_psi, tmp_fact);
		bigint *y = MAT.get_column(MAT.get_no_of_columns()-1);
		B0.sto_column(y, MAT.get_no_of_rows(), j);
		delete[] y;
	}

	for (j = 0; j < l; j++) {
                bigint *col_O = O.get_column(j);
                B0.sto_column(col_O, O.get_no_of_rows(), noc+j);
                delete[] col_O;
        } // End of for
	lidia_debug_handler_c("alg_factor.c", "in factor(...)", 3,
			      std::cout << "  B0:\n" << B0 << std::endl);
        I1.image(B0);
	lidia_debug_handler_c("alg_factor.c", "in factor(...)", 3,
			      std::cout << "    I1 = \n" << I1 << std::endl);

        noc = Ann_g.get_no_of_columns();

        bigmod_matrix B1(n, noc+l, MODUL);
        bigmod_matrix DUMMY1(noc+l, noc+l, MODUL);
        bigmod_matrix MAT(n, noc+l, MODUL);

        for (j = 0; j < noc; j++) {
		for (i = l; i < n; i++)
			image_psi[i-l] = Ann_g.member(i-l, j);
		bigint tmp_fact;
		MAT.solve(M_psi, image_psi, tmp_fact);
		bigint *tmp = MAT.get_column(MAT.get_no_of_columns()-1);
		B1.sto_column(tmp, MAT.get_no_of_rows(), j);
		delete[] tmp;
        } // End of for (1)

        for (j = 0; j < l; j++) {
		bigint * tmp;
		B1.sto_column(tmp = O.get_column(j), O.get_no_of_rows(), noc+j);
		delete[] tmp;
	}
	lidia_debug_handler_c("alg_factor.c", "in factor(...)", 3,
			      std::cout << "    B1:\n" << B1 << std::endl);
        I2.image(B1);
	lidia_debug_handler_c("alg_factor.c", "in factor(...)", 3,
			      std::cout << "    I2 = \n" << I2 << std::endl);
}
// End of factor
//-----------------------------------------------------------------------------




//-----------------------------------------------------------------------------
// Name:                p_radical
// Input:       prime number
// Output:      p-radical Ip of O/pO
// This function computes Ip + pO, the p-radical of O/pO

static ideal_mod_p p_radical(bigint p)
{
	lidia_debug_handler_l("alg_factor.c", "function p_radical", 1);

	lidia_size_t j, k;
	order_mod_p im_e;

	bigmod_matrix B1(dimension, dimension, MODUL);
	ideal_mod_p Ip(B1);

	bigint q = p;

	bigmod_matrix A(dimension, dimension, MODUL);

	lidia_debug_handler_c("p-radical", "in p_radical(udigit p)", 1,
			      std::cout << "\nExecuting function p-radical\n");

	// compute j such that the j-th power of p exceeds the dimension
	j = 1;
	while (q < dimension) {
		j++;
		q = q * p;
	} // End of while

	order_mod_p e(dimension);
	for (k = 0; k < dimension; e[k++] = 0) {
		e[k] = 1;
		power(im_e, e, q);
		// Since the vector is already reduced, we can use:
		A.sto_column_vector(im_e, dimension, k);
	} // End of for

	Ip.kernel(A);
	lidia_debug_handler_c("p_radical", "in p_radical(udigit p)", 1,
			      std::cout << "P-Radical: A = \n" << A << std::endl;
			      std::cout << "Kern A = \n" << Ip << std::endl);
	return Ip;
}
// End of p-radical
//-----------------------------------------------------------------------------



// Damian Weber's Code with some changes (adaption to LiDIA) by Stefan Neis
// and with additional code using alg_numbers for computing two element
// representation;
//-----------------------------------------------------------------------------
// Name:        factor_p
// Input:       prime (prime number to be decomposed)
//              &Order (the Order in which the decomposition takes place
//              &number (number of prime ideals occuring in the decomposition
//              &ideals (ideals[0], ideals[2], ..., ideals[number-1]) are
//              these prime ideals
// Output:      void
// factor_p is a procedur to decompose an integer prime in a p-maximal
// order of a number field

// Note: The prime ideals are allocated by this procedure, but you habe to free
// them yourself!

void factor_p(const bigint & prime, const order & Order,
              lidia_size_t &number, prime_ideal* & ideals)

{
	lidia_debug_handler_l("alg_factor.c", "function factor_p", 4);

	long j, k, l, r;

	long ej; // to compute c_exp[j]
	long possible_exp; // additional information to compute c_exp[j]
	long max_exp; // additional information to compute c_exp[j]

	long *c_exp; // a vector, which contains the powers in the
	// factorization of pO

	dimension = Order.degree();

	r = dimension + 2;

//-----------------------------------------------------------------------------
// Initializing D. Webers data structures
//-----------------------------------------------------------------------------

	init_mult_tab(Order, prime);
//-----------------------------------------------------------------------------
// ideals modulo pO
//-----------------------------------------------------------------------------
	ideal_mod_p *C; // C[j] = product of those max. ideals,
                                //   - whose j-th power divides pO, later
                                //   - whose j-th power divides pO exactly.
	ideal_mod_p *F; // the factoring algorithm returns two ideals
                                // F[0], F[1]

	C = new ideal_mod_p[2*r];

	F = new ideal_mod_p[2];
	c_exp = new long[2*r];

	ideal_mod_p I = p_radical(MODUL); // the p_radical is the product of all
	// prime ideals, which divide pO

	lidia_debug_handler_c("alg_factor.c", "function factor_p", 4,
			      std::cout << "p-radical (MODUL = " << MODUL << "):\n";
			      std::cout << I << std::endl);

	C[0] = I;
	j = 0;
	k = 1;

	do {
		factor(C[j], F[0], F[1]);

		lidia_debug_handler_c("alg_factor.c", "function factor_p", 4,
				      std::cout << "factor C[" << j << "] = \n" << C[j] << " = \n";
				      std::cout << F[0] << " *\n" << F[1] << std::endl);


		if (empty(F[1]) == true) {
			j++;
		}
		else {
			C[j] = F[0];
			C[k] = F[1];
			k++;
		} // End of else
	} while (j < k);

	number = k;

	possible_exp = dimension;

	for (j = 0; j < number; j++)
		possible_exp += C[j].get_no_of_columns() - dimension;

	for (j = 0; j < number; j++) {
		lidia_debug_handler_c("alg_factor.c", "function factor_p", 4,
				      std::cout << "possible_exp = " << possible_exp << std::endl);
		if (possible_exp == 0) {
			for (k = j; k < number; k++) {
				c_exp[k] = 1;
			} // End of for (2)

		} // End of if
		max_exp = 1 + possible_exp / (dimension - C[j].get_no_of_columns());
		lidia_debug_handler_c("alg_factor.c", "function factor_p", 4,
				      std::cout << "max_exp = " << max_exp << std::endl);
		ideal_mod_p Q = C[j];
		ej = 1;
		do {
			if (ej == max_exp) break;
			ej++;
			ideal_mod_p Q_ = Q * C[j];
			lidia_debug_handler_c("alg_factor.c", "function factor_p", 4,
					      std::cout << "Comparing " << Q << " and " << Q_);
			if (Q == Q_) {
				lidia_debug_handler_c("alg_factor.c", "function factor_p", 4,
						      std::cout << ": equal!" << std::endl);
				ej--;
				break;
			} // End of if
			lidia_debug_handler_c("alg_factor.c", "function factor_p", 4,
					      std::cout << ": not equal!" << std::endl);
			Q = Q_;
		} // End of do
		while (true);

		c_exp[j] = ej;
		possible_exp -= ej - 1;
	} // End of for

	ideals = new prime_ideal[number];

	for (j = 0; j < number; j++) {
		// compute the two-element-representation of each prime ideal using the
		// Fp-basis of the ideal.
		// c_exp[j] is the ramification index of the ideal C[j]
		// f is the degree of inertia of C[j], so p^f is the norm
		long f = dimension - C[j].get_no_of_columns();
		if (f >= dimension-1 && C[j].is_matrix_zero()) {
			ideals[j] = prime_ideal(prime, Order);
			break;
		} // End of if
		bigint norm_cj;
		power(norm_cj, prime, f);
		long R = 1;
		long m = dimension + C[j].get_no_of_columns();
		long *lambda = new long[m];
		for (k = 0; k < m; lambda[k++] = R);
		bigint *a = new bigint[dimension];
		bigint tmp;

		do {
			for (l = 0; l < dimension; l++) {
				multiply(a[l], prime, lambda[l]);
				for (k = dimension; k < m; k++) {
					multiply(tmp, bigint(lambda[k]), C[j].member(l, k-dimension));
					a[l] += tmp;
				} // End of for (2)
			} // End of for (1)
			alg_number alpha (a, bigint(1), Order);
			divide(tmp, norm(alpha).numerator(), norm_cj);
			remainder(tmp, tmp, prime);
			if (!tmp.is_zero()) {
				delete [] lambda;
				delete [] a;
				ideals[j] = prime_ideal(prime, alpha, c_exp[j], f);
				break;
			} // End of if
			for (l = m-1; lambda[l] == -R; l--);
			lambda[l]--;
			for (k = l+1; k < m; lambda[k++] = R);
			for (l = m-1; lambda[l] == 0 && l >= 0; l--);
			if (l < 0) {
				R++;
				for (k = 0; k < m; lambda[k++] = R);
			} // End of if
		} while (true);
	} // End of for

	delete[] C;
	delete[] F;
	delete[] c_exp;
	remove_mult_tab();
}
// End of factor_p



#ifdef LIDIA_NAMESPACE
}	// end of namespace LiDIA
#endif
