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

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


#define WDH 3
#define STARTVALUE 4
    //STARTVALUE must be > 3

Fp_polynomial test_array1[WDH];
Fp_polynomial test_array2[WDH];

void init_test_arrays(const bigint &p, lidia_size_t n1, lidia_size_t n2)
{
    for (lidia_size_t i = 0; i < WDH; i++)
    {
	randomize(test_array1[i], p, n1);
	randomize(test_array2[i], p, n2);
    }
}

void
test_mul(long &t_p, long &t_f, int n, const bigint &p)
{
    init_test_arrays(p, n, n);
    Fp_polynomial c;
    int k;
    timer t;
    t.start_timer();
    for (k = 0; k < WDH; k++) 
	plain_mul(c, test_array1[k], test_array2[k]);
    t.stop_timer();
    t_p = t.user_time();
    
    t.start_timer();
    for (k = 0; k < WDH; k++)
	fft_mul(c, test_array1[k], test_array2[k]);
    t.stop_timer();
    t_f = t.user_time();
}


void
test_div(long &t_p, long &t_f, int n, const bigint &p)
{
    init_test_arrays(p, 2*n, n);
    timer t;
    int k;
    Fp_polynomial c, d;
    t.start_timer();
    for (k = 0; k < WDH; k++)
	plain_div_rem(d, c, test_array1[k], test_array2[k]);
    t.stop_timer();
    t_p = t.user_time();
    
    t.start_timer();
    for (k = 0; k < WDH; k++)
	fft_div_rem(d, c, test_array1[k], test_array2[k]);
    t.stop_timer();
    t_f = t.user_time();
}

void
test_inv(long &t_p, long &t_f, int n, const bigint &p)
{
    timer t;
    int k;
//    init_test_arrays(p, n, 1);
    for (k = 0; k < WDH; k++)
	do
	randomize(test_array1[k], p, n);
	while (test_array1[k][0] == 0);
    Fp_polynomial c;
    t.start_timer();
    for (k = 0; k < WDH; k++)
	plain_inv(c, test_array1[k], n);
    t.stop_timer();
    t_p = t.user_time();
    
    t.start_timer();
    for (k = 0; k < WDH; k++)
	newton_inv(c, test_array1[k], n);
    t.stop_timer();
    t_f = t.user_time();
}


bigint seek_prime_with_n_bits(int n)
{
    if (n < 2) n = 2;
    bigint t(1);
    t = (t << n) - 1;		//the largest n-bit number
    return previous_prime(t);
}


int
crossover(void (*FUNCTION)(long&, long&, int, const bigint&), const bigint &p)
//compute crossover-point for a given FUNCTION and a fixed modulus p
{
    long l1, l2;
    long plain_time[20], new_time[20];
    int counter = -1;
    cout<<"prime : "<<p<<" ("<<p.bit_length()<<" bits)"<<endl;

    int n;
    
    cout<<"\tdegree\t\tplain \tnew"<<endl;
    cout<<"\t-----------------------------------"<<endl;
    n = 1 << (STARTVALUE-1);

    do
    {
	counter++;
	n = 2*n;
	FUNCTION(l1, l2, n, p);
	cout<<"\t"<<n<<"\t\t"<<l1<<"\t"<<l2<<endl;
	plain_time[counter] = l1;
	new_time[counter] = l2;
    }
    while (((double)l1/(double)l2 < 1.1) || (l1 + l2 < 100));

    bool measured = true;
    int interval = n/2;
    while (interval > 4)
    {
	if (l1 < l2)
	{
	    n += interval;
	    measured = false;
	}
	else
	    n -= interval;
	interval = interval/2;
	if (measured)
	{
	    counter--;
	    l1 = plain_time[counter];
	    l2 = new_time[counter];
	    cout<<"\t"<<n<<"\t\t*"<<l1<<"\t*"<<l2<<endl;
	}
	else
	{
	    FUNCTION(l1, l2, n, p);
	    cout<<"\t"<<n<<"\t\t"<<l1<<"\t"<<l2<<endl;
	}
    }
    //set crossover-points
    cout<<"Set crossover to "<<n<<endl;
    return n;
}


void do_work(int crov[CROV_NUM_VALUES],
	void (*FUNCTION)(long&, long&, int, const bigint&),
	const char* NAME, const bigint prime_table[CROV_NUM_VALUES])
//compute crossover-points 
{
    int i;
    cout<<NAME<<"  : "<<endl;
    for (i = 0; i < CROV_NUM_VALUES; i++)
	crov[i] = crossover(FUNCTION, prime_table[i]);
}


main()
{
    cout<<"This program tries to find some crossover points."<<endl;
    cout<<"This may take a while."<<endl<<endl;

    int x[CROV_NUM_VALUES] = {0, 10, 20, 40, 80, 160, 320, 640, 1280, 2560};
	//bitlengths of moduli
    int y_mul[CROV_NUM_VALUES];
    int y_div[CROV_NUM_VALUES];
    int y_inv[CROV_NUM_VALUES];
    int halfgcd = 16;
    int gcd = 128;
    int log2_newton = 5;

    int i;
    bigint prime_table[CROV_NUM_VALUES];
    cout<<"Generating primes"<<endl;
    for (i = 0; i < CROV_NUM_VALUES; i++)
    {
	prime_table[i] = seek_prime_with_n_bits(x[i]);
	cout<<"+"<<flush;
//	string_to_bigint(string_table[i], prime_table[i]);
    }
    cout<<" done."<<endl<<endl;

    do_work(y_mul, test_mul, "multiply", prime_table);
    for (i = 0; i < CROV_NUM_VALUES; i++)
	cout<<"y_mul["<<i<<"] = "<<y_mul[i]<<endl;
    
    do_work(y_inv, test_inv, "invert", prime_table);
    for (i = 0; i < CROV_NUM_VALUES; i++)
	cout<<"y_inv["<<i<<"] = "<<y_inv[i]<<endl;

    do_work(y_div, test_div, "divide", prime_table);
    for (i = 0; i < CROV_NUM_VALUES; i++)
	cout<<"y_div["<<i<<"] = "<<y_div[i]<<endl;


    cout<<"Writing file...";

    ofstream s;
    s.open("crossover.tbl");
    if (!s)
	lidia_error_handler("Fp_polynomial","crossover::error while writing the file \"crossover.tbl\"");

    s<<"// LiDIA - a library for computational number theory\n";
    s<<"//   Copyright (c) 1994, 1995 by the LiDIA Group\n";
    s<<"//\n";
    s<<"// This file was generated automatically by 'make crossover'.\n";
    s<<"// For default values, copy 'crossover.tbl.default' to\n";
    s<<"// 'crossover.tbl', type  'touch crossover.tbl' and\n";
    s<<"// 'make'.\n";
    s<<"//\n";
    s<<"\n";

    s<<"static int x_val[CROV_NUM_VALUES] = { "<<x[0];
    for (i = 1; i < CROV_NUM_VALUES; i++)
	s<<", "<<x[i];
    s<<" };\n";

    s<<"static int fftmul_val[CROV_NUM_VALUES] = { "<<y_mul[0];
    for (i = 1; i < CROV_NUM_VALUES; i++)
	s<<", "<<y_mul[i];
    s<<" };\n";
    
    s<<"static int fftdiv_val[CROV_NUM_VALUES] = { "<<y_div[0];
    for (i = 1; i < CROV_NUM_VALUES; i++)
	s<<", "<<y_div[i];
    s<<" };\n";
    
    s<<"static int inv_val[CROV_NUM_VALUES] = { "<<y_inv[0];
    for (i = 1; i < CROV_NUM_VALUES; i++)
	s<<", "<<y_inv[i];
    s<<" };\n";
    
    s<<"\n";
    s<<"int gcd_val = "<<gcd<<";\n";
    s<<"int halfgcd_val = "<<halfgcd<<";\n";
    s<<"int log2_newton_val = "<<log2_newton<<";\n";
    s<<endl;
    s.close();

    cout<<" done."<<endl;
}

