//
// LiDIA - a library for computational number theory
//   Copyright (c) 1994, 1995 by the LiDIA Group
//
// File        : Fp_factor.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>
#include <LiDIA:poly_modulus.h>
#else
#include <LiDIA/Fp_polynomial.h>
#include <LiDIA/Fp_polynomial_util.h>
#include <LiDIA/factorization.h>
#include <LiDIA/poly_modulus.h>
#endif



void sf_berlekamp_work(factorization< Fp_polynomial > &factors,
	const Fp_polynomial &x_to_the_power_of_p, const poly_modulus &F);
void sf_can_zass_work(factorization< Fp_polynomial > &factors,
	const Fp_polynomial &x_to_the_power_of_p, const poly_modulus &F,
	int DDF_FLAG = 1);
	

void
factor_quadratic_pol(factorization< Fp_polynomial >& F, const Fp_polynomial &f)
//f.degree() must be 2
//input may not alias output
{
    if (f.degree() != 2)
	lidia_error_handler("", "factor_quadratic_pol()::degree != 2");

    bool verbose = single_factor< Fp_polynomial >::verbose();
    const bigint &p = f.modulus();
    
    if (verbose) cout << "factoring quadratic polynomial" << endl;

    F.kill();
    Fp_polynomial g;

    if (f.const_term().is_zero())
    {
	shift_right(g, f, 1);
	append_irred_factor(F, g);
	g.assign_x();
	append_irred_factor(F, g);
	return;
    }
	
    bigint t;
    square(t, f[1]);
    subtract(t, t, f[0]*4);    // t = b^2 - 4a
    
    if (jacobi(t, p) == 1)
    {
	if (verbose) cout << " splitting f in 2 linear factors " << endl;
	t.assign( find_root(f) );
	t.negate();

	g.set_modulus(p);
	g.assign_x();
	g.set_coefficient(t, 0);
	append_irred_factor(F, g);

	divide(g, f, g);
	append_irred_factor(F, g);
    }
    else
    {
	if (verbose) cout << " f is irreducible with degree 2 " << endl;
	append_irred_factor(F, f);
    }
}



void factor_work(factorization< Fp_polynomial > &Fact, const Fp_polynomial& f,
		int verbose=0)
//f is assumed to be monic, non-zero, square-free, f(0)!=0
//f may not alias a component of Fact
{
    lidia_size_t n = f.degree();
    Fact.kill();


/*   test for trivial/simple cases   */
    switch(n)
    {
	case(-1):lidia_error_handler("Fp_polynomial",
		    "factor_work(factorization<Fp_polynomial>&,Fp_polynomial&)"
		    "::input is zero polynomial");
		    
	case(0):
	case(1): append_irred_factor(Fact, f); return;
	case(2): factor_quadratic_pol(Fact, f); return;
    }

/*   test for binomial/trinomial   */
    const bigint &p = f.modulus();
    lidia_size_t i, non_zeros = 2;   //first non-zero coefficients: f(n), f(0)
    for (i = 1; i < n; i++)
	if (!f[i].is_zero())
	{
	    non_zeros++;
	    if (non_zeros > 3) break;
	}
    switch(non_zeros)
    {
	case(2) : Fact.assign( factor_binomial(f) );
		  if (Fact.no_of_components() > 1  
			|| Fact.no_of_prime_components() == 1) return;
		  Fact.kill();
		  break;
	case(3) : /*   simple_trinomial_irred_test(f)   */
		  if (n == p  &&  f[1] == p-1)
		  {//f = x^p - x - a,  a!=0  =>  f irred.
		      if (verbose) cerr << "found irred. trinomial" << endl;
		      append_irred_factor(Fact, f);
		      return;
		  }
		  break;
    }



/*   unfortunately, we must work harder for the rest...   */
    my_timer t;
    poly_modulus F(f);
    Fp_polynomial x_to_the_power_of_p, x;
    x.set_modulus(p);
    x.assign_x();

    if (verbose) t.start("computing x^p...");
    power_x(x_to_the_power_of_p, p, F);
    if (verbose) t.stop();



/*   splitting linear factors   */
    if (verbose) t.start("splitting linear factors");
    Fp_polynomial d1, g1, ff;
    subtract(g1, x_to_the_power_of_p, x);
    gcd(d1, g1, f);
    if (!d1.is_one())
    {
	if (verbose) 
	    cerr << " root_edf, number of factors = " << d1.degree() << endl;
	root_edf(Fact, d1);
	divide(ff, f, d1);
	if (ff.degree() < 1)
	{
	    if (verbose) t.stop();
	    return;
	}
	F.build(ff);
	remainder(x_to_the_power_of_p, x_to_the_power_of_p, F);
    }
    else
	ff.assign(f);
    if (verbose) t.stop();


    factorization< Fp_polynomial > Fact2;

#if 0
/*   splitting factors of degree 2   */
    Fp_polynomial d2, g2;
    if (verbose) t.start("splitting factors of degree 2");
    power_compose(g2, x_to_the_power_of_p, 2, F);
    subtract(g2, g2, x);
    gcd(d2, g2, ff);
    if (!d2.is_one())
    {
	if (verbose) cerr << " edf, degree 2, number of factors = " 
			    << d2.degree()/2 << endl;
	F.build(d2);
	Fp_polynomial tmp;
	remainder(tmp, x_to_the_power_of_p, F);
	
	edf(Fact2, F, tmp, 2);
	multiply(Fact, Fact, Fact2);
	
	divide(ff, ff, d2);
	if (ff.degree() < 1)
	{
	    if (verbose) t.stop();
	    return;
	}
	F.build(ff);
	remainder(x_to_the_power_of_p, x_to_the_power_of_p, F);
    }
    if (verbose) t.stop();
#endif


/*   general case   */
    if (verbose) t.start("general case");

    lidia_size_t bl = p.bit_length();
    if (n*bl < 100000 || n/bl > 20)
	sf_berlekamp_work(Fact2, x_to_the_power_of_p, F);
    else
	sf_can_zass_work(Fact2, x_to_the_power_of_p, F);
    multiply(Fact, Fact, Fact2);
    if (verbose) t.stop();
}



void 
factor(factorization< Fp_polynomial > &F, const Fp_polynomial& f)
//input may not alias output
{
    debug_handler("Fp_polynomial", "factor( factorization< Fp_polynomial >&, Fp_polynomial& )");

    if (f.is_zero())
	lidia_error_handler("Fp_polynomial", "factor( factorization< Fp_polynomial >&, Fp_polynomial& )::input is zero polynomial");
	
    bool verbose = single_factor< Fp_polynomial >::verbose();
    F.kill();

    lidia_size_t n = f.degree();
    if (verbose) cerr << "factor degree " << n << endl;

    if (n <= 1)
    {
	append_irred_factor(F, f);
	return;
    }

    factorization< Fp_polynomial > G;
    Fp_polynomial tmp, g;
    tmp.set_modulus( f.modulus() );
    const Fp_polynomial *ff;
    
    if (f.is_monic())
	ff = &f;
    else
    {
	tmp.assign( f.lead_coeff() );
	G.append( tmp );
	
	g.assign(f);
	g.make_monic();
	ff = &g;
    }


/*   eliminate powers of 'x' and make square-free   */
    lidia_size_t i = 0;
    while (i < n ? (*ff)[i].is_zero() : 0)
	i++;
    if (i > 0)
    {
	tmp.assign_x();
	append_irred_factor(G, tmp, i);
	
	shift_right(tmp, *ff, i);
	n -= i;
	square_free_decomp(F, tmp);
    }
    else
	square_free_decomp(F, *ff);

    multiply(F, F, G);


/*   factor components   */
    while(F.no_of_composite_components() != 0)
    {
	if (verbose) cerr << "factoring " << F.composite_base(0) << endl;
	factor_work(G, F.composite_base(0).base(), verbose);
	G.power(F.composite_exponent(0));
	F.replace(0, G);
    }
}

factorization< Fp_polynomial >
single_factor< Fp_polynomial >::factor() const
{
    factorization< Fp_polynomial > F;
    ::factor(F, rep);
    return F;
}
