//
// LiDIA - a library for computational number theory
//   Copyright (c) 1994, 1995 by the LiDIA Group
//
// File        : 
// Author      : Victor Shoup, 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:poly_modulus.h>
#include <LiDIA:random.h>

#include <LiDIA:single_factor.h>
#include <LiDIA:factorization.h>

#else

#include <LiDIA/Fp_polynomial.h>
#include <LiDIA/Fp_polynomial_util.h>
#include <LiDIA/poly_modulus.h>
#include <LiDIA/random.h>

#include <LiDIA/single_factor.h>
#include <LiDIA/factorization.h>

#endif


int DDF_GCD_BLOCKING_FACTOR = 4;

//extern "C" int getpid(void);
extern "C" int unlink(const char *);


char *
create_file_name(const char *stem, const char *ext, lidia_size_t d)
//returns "stem.ext.d"
{
    static char sbuf[256];

    debug_handler("Fp_polynomial", "create_file_name ( char*, char*, lidia_size_t )");
    strcpy(sbuf, stem);
    strcat(sbuf, ".");
    strcat(sbuf, ext);
    strcat(sbuf, ".");

    char dbuf[6];
    dbuf[5] = '\0';
    lidia_size_t i, dd;
    dd = d;

    for (i = 4; i >= 0; i--)
    {
	dbuf[i] = (char)((dd % 10) + '0');
	dd = dd / 10;
    }

    strcat(sbuf, dbuf);
    return sbuf;
}



void 
file_cleanup(lidia_size_t k, lidia_size_t l, const char *new_ddf_stem)
//delete all temporary files
{
    debug_handler("Fp_polynomial", "file_cleanup( lidia_size_t, lidia_size_t, char* )");
    lidia_size_t i;

    for (i = 1; i <= k; i++)
	unlink(create_file_name(new_ddf_stem, "baby", i));

    for (i = 1; i <= l; i++)
	unlink(create_file_name(new_ddf_stem, "giant", i));
}


void create_stem(char *& stem)
//returns *stem = "ddf-pid", where pid is the pid of the current process
{
    char *tmp = new char[128];
    if (!tmp)
	lidia_error_handler("Fp_polynomial", "create_stem( void )::out of memory");
    int num;
    bool ok = false;
    fstream s;

    num = (int)getpid() % 100000;	//5 digits are enough

    //try current directory...
    sprintf(stem, "./ddf-%d", num);
    strcpy(tmp, stem);
    strcat(tmp, ".baby.00001");	//test if pid is already used
    s.open(tmp, ios::out|ios::noreplace);
    if (s) ok = true;		//file doesn't exist yet
    s.close();

    if (!ok)
    {//try "/tmp"-directory...
	sprintf(stem, "/tmp/ddf-%d", num);
	strcpy(tmp, stem);
	strcat(tmp, ".baby.00001");
	s.open(tmp, ios::out|ios::noreplace);
	if (s) ok = true;
	s.close();
    }

    if (!ok)
	lidia_error_handler("Fp_polynomial","create_stem(...)::cannot write temporary files into current nor into /tmp-directory");
    delete[] tmp;
}



void 
fetch_giant_step(Fp_polynomial & g, lidia_size_t gs, 
		const poly_modulus & F, const char *new_ddf_stem)
{
    debug_handler("Fp_polynomial", "fetch_giant_step( Fp_polynomial&, lidia_size_t, poly_modulus&, char* )");

    ifstream s;

    s.open(create_file_name(new_ddf_stem, "giant", gs), ios::in);
    if (!s)
	lidia_error_handler_c("Fp_polynomial", 
	"fetch_giant_step( ... )::open read error", 
	cout << "open read error: " << create_file_name(new_ddf_stem,
		"giant", gs) << "\n";
	);

    s >> g;
    s.close();
    remainder(g, g, F);
}


void 
fetch_baby_steps(base_vector < Fp_polynomial > &v, lidia_size_t k,
		const char *new_ddf_stem, const bigint & p)
{
    debug_handler("Fp_polynomial", "fetch_baby_steps( base_vector<Fp_polynomial>&, lidia_size_t, char*, bigint& )");

    ifstream s;

    if (v.capacity() < k)
	v.set_capacity(k);
    v.set_size(k);


    lidia_size_t i;
    for (i = 1; i <= k - 1; i++)
    {
	s.open(create_file_name(new_ddf_stem, "baby", i), ios::in);
	if (!s)
	    lidia_error_handler_c("Fp_polynomial", 
	    "fetch_baby_steps( ... ):::open read error",
	    cout << "open read error: " << create_file_name(new_ddf_stem,
		    "baby", i) << "\n";
	    );

	s >> v[i];
	s.close();
    }

    v[0].set_modulus(p);
    v[0].assign_x();
}







void 
generate_baby_steps(Fp_polynomial & h1, const Fp_polynomial & f,
	const Fp_polynomial & h, lidia_size_t k, const char *new_ddf_stem)
{
    debug_handler("Fp_polynomial", "generate_baby_steps( Fp_polynomial&, Fp_polynomial&, Fp_polynomial&, lidia_size_t, char*, lidia_size_t )");

    f.comp_modulus(h, "generate_baby_steps");

    my_timer t;
    ofstream s;
    bool verbose = single_factor < Fp_polynomial >::verbose();

    if (verbose) t.start("generating baby steps...");

    poly_modulus F(f);

    poly_argument H;
    H.build(h, F, 2 * square_root(F.deg()));


    h1.assign(h);

    lidia_size_t i;

    for (i = 1; i <= k - 1; i++)
    {
	s.open(create_file_name(new_ddf_stem, "baby", i), ios::out);
	if (!s)
	    lidia_error_handler_c("Fp_polynomial",
	    "generate_baby_steps( ... )::open write error",
	    cout << "open write error: " << create_file_name(new_ddf_stem,
	    "baby", i) << "\n";
	    );

	s << h1 << "\n";
	s.close();

	H.compose(h1, h1, F);
	if (verbose) cerr << "+";
    }

    if (verbose) t.stop();
}


void 
generate_giant_steps(const Fp_polynomial & f, const Fp_polynomial & h,
	lidia_size_t l, const char *new_ddf_stem)
{
    debug_handler("Fp_polynomial", "generate_giant_steps( Fp_polynomial&,Fp_polynomial&, lidia_size_t, char*, lidia_size_t )");

    f.comp_modulus(h, "generate_giant_steps");

    my_timer t;
    ofstream s;
    bool verbose = single_factor < Fp_polynomial >::verbose();

    if (verbose) t.start("generating giant steps...");

    poly_modulus F(f);

    poly_argument H;
    H.build(h, F, 2 * square_root(F.deg()));

    Fp_polynomial h1;

    h1.assign(h);

    lidia_size_t i;

    for (i = 1; i <= l; i++)
    {
	s.open(create_file_name(new_ddf_stem, "giant", i), ios::out);
	if (!s)
	    lidia_error_handler_c("Fp_polynomial",
	    "generate_giant_steps( ... )::open write error",
	    cout << "open write error: " << create_file_name(new_ddf_stem,
		    "giant", i) << "\n";
	    );

	s << h1 << "\n";
	s.close();

	if (i != l)
	    H.compose(h1, h1, F);

	if (verbose) cerr << "+";
    }

    if (verbose) t.stop();
}




void 
new_add_factor(factorization < Fp_polynomial > &u, 
		const Fp_polynomial & g, lidia_size_t m)
//used in ddf, the exponents of 'factors' are the degrees
//of the irred. polynomials !!!
{
    debug_handler("Fp_polynomial", "new_add_factor(factorization< Fp_polynomial >&, Fp_polynomial&, lidia_size_t, lidia_size_t )");

    u.append(g, m);

    if (single_factor < Fp_polynomial >::verbose())
	cerr << "split " << m << " " << g.degree() << endl;
}




void 
new_process_table(factorization < Fp_polynomial > &u, Fp_polynomial & f, 
		  const poly_modulus & F, base_vector < Fp_polynomial > &buf,
		  lidia_size_t size, lidia_size_t StartInterval,
		  lidia_size_t IntervalLength)
//instead of computing gcd(f,buf[i]) for all i, we compute
//g = product of all buf[i]. If gcd(f,g) = 1, we do not have to compute
//any further gcd and can return immediately
{
    debug_handler("Fp_polynomial", "new_process_table( factorization< Fp_polynomial >&, Fp_polynomial&, poly_modulus&, base_vector<Fp_polynomial>&, lidia_size_t, lidia_size_t, lidia_size_t, lidia_size_t )");
    
    if (size == 0)
	return;

    Fp_polynomial & g = buf[size - 1];

    lidia_size_t i;

    for (i = 0; i < size - 1; i++)
	multiply(g, g, buf[i], F);

    gcd(g, f, g);
    if (g.degree() == 0)
	return;

    divide(f, f, g);

    lidia_size_t d = (StartInterval - 1) * IntervalLength + 1;
    i = 0;
    lidia_size_t interval = StartInterval;

//next, we 'refine' our gcd computations
    while (i < size - 1 && 2 * d <= g.degree())
    {
	gcd(buf[i], buf[i], g);
	if (buf[i].degree() > 0)
	{
	    new_add_factor(u, buf[i], interval);
	    divide(g, g, buf[i]);
	}
	i++;
	interval++;
	d += IntervalLength;
    }

    if (g.degree() > 0)
    {
	if (i == size - 1)
	    new_add_factor(u, g, interval);
	else
	    new_add_factor(u, g, (g.degree() + IntervalLength - 1) / IntervalLength);
    }
}


void 
giant_refine(factorization < Fp_polynomial > &u, const Fp_polynomial & ff,
		lidia_size_t k, const char *new_ddf_stem)
{
    debug_handler("Fp_polynomial", "giant_refine( factorization< Fp_polynomial >&, Fp_polynomial&, lidia_size_t, char*, lidia_size_t )");

    my_timer t;
    bool verbose = single_factor < Fp_polynomial >::verbose();

    if (verbose)
    {
	cerr << "giant refine...\n";
	t.start("giant refine time: ");
    }

    u.kill();

    base_vector < Fp_polynomial > BabyStep;

    fetch_baby_steps(BabyStep, k, new_ddf_stem, ff.modulus());

    base_vector < Fp_polynomial >
			buf((lidia_size_t)DDF_GCD_BLOCKING_FACTOR,
			    (lidia_size_t)DDF_GCD_BLOCKING_FACTOR);

    Fp_polynomial f;
    f.assign(ff);

    poly_modulus F;
    F.build(f);

    Fp_polynomial g;
    Fp_polynomial h;

    lidia_size_t size = 0;
    lidia_size_t first_gs = 0;	//initialized only because of compiler warnings

    lidia_size_t d = 1;

    while (2 * d <= f.degree())
    {

	lidia_size_t old_n = f.degree();

	lidia_size_t gs = (d + k - 1) / k;
	lidia_size_t bs = gs * k - d;

	if (bs == k - 1)
	{
	    size++;
	    if (size == 1)
		first_gs = gs;
	    fetch_giant_step(g, gs, F, new_ddf_stem);
	    subtract(buf[size - 1], g, BabyStep[bs]);
	}
	else
	{
	    subtract(h, g, BabyStep[bs]);
	    multiply(buf[size - 1], buf[size - 1], h, F);	//poly_modulus

	}

	if (verbose && bs == 0)
	    cerr << "+";

	if (size == DDF_GCD_BLOCKING_FACTOR && bs == 0)
	{
	    new_process_table(u, f, F, buf, size, first_gs, k);
	    if (verbose) cerr << "*";
	    size = 0;
	}

	d++;

	if (2 * d <= f.degree() && f.degree() < old_n)
	{
	    F.build(f);

	    lidia_size_t i;
	    for (i = 1; i <= k - 1; i++)
		remainder(BabyStep[i], BabyStep[i], F);
	}
    }

    if (size > 0)
    {
	new_process_table(u, f, F, buf, size, first_gs, k);
	if (verbose) cerr << "*";
    }

    if (f.degree() > 0)
	new_add_factor(u, f, -1);

    if (verbose) t.stop();
}


void 
interval_refine(factorization< Fp_polynomial > &factors,
	const Fp_polynomial &ff, lidia_size_t k, lidia_size_t gs,
	const base_vector< Fp_polynomial > &BabyStep, const char *new_ddf_stem)
{
    debug_handler("Fp_polynomial", "interval_refine( factorization< Fp_polynomial >&, Fp_polynomial&, lidia_size_t, lidia_size_t, base_vector<Fp_polynomial>&, lidia_size_t )");

    if (BabyStep.size() != 0)
	ff.comp_modulus(BabyStep[0], "interval_refine");

    base_vector < Fp_polynomial >
			buf((lidia_size_t)DDF_GCD_BLOCKING_FACTOR, 
			    (lidia_size_t)DDF_GCD_BLOCKING_FACTOR);

    Fp_polynomial f(ff);

    poly_modulus F;
    F.build(f);

    Fp_polynomial g;

    fetch_giant_step(g, gs, F, new_ddf_stem);

    lidia_size_t size = 0;

    lidia_size_t first_d = 0;	//initialized only because of compiler warnings

    lidia_size_t d = (gs - 1) * k + 1;
    lidia_size_t bs = k - 1;

    while (2 * d <= f.degree())
    {
	lidia_size_t old_n = f.degree();

	if (size == 0)
	    first_d = d;
	remainder(buf[size], BabyStep[bs], F);
	subtract(buf[size], buf[size], g);
	size++;

	if (size == DDF_GCD_BLOCKING_FACTOR)
	{
	    new_process_table(factors, f, F, buf, size, first_d, 1);
	    size = 0;
	}

	d++;
	bs--;

	if (bs < 0)
	{//exit
	    d = f.degree() + 1;
	}

	if (2 * d <= f.degree() && f.degree() < old_n)
	{
	    F.build(f);
	    remainder(g, g, F);
	}
    }

    new_process_table(factors, f, F, buf, size, first_d, 1);

    if (f.degree() > 0)
	new_add_factor(factors, f, f.degree());
}




void 
baby_refine(factorization < Fp_polynomial > &factors, 
	    const factorization < Fp_polynomial > &u,
	    lidia_size_t k, const char *new_ddf_stem)
{
    debug_handler("Fp_polynomial", "baby_refine( factorization< Fp_polynomial >&, factorization< Fp_polynomial >&, lidia_size_t, char*, lidia_size_t )");

    my_timer t;
    bool verbose = single_factor < Fp_polynomial >::verbose();

    if (verbose)
    {
	cerr << "baby refine...\n";
	t.start("baby refine time: ");
    }

    factors.kill();

    base_vector < Fp_polynomial > BabyStep;

    lidia_size_t i;

    for (i = 0; i < u.no_of_composite_components(); i++)
    {
	const Fp_polynomial & g = u.composite_base(i).base();
	lidia_size_t gs = u.composite_exponent(i);

	if (gs == -1 || 2 * ((gs - 1) * k + 1) > g.degree())
	    new_add_factor(factors, g, g.degree());
	else
	{
	    if (BabyStep.size() == 0)
		fetch_baby_steps(BabyStep, k, new_ddf_stem, u.composite_base(0).base().modulus());
	    interval_refine(factors, g, k, gs, BabyStep, new_ddf_stem);
	}
    }

    if (verbose) t.stop();
}



void 
ddf(factorization < Fp_polynomial > &factors,
	const Fp_polynomial & f, const Fp_polynomial & h)
// Performs distinct-degree factorization.
// h  = X^p mod f
// the exponents of 'F' are the degrees of the irred. factors of 'f' !!!
{
    debug_handler("Fp_polynomial", "ddf( factorization< Fp_polynomial >&, Fp_polynomial&, Fp_polynomial&, lidia_size_t )");

    f.comp_modulus(h, "ddf");

    if (f.degree() <= 0)
	lidia_error_handler("Fp_polynomial", "ddf( ... )::polynomial has degree <= 0");

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

    char *new_ddf_stem = new char[128];
    if (!new_ddf_stem)
	lidia_error_handler("Fp_polynomial","ddf::out of memory");
    create_stem(new_ddf_stem);


    lidia_size_t B = f.degree() / 2;
    lidia_size_t k = square_root(B);
    lidia_size_t l = (B + k - 1) / k;

    Fp_polynomial h1;
    generate_baby_steps(h1, f, h, k, new_ddf_stem);
    generate_giant_steps(f, h1, l, new_ddf_stem);

    factorization < Fp_polynomial > u;
    giant_refine(u, f, k, new_ddf_stem);
    baby_refine(factors, u, k, new_ddf_stem);

    file_cleanup(k, l, new_ddf_stem);
    delete[] new_ddf_stem;
}



factorization< Fp_polynomial > ddf(const Fp_polynomial& f)
{
    factorization< Fp_polynomial > factors;
    poly_modulus F(f);
    Fp_polynomial b;
    power_x(b, f.modulus(), F);
    ddf(factors, f, b);
    return factors;
}

factorization< Fp_polynomial > single_factor< Fp_polynomial >::ddf() const
{
    return ::ddf(rep);
}
