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

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

//
// IMPORTANT:
//
// Everything in this file is heavily based on
// Lidl, Niederreiter: Introduction to finite fields and their applications
// Cambridge University Press, 1986
//


bigint order_mod(const bigint & a, const bigint & b, int verbose = 0);

void
factor_quadratic_pol(factorization< Fp_polynomial >& F, const Fp_polynomial &f);
//f.degree() must be 2, input may not alias output


/*   special algorithms for factoring binomials
    - case1_factor_binomial, case2_factor_binomial,
      case3_factor_binomial, case4_factor_binomial
      are "sub"cases
    - factor_binomial
      is the main function
      assumptions: f must be a monic binomial, f(0)!=0
*/



static
Fp_polynomial
case1_factor_binomial(const bigint & a, lidia_size_t t, lidia_size_t r, const bigint & p)
{
//also correct for GF(p^n)
    //returns a factor of f = x^t - a
    //r must be a prime factor of t, dividing d = (p-1)/ord(a)
    // => a = b^r  for some b,   x^t - a = x^(t1*r) - b^r

    lidia_size_t t1 = t / r;
    bigint b;

//compute b so that a = b^r
    bool verbose = single_factor< Fp_polynomial >::verbose();
    if (verbose) cerr<<"Compute "<<r<<"-th root..."<<endl;
    Fp_polynomial g;
    g.set_modulus(p);
    g.set_coefficient(r);
    g.set_coefficient(-a, 0);
    b.assign( find_root(g) );
    if (verbose) cerr<<"done."<<endl;


// x^t1 - b  is a divisor of f
    Fp_polynomial factor;
    factor.set_modulus(p);
    factor.set_coefficient(t1);
    if (r % 2 != 0)
	b.negate();	//if r is even, -b is also an r-th root
    factor.set_coefficient(b, 0);	//was actually: factor[0] = -b
    return factor;
}


static
Fp_polynomial 
case2_factor_binomial(const bigint & a, lidia_size_t t, lidia_size_t r, const bigint & p)
{
    //returns a factor of f = x^t - a  (mod p)
    //r must be a prime factor of t, not dividing e = ord(a)
    // => r1*r = 1 mod (p-1)  for some r1,  x^t - a = x^(t1*r) - a^(r1*r)

    lidia_size_t t1 = t / r;
    bigint r1;

    InvMod(r1, r, p - 1);
    bigint b;
    power_mod(b, a, r1, p);	//b = a^r1  mod p

//x^t1 - a^r1  is a divisor of f
    Fp_polynomial factor;
    factor.set_modulus(p);
    factor.set_coefficient(t1);
    factor.set_coefficient(-b, 0);
    return factor;
}



static
Fp_polynomial 
case3_factor_binomial(const bigint & a, lidia_size_t t, const bigint & d, const bigint & p)
{
//also correct for GF(p^n)
    //f = x^t - a
    //all prime factors of t divide e = ord(a) but not d = (p-1)/e
    //t == 0 mod 4
	
    bigint c, d_half, tmp;
    shift_right(d_half, d, 1);	//d_half = d/2

    power_mod(c, a, d_half, p);
    InvMod(tmp, 2, p);
    MulMod(c, c, tmp, p);	//c = a^d_half * 2^(-1)

    add(tmp, p, 1);
    shift_right(tmp, tmp, 2);	//tmp = (p+1)/4

    power_mod(c, c, tmp, p);	//c = (a^d_half * 2^(-1)) ^ ((p+1)/4)

    Fp_polynomial factor;
    factor.set_modulus(p);
    lidia_size_t t2 = t >> 2;	//t2 = t/4;

//x^t - a = x^(4*t2) + 4c^4
    //        = (x^(2*t2) + 2*c*x^t2 + 2*c^2) * (x^(2*t2) - 2*c*x^t2 + 2c^2)
    //        = factor * g

    factor.set_coefficient(2 * t2);
    square(tmp, c);
    tmp.multiply_by_2();	//tmp = -c^2

    factor.set_coefficient(tmp, 0);
    c.multiply_by_2();		//c = 2c

    factor.set_coefficient(c, t2);
    return factor;
}




static
factorization < Fp_polynomial >
case4_factor_binomial(const bigint & a, lidia_size_t t, lidia_size_t A, const bigint & e, const bigint & p, bool verbose)
{
//also correct for GF(p^n)
    //f = x^t - a,  e = ord(a)
    //p == 3 mod 4, all prime factors of t divide e, but not d = (p-1)/e
    //p = 2^A * u - 1, u odd,  2^A must divide t

    lidia_size_t B = 1 << (A - 1);
    lidia_size_t v = t >> (A - 1);		//t = B * v

    if (verbose)
    {
	cerr << "f factors as a product of " << B << " monic irreducible ";
	cerr << "polynomials of degree " << v << endl;
    }

    Fp_polynomial g;
    g.set_modulus(p);
    lidia_size_t i;
    bigint c;

    if (verbose) cerr << "Computing special polynomial" << endl;

//build g
    c.assign_one();
    // c(0) = 1
    // c(i) = ( (B-i-1)! B )/( i! (B-2i)! )
    // c(i) = c(i-1) * ( (B-2i+2)(B-2i+1) )/( (i)(B-i) )

    g.set_coefficient(B);
    for (i = 1; i <= (B / 2); i++)
    {
	c *= (B - 2 * i + 2) * (B - 2 * i + 1);
	c /= i * (B - i);
	g.set_coefficient(c, B - 2 * i);
    }

//find roots of g
    if (verbose) cerr << "Finding roots" << endl;
    base_vector < bigint > roots;
    find_roots(roots, g);

//build complete factorization
    if (verbose) cerr << "Build complete factorization" << endl;
    bigint r;
    bigint tmp, D;

    shift_right(D, e, 1);
    inc(D);			//D = (e/2)+1, D is even !

    //compute r s.t.
    // 2Br == D  mod (p-1)
    //we know that gcd(2B,p-1) = gcd(B,p-1) = 2

    xgcd_left(tmp, 2 * B, p - 1);	//2 == tmp*(2B)  mod (p-1)

    MulMod(D, tmp, D, p - 1);	//have 2r == D  mod (p-1)

//    if (D.sizetify(r))
//    {
//	cerr << "tmp = " << tmp << "   ";
//	cerr << "p-1 = " << p - 1 << "   B = " << B << "   D = " << D << endl;
//	lidia_error_handler("factor_binomial", "D ist zu gross");
//    }
    if ((r & 1) != 0)
	lidia_error_handler("factor_binomial", "d is not divisible by 2 !!!");
    r = r >> 1;

    bigint b;
    power_mod(b, a, r, p);	//b = a^r

    MulMod(tmp, b, b, p);	//square_mod


//factor = x^v - b*roots[i]*x^(v/2) - b^2
    if (verbose) cerr << "Add factors..." << endl;
	
    factorization < Fp_polynomial > F;
    Fp_polynomial factor;
    factor.set_modulus(p);
    factor.set_coefficient(v);
    tmp.negate();
    factor.set_coefficient(tmp, 0);
    b.negate();

    for (i = 0; i < B; i++)
    {
	MulMod(tmp, b, roots[i], p);
	factor.set_coefficient(tmp, v / 2);
	append_irred_factor(F, factor);
    }
    return F;
}



factorization < Fp_polynomial >
factor_binomial(const Fp_polynomial & f, int FACTOR_P_MINUS_ONE)
{
//f must be a monic binomial with f(0)!=0
//funktioniert auch fuer GF(p) mit p Primzahlpotenz

    bool verbose = single_factor<Fp_polynomial>::verbose();
    factorization < Fp_polynomial > F;

    if (!f.is_binomial() || f.const_term().is_zero())
    {
	if (verbose) cerr<<"factor_binomial::input is not a binomial or has const term zero" << endl;
	F.assign(f);
	return F;
    }

    if (verbose)
    {
	cerr << "factor_binomial ";
	f.pretty_print(cerr);
	cerr << " :"<< endl;
    }

    const bigint & p = f.modulus();
    bigint p_minus_one(p);
    dec(p_minus_one);

    lidia_size_t i;
    lidia_size_t deg = f.degree();

    switch(deg)
    {
	case(1) :
	    if (verbose) 
		cerr << "polynomial has degree 1 => irreducible" << endl;
	    append_irred_factor(F,f);
	    return F;
	case(2) :
	    factor_quadratic_pol(F, f);
	    return F;
    }
    
//now, deg > 2

    Fp_polynomial g, h;
    g.set_modulus(p);
    
    if (deg == p_minus_one && f.const_term() == p_minus_one)
    //f = (x^(p-1) - 1)
    {
	if (verbose) cerr << "f = (x^(" << p << "-1) - 1) mod " << p << endl;
	g.assign_x();
	lidia_size_t small_p;
	p.sizetify(small_p);
	for (i = 1; i < small_p; i++)
	{
	    g.set_coefficient(i, 0);
	    append_irred_factor(F, g);
	}
	return F;
    }


    lidia_size_t r;
    bigint a = -f.const_term();

//f = x^t - a, t>=2, a!=0

    fac_vec t_fac(deg);		//t_fac contains the factorization of deg
    int ii, num_factors = t_fac.number_of_factors();
    
    if (f.const_term() == p_minus_one  &&  
		!(num_factors == 1 && t_fac[0].a == 1))
    {//f = x^t - 1, t not prime
	g.assign_one();
	g.negate();
	F.append(f);
	for (ii = 0; ii < num_factors; ii++)
	{
	    if (verbose) cerr << "Refinement with x^" << deg/t_fac[ii].q << " - 1" << endl;
	    g[deg/t_fac[ii].q].assign_one();
	    F.refine(g);
	    g[deg/t_fac[ii].q].assign_zero();
	}
	cerr << F << endl << endl;
	return F;
    }

    
    if (p.length() > 5 && !FACTOR_P_MINUS_ONE)
    {
//	if (verbose) cerr << "avoid factorization of (modulus-1)" << endl;
	F.append(f);
	return F;
    }
    
    bigint e, d;
    e = order_mod(a, p);		//order of a in GF(p)

    divide(d, p - 1, e);
    long rem;

    if (verbose)
    {
	cerr << "Computing prime factors of the degree and of the order ";
	cerr << "of the const term..." << endl;
	cerr << "deg = " << deg << "   e = " << e << endl;
    }


    lidia_size_t count = 0;
    bool hit;
    for (ii = num_factors-1; ii >=0 ; ii--)
    {
	hit = false;
	r = t_fac[ii].q;

	remainder(rem, d, r);
	if (rem == 0)		//if (! r does not divide (p-1)/e)
	{
	    hit = true;
	    if (verbose)
	    {
		cerr << "prime factor " << r << " of " << deg << " divides ";
		cerr << "(p-1)/e = " << d << endl;
		cerr << "    => split into 2 factors" << endl;
	    }
	    g = case1_factor_binomial(a, deg, r, p);
	    if (count == 0)
	    {
		divide(h, f, g);
		F.append(g);
		F.append(h);
		count++;
	    }
	    else
		F.refine(g);
/*	
	    if (verbose) cerr << "cannot split f (cannot take roots in Fp)" << endl;
	    F.append(f);
*/
	}
	
      if (!hit)
      {
	remainder(rem, e, r);
	if (rem != 0)		//if (! r divides e)
	{
	    if (verbose) 
	    {
		cerr << "prime factor " << r << " of " << deg << " does not ";
		cerr << "divide the order " << e << endl;
		cerr << "    => split into 2 factors" << endl;
	    }
	    g = case2_factor_binomial(a, deg, r, p);
	    if (count == 0)
	    {
		divide(h, f, g);
		F.append(g);
		F.append(h);
		count++;
	    }
	    else
		F.refine(g);
	}
      }
    }

    if (count != 0)
    {
	F.refine();
	return F;
    }

//now, all prime factors of t divide e, but not d
    if (verbose) cerr << "testing second condition..." << endl;

    if (deg % 4 != 0)
    {
	if (verbose) cerr << "deg != 0 mod 4  => f is irreducible" << endl;
	append_irred_factor(F, f);
	return F;
    }

//now, deg == 0 mod 4
    if (p % 4 == 1)
    {
	if (verbose) cerr << "deg % 4 == 0  and  modulus % 4 == 1  => f is irreducible" << endl;
	append_irred_factor(F, f);
	return F;
    }

//now, deg == 0 mod 4 and p == 3 mod 4
    if (verbose) cerr << "deg % 4 == 0  and  modulus % 4 == 3" << endl;
    bigint tmp(p);
    inc(tmp);
    lidia_size_t A = 0;
    while (tmp.is_even())
    {
	A++;
	tmp.divide_by_2();
    }

    if (verbose)
    {
	cerr << "deg = " << deg << "   p = " << p << "   A = " << A << endl;
	cerr << "p = 2^A*u - 1" << endl;
    }

    if (deg % (1 << A) == 0)		//if (deg is divisible by 2^A)
    {
	if (verbose) cerr << "deg is divisible by 2^A  => compute canonical factorization" << endl;
	
	F = case4_factor_binomial(a, deg, A, e, p, verbose);
    }
    else
    {
	if (verbose) cerr << "deg is not divisible by 2^A  => split into 2 factors" << endl;
		
	g = case3_factor_binomial(a, deg, d, p);
	F.append(g);
	g.set_coefficient(-g[deg / 4], deg / 4);
		//negate coeff. of x^(deg/4)
	F.append(g);
    }
    return F;
}

