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



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


void append_irred_factor(factorization<Fp_polynomial> &F,
		const Fp_polynomial & f, lidia_size_t i)
{
    single_factor<Fp_polynomial> tmp(f);
    if (f.degree() > 0)
	tmp.set_prime_flag(decomposable_object::prime);
    F.append(tmp, i);
}



static void
sq_fr_special(factorization< Fp_polynomial >& u, const Fp_polynomial& f,
lidia_size_t p)
//f(x) = g(x^p)
{
    Fp_polynomial t;
    t.set_modulus(f.modulus());
    t.set_max_degree( f.degree()/p );
    for (lidia_size_t i = 0; i <= f.degree()/p; i++)
	t.set_coefficient(f[i*p], i);

    square_free_decomp(u, t);
    u.power(p);
}


void square_free_decomp(factorization< Fp_polynomial >& u, const Fp_polynomial& f)
{
// Performs square-free decomposition.
// ASSUMPTION: deg(f) > 0, f must be monic
// see: D.Knuth, The Art Of Computer Programming, Vol.2, 4.6.4, Ex. 34+36

	debug_handler_c( "Fp_polynomial", "square_free_decomp(...)", 8,
	cout<<"input: f = "<<f<<endl; );

    if (f.is_zero() || !f.is_monic())
	lidia_error_handler( "Fp_polynomial", "square_free_decomp(...)::input"
	"polynomial must be monic");

    Fp_polynomial t1;

    if (f.degree() <= 1)
    {
	u.assign( single_factor< Fp_polynomial > (f) );
	return;
    }

    derivative(t1, f);
	debug_handler_c( "Fp_polynomial", "square_free_decomp(...)", 8,
	cout<<"derivation: f' = "<<t1<<endl; );

    lidia_size_t i, p;
    f.modulus().sizetify(p);

    if ( t1.is_zero() )
    {//f = g^p;
	sq_fr_special(u, f, p);
	return;
    }

    Fp_polynomial q;
    gcd(q, f, t1);
	debug_handler_c( "Fp_polynomial", "square_free_decomp(...)", 8,
	cout<<"gcd(f,f') = "<<q<<endl; );

    if (q.is_one())
    {//f is square_free
	    debug_handler_c( "Fp_polynomial", "square_free_decomp(...)", 8,
	    cout<<"f is square_free"<<endl; );
	u.assign( single_factor< Fp_polynomial > (f) );
	return;
    }

    //gcd( f, f' ) != 1
    Fp_polynomial v, w, s, d;

    divide(v, f, q);
    divide(w, t1, q);
    i = 0;
    lidia_size_t control = 0, f_old_degree = f.degree();
    u.kill();

    for (;;)
    {
	i = i + 1;
	    debug_handler_c( "Fp_polynomial", "square_free_decomp(...)", 8,
	    cout<<"  c("<<i<<") = "<<v<<endl; );

	derivative(t1, v);
	subtract(s, w, t1);
	    debug_handler_c( "Fp_polynomial", "square_free_decomp(...)", 8,
	    cout<<"  d("<<i<<") = "<<s<<endl; );

	if (s.is_zero())
	{
		debug_handler_c( "Fp_polynomial", "square_free_decomp(...)", 8,
		cout<<"  p("<<i<<") = "<<v<<endl; );

	    if (!v.is_one())
	    {
		u.append(v, i);
		control += i*v.degree();
	    }
	    break;
	}

	gcd(d, v, s);

	    debug_handler_c( "Fp_polynomial", "square_free_decomp(...)", 8,
	    cout<<"  p("<<i<<") = "<<d<<endl; );

	if (!d.is_one())
	{
	    divide(v, v, d);
	    divide(w, s, d);
	    u.append(d, i);
	    control += i*d.degree();
	}
	else
	    w.assign(s);
    }

    if (control == f_old_degree)
    {
	    debug_handler_c( "Fp_polynomial", "square_free_decomp(...)", 8,
	    cout<<"return "<<u<<endl; );
	return;
    }

	debug_handler_c( "Fp_polynomial", "square_free_decomp(...)", 8,
	cout<<"deg = "<<control<<" instead of "<<f_old_degree<<endl
	    <<"factorization to be corrected :\n"<<u<<endl; );

    /*   now, some corrections   */
    Fp_polynomial z, U, tmp;
    lidia_size_t j1, j2, J, J2;

    /*   t1 = prod_i u_i^(i-1)   */
    power(t1, u.composite_base(0).base(), u.composite_exponent(0)-1);
    for (j1 = 1; j1 < u.no_of_composite_components(); j1++)
    {
	power(tmp, u.composite_base(j1).base(), u.composite_exponent(j1)-1);
	multiply(t1, t1, tmp);
    }
	debug_handler_c( "Fp_polynomial", "square_free_decomp(...)", 8,
	cout<<"t1 = "<<t1<<endl; );

    divide(z, q, t1);
    factorization< Fp_polynomial > u2;
    sq_fr_special(u2, z, p);

	debug_handler_c( "Fp_polynomial", "square_free_decomp(...)", 8,
	cout<<"z = "<<z<<endl<<"with squarefree decomposition :\n"<<u2<<endl; );

    factorization< Fp_polynomial > u3, empty, tmp_fact;

    for (j1 = 0; j1 < u.no_of_composite_components(); j1++)
    {
	U.assign( u.composite_base(j1).base() );
	J = u.composite_exponent(j1);

	    debug_handler_c( "Fp_polynomial", "square_free_decomp(...)", 8,
	    cout<<" correcting exponent "<<J<<" : "<<U<<endl; );

	for (j2 = 0; j2 < u2.no_of_composite_components(); j2++)
	{
	    const Fp_polynomial & U2 = u2.composite_base(j2).base();
	    J2 = u2.composite_exponent(j2);

		debug_handler_c( "Fp_polynomial", "square_free_decomp(...)", 8,
		cout<<"  comparing with u2("<<j2<<") ="<<U2<<" ^"<<J2<<endl; );

	    gcd(d, U, U2);

		debug_handler_c( "Fp_polynomial", "square_free_decomp(...)", 8,
		cout<<"  ggT = "<<d<<endl; );

	    if (!d.is_one())
	    {
		u3.append( d, J + J2 );

	    /*   u2[j2] /= d   */
		if (d == U2)
		{
		    u2.replace(j2, empty);
		    j2--;
		}
		else
		{
		    divide(tmp, U2, d);
		    tmp_fact.assign( single_factor< Fp_polynomial > (tmp) );
		    tmp_fact.power(J2);
		    u2.replace(j2, tmp_fact);
		}

	    /*   U /= d   */ 
		if (d == U)
		{
		    U.assign_one();
		    break;
		}
		else
		    divide(U, U, d);
	    }
	}
	if (!U.is_one())
	    u3.append(U, J);
    }

    multiply(u, u2, u3);
	debug_handler_c( "Fp_polynomial", "square_free_decomp(...)", 8,
	cout<<"return "<<u<<endl; );
}





//#if 0
//obsolete

void old_square_free_decomp(factorization< Fp_polynomial > &u,
	const Fp_polynomial &f)
{
	debug_handler_c( "Fp_polynomial", "old_square_free_decomp(...)", 8,
	cout<<"INPUT = "<<f<<endl; );
    if (f.degree() <= 1)
    {
	u.assign( single_factor< Fp_polynomial > (f) );
	return;
    }

    Fp_polynomial d, t;
    derivative(t, f);

    if (t.is_zero())
    {
	lidia_size_t i, p;
	f.modulus().sizetify(p);
	t.set_max_degree(f.degree()/p);
	for (i = 0; i <= f.degree()/p; i++)
	    t[i] = f[i*p];
	    debug_handler_c( "Fp_polynomial", "old_square_free_decomp(...)", 8,
	    cout<<"F = "<<t<<" ^ P"<<endl; );
	u.kill();
	old_square_free_decomp(u, t);
	u.power(p);
	return;
    }

    gcd(d, t, f);
    if (d.is_one())
    {
	debug_handler_c( "Fp_polynomial", "old_square_free_decomp(...)", 8,
	cout<<"F IS SQUAREFREE"<<endl; );
	u.assign( single_factor< Fp_polynomial > (f) );
	return;
    }

    divide(t, f, d);
	debug_handler_c( "Fp_polynomial", "old_square_free_decomp(...)", 8,
	cout<<"APPEND t = "<<t<<endl; );
    u.kill();
    old_square_free_decomp(u, d);
    u.append(t);
    u.refine();
}
//#endif


factorization< Fp_polynomial > square_free_decomp(const Fp_polynomial &f)
{
    factorization< Fp_polynomial > F;
    square_free_decomp(F, f);
    return F;
}


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

