//
// LiDIA - a library for computational number theory
//   Copyright (c) 1994, 1995 by the LiDIA Group
//
// File        : 
// Author      : Thomas Pfahler (TPf)
// Last change : TPf, Feb 29, 1996, initial version
//


#if defined(HAVE_MAC_DIRS) || defined(__MWERKS__)
#include <LiDIA:gf_polynomial.h>
#else
#include <LiDIA/gf_polynomial.h>
#endif


void 
find_roots(base_vector < gf_p_element > &x, const gf_polynomial & f)
{
// f is monic, and has deg(f) distinct roots.
// returns the list of roots
    debug_handler("factoring.c", "find_roots( base_vector<gf_p_element>&, gf_polynomial& )");

    if (f.degree() == 0)
	return;

    if (f.degree() == 1)
    {
	lidia_size_t k = x.size();

	if (x.capacity() < k + 1)
	    x.set_capacity(k + 1);
	x.set_size(k + 1);

	negate(x[k], f.const_term());
	return;
    }

    gf_polynomial h;

    const gf_p_base &pb = f.base();
    const bigint &p = pb.field().characteristic();

    if (p != 2)
    {
	gf_p_element one(pb), r(pb);
	one.assign_one();
	const bigint &q = pb.field().number_of_elements();
	bigint q1(q);
	q1.divide_by_2();

	{
	    do
	    {
		r.randomize();
		power_x_plus_a_mod(h, r, q1, f);
		subtract(h, h, one);
		gcd(h, h, f);
	    }
	    while (h.degree() <= 0 || h.degree() == f.degree());
	}
    }
    else	/* p == 2 */
    {
	do
	{
	    lidia_size_t i, k = pb.field().degree(), n = f.degree();
	    gf_polynomial g;
	    g = randomize(pb, n-1);
	    h = g;
	    for (i = 1; i < k; i++)
	    {
		square(g, g, f);
		add(h, h, g);
	    }
	    gcd(h, h, f);
	}
	while (h.degree() <= 0 || h.degree() == f.degree());
    }

    find_roots(x, h);
    divide(h, f, h);
    find_roots(x, h);
}


base_vector< gf_p_element > find_roots(const gf_polynomial &f)
{
    base_vector< gf_p_element > v;
    find_roots(v, f);
    lidia_size_t i;
    gf_polynomial h, g;
    h.assign_x(f.base());
    g.assign_one(f.base());
    for (i = 0; i < v.size(); i++)
    {
	h[0] = -v[i];
	multiply(g, g, h);
    }
    if (g != f)
	cout<<"Mist."<<v<<endl<<f<<endl<<g<<endl;
    return v;
}



void compose(gf_polynomial& x, const gf_polynomial& g,
    const gf_polynomial& h, const gf_poly_modulus& F)
// x = g(h) mod f
{
    lidia_size_t m = square_root(g.degree() + 1);
    if (m == 0)
    {
	x.assign_zero(h.base());
	return;
    }

    gf_poly_argument A;
    A.build(h, F, m);
    A.compose(x, g, F);
}

void compose2(gf_polynomial& x1, gf_polynomial& x2,
	const gf_polynomial& g1, const gf_polynomial& g2,
	const gf_polynomial& h, const gf_poly_modulus& F)
// xi = gi(h) mod f (i=1,2)
// ALIAS RESTRICTION:  xi may not alias gj, for i != j
{
    lidia_size_t m = square_root(g1.degree() + g2.degree() + 2);
    if (m == 0)
    {
	x1.assign_zero(h.base());
	x2.assign_zero(h.base());
	return;
    }
    
    gf_poly_argument A;
    A.build(h, F, m);
    A.compose(x1, g1, F);
    A.compose(x2, g2, F);
}

    
void compose3(gf_polynomial& x1, gf_polynomial& x2, gf_polynomial& x3,
    const gf_polynomial& g1, const gf_polynomial& g2, const gf_polynomial& g3,
    const gf_polynomial& h, const gf_poly_modulus& F)
// xi = gi(h) mod f (i=1..3)
// ALIAS RESTRICTION:  xi may not alias gj, for i != j
{
    lidia_size_t m = square_root(g1.degree() + g2.degree() + g3.degree() + 3);
    if (m == 0)
    {
	x1.assign_zero(h.base());
	x2.assign_zero(h.base());
	x3.assign_zero(h.base());
	return;
    }
    
    gf_poly_argument A;
    A.build(h, F, m);
    A.compose(x1, g1, F);
    A.compose(x2, g2, F);
    A.compose(x3, g2, F);
}


void 
trace_map(gf_polynomial & w, const gf_polynomial & a, lidia_size_t d,
	const gf_poly_modulus & F, const gf_polynomial & b)
// w = a+a^q+...+^{q^{d-1}} mod f;
// it is assumed that d >= 0, and b = X^q mod f, q a power of p
{
    debug_handler("gf_polynomial", "trace_map( gf_polynomial&, gf_polynomial&, lidia_size_t, gf_poly_modulus&, gf_polynomial& )");

    w.build_frame(gf_polynomial::common_field(a.ffield, b.ffield));

    gf_polynomial z(b), y(a), t;
    w.assign_zero();

    while (d)
    {
	if (d == 1)
	{
	    if (w.is_zero())
		w.assign(y);
	    else
	    {
		compose(w, w, z, F);
		add(w, w, y);
	    }
	}
	else
	{
	    if ((d & 1) == 0)
	    {
		compose2(z, t, z, y, z, F);
		add(y, t, y);
	    }
	    else
	    {
		if (w.is_zero())
		{
		    w.assign(y);
		    compose2(z, t, z, y, z, F);
		    add(y, t, y);
		}
		else
		{
		    compose3(z, t, w, z, y, w, z, F);
		    add(w, w, y);
		    add(y, t, y);
		}
	    }
	}
	d = d >> 1;
    }

    w.delete_frame();
}


#if 0

void 
power_compose(gf_polynomial & y, const gf_polynomial & h, lidia_size_t q, const gf_polynomial & f)
// w = X^{q^d} mod f;
// it is assumed that d >= 0, and b = X^q mod f, q a power of p
{
    debug_handler("gf_polynomial", "power_compose( gf_polynomial&, gf_polynomial&, lidia_size_t, gf_polynomial& )");

    y.build_frame(gf_polynomial::common_field(h.ffield, f.ffield));

    gf_polynomial z(h);
    lidia_size_t sw;

    y.assign_x();

    while (q)
    {
	sw = 0;

	if (q > 1)
	    sw = 2;
	if (q & 1)
	{
	    if (y.is_x())
		y.assign(z);
	    else
		sw = sw | 1;
	}

	switch (sw)
	{
	case 0:
	    break;

	case 1:
	    compose(y, y, z, F);
	    break;

	case 2:
	    compose(z, z, z, F);
	    break;

	case 3:
	    compose2(y, z, y, z, z, F);
	    break;
	}

	q = q >> 1;
    }

    y.delete_frame();
}

#endif
