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

#ifndef LIDIA_FP_POL_FFT__H
#define LIDIA_FP_POL_FFT__H

#ifndef HEADBANGER


#if defined(HAVE_MAC_DIRS) || defined(__MWERKS__)
#include <LiDIA:lidia.h>
#include <LiDIA:base_vector.h>
#include <LiDIA:crt.h>
#include <LiDIA:crt_table.h>
#include <LiDIA:Fp_polynomial.h>

#else

#include <LiDIA/lidia.h>
#include <LiDIA/base_vector.h>
#include <LiDIA/crt.h>
#include <LiDIA/crt_table.h>
#include <LiDIA/Fp_polynomial.h>

#endif


//---------------------------------------
// NOTE : for implementations see files
//  fft.c, fft_rep.c, modular_fft_rep.c
//---------------------------------------



class Fp_polynomial;
class poly_modulus;

lidia_size_t next_power_of_two(lidia_size_t m);
lidia_size_t square_root(lidia_size_t n);


/***************************************************************
					class fft_table
***************************************************************/
			
class fft_table
{
	/***********************************
			class bit_reverse_table
	***********************************/
	class bit_reverse_table
	{
		lidia_size_t** mem;
		lidia_size_t allocated;

		lidia_size_t rev_inc(lidia_size_t ia, lidia_size_t k);
		lidia_size_t* table(lidia_size_t k);
			//returns mem[k], initializes if mem[k]==0 or k>allocated
	public:
		bit_reverse_table() : mem(0), allocated(0)
		{ debug_handler( "fft_table::bit_reverse_table", "bit_reverse_table()" ); }
		~bit_reverse_table();

		void copy(sdigit* A, const sdigit* a, lidia_size_t k);
			//copies a in "bitreverse order" into A
	};

    /***********************************
            class fft_crt_table
    ***********************************/
    class fft_crt_table
    {   //only used in fft_table
        crt_table CT;
        const mcp mod;
        fft_crt_table* next;    //pointer to next fft_crt_table

        fft_crt_table();        //disable
		fft_crt_table(const fft_crt_table&);
		
    public:
        fft_crt_table(sdigit* vec, lidia_size_t length, const mcp& m);

        void info() const;
		
		friend class fft_data;
		friend class fft_table;
		friend class fft_rep;		//get_result
		friend class modular_fft_rep;	//get_result
    };




/*************************************
	members & methods of fft_table
*************************************/
private:
	sdigit* FFT_primes;		//vector of FFT-primes
	lidia_size_t num_FFT_primes;		//number of FFT-primes

	lidia_size_t max_degree;	//fft_table is build for degree 2^max_degree
		//sort criterion for fft_tables

	sdigit** RootTable;
	sdigit** RootInvTable;
	sdigit** TwoInvTable;
		//RootTable[i][j] = w^{2^{MaxRoot-j}},
		// where w is a primitive 2^max_degree root of unity for FFT_prime[i]
		//RootInvTable[i][j] = 1/RootTable[i][j] mod FFTPrime[i]
		//TwoInvTable[i][j] = 1/2^j mod FFTPrime[i]

	fft_crt_table* CRT_table_list;
		//list of crt_tables, sorted by modulus

	lidia_size_t reference_counter;


//this is a self-organizing list !
	fft_table* next;	//pointer to next fft_table
    static fft_table* head;		//pointer to head of list

	static fft_table* look_for_fft_table(lidia_size_t l);
		//returns pointer to fft_table which is at least built for
		//convolutions of degree 2^l
	static void decr_reference(fft_table* fptr);
		//decreases reference_counter by one and deletes *fptr 


//the FFT-routine
	static bit_reverse_table BitReverse;

	void FFT(long* A, const long* a, lidia_size_t k, long q, const long* root);
		// the low-level FFT routine.
		// computes a 2^k point FFT modulo q, using the table root for the roots.


//generating primes
	sdigit next_prime(sdigit old) const;
		// returns q where q is less than (old % 2^max_degree)+1 and prime,
		// q = 1 mod 2^max_degree
		// error if no more primes are found.
		
	void seek_primes(lidia_size_t b_log, lidia_size_t sum_log);
		//enlarges FFT_primes[] by new_num primes where
		//sum_{i=0..new_num}(log_2(new_primes[i])) + sum_log > b_log




	fft_table();    //disable

public:
	fft_table(lidia_size_t l);
		//creates a new fft_table for degree 2^l

	~fft_table();

    static void info();

	fft_crt_table* use_fft_table(const mcp& m);
		//checks if a crt_table is already initialized; if not, enlarges
		//prime vector (if necessary) and initializes a new crt_table

	
	friend class fft_data;
	friend class FFT_info_class;
};





/***************************************************************
					class fft_data
***************************************************************/

class fft_data
{
private:
	fft_table* FT;
	fft_table::fft_crt_table* CT;

public:
	fft_data() :
		FT(0),
		CT(0)
	{
	  	debug_handler( "fft_data", "fft_data( void )" );
	}
	
	fft_data(const fft_data& x)
	{
	  	debug_handler( "fft_data", "fft_data( const fft_data& )" );
	  	init(x);
	}

	~fft_data()
	{
	  	debug_handler( "fft_data", "destructor" );
	  	clear();
	}

	void clear();
	
	void init(lidia_size_t l, const mcp& m);
		//initializes for convolutions of degree 2^l modulo m.mod()`

	void init(const fft_data& x);
		//initializes with the same values as x
	
	inline crt_table& crttable() const
	{
	    return CT->CT;
	}

	inline lidia_size_t maxroot() const
	{
	  	return FT->max_degree;
	}

	bool set_new_length(lidia_size_t l);
		//prepares for convolutions of degree 2^l
		//only possible, if CT!=0
		//returns true if FT has changed, which means that CT has changed

	void evaluate(sdigit* A, const sdigit* a, lidia_size_t k, lidia_size_t ix) const;
 	void interpolate(sdigit* A, const sdigit* a, lidia_size_t k, lidia_size_t ix) const;
	void pointwise_multiply(sdigit* x, const sdigit* a, const sdigit* b, lidia_size_t k, lidia_size_t ix) const;
	void pointwise_add(sdigit* x, const sdigit* a, const sdigit* b, lidia_size_t k, lidia_size_t ix) const;
	void pointwise_subtract(sdigit* x, const sdigit* a, const sdigit* b, lidia_size_t k, lidia_size_t ix) const;
	void pointwise_add_mul(sdigit* x, const sdigit* a, const sdigit* b, const sdigit* c, const sdigit* d, lidia_size_t k, lidia_size_t ix) const;

	void divide_by_power_of_two(sdigit* vec, lidia_size_t length, lidia_size_t l, lidia_size_t index) const;
       //divides vec[0..length-1] by 2^l mod FFT_prime[index]
       //l must be <= max_degree

	friend class fft_rep;
	friend class modular_fft_rep;
	
	friend void multiply(fft_rep &, const fft_rep &, const fft_rep& );
	friend void reduce(fft_rep &, const fft_rep &, lidia_size_t);
	friend void reduce(fft_rep &, const modular_fft_rep &, lidia_size_t, lidia_size_t);
	friend void reduce(modular_fft_rep&, const modular_fft_rep&, lidia_size_t);
	friend void multiply(modular_fft_rep &, const modular_fft_rep &, const modular_fft_rep &, lidia_size_t);
	friend void add(modular_fft_rep &, const modular_fft_rep &, const modular_fft_rep &, lidia_size_t);
	friend void subtract(modular_fft_rep &, const modular_fft_rep &, const modular_fft_rep &, lidia_size_t);
	friend void add_mul(modular_fft_rep &, const modular_fft_rep &, const modular_fft_rep &, const modular_fft_rep &, const modular_fft_rep &, lidia_size_t );
	friend void multiply(modular_fft_rep &, const fft_rep &, const modular_fft_rep &, lidia_size_t);
	friend void reduce(modular_fft_rep &, const fft_rep &, lidia_size_t, lidia_size_t);
	friend void subtract(modular_fft_rep &, const fft_rep &, const modular_fft_rep &, lidia_size_t);

};


 








class poly_mod_rep;


/***************************************************************

				class modular_fft_rep

****************************************************************/
// FFT representation of polynomials modulo an FFTPrime


class modular_fft_rep
{
	static sdigit *stat_vec;
	static lidia_size_t stat_alloc;
		//static auxiliary vector
		//stat_vec is always enlarged if too small, never made smaller
		
	sdigit *s;
	lidia_size_t k;    	// length of representation is 2^k
	lidia_size_t max_k;	// 2^max_k sdigits allocated for s

	fft_data F;  //used for any FFT-operation
		
	crt C;
	modular_fft_rep();	//disable
	void set_length(lidia_size_t l);
		//allocates space for convolutions of degree 2^l (s, stat_vec)	
	void init(lidia_size_t l, const mcp& m);

//	friend class fft_rep;

public:
	modular_fft_rep(const modular_fft_rep& R);
	modular_fft_rep(const fft_rep& R);
		//both constructors initialize the current modular_fft_rep
		//with the same fft_data as R
	
	modular_fft_rep(lidia_size_t l, const mcp& m);
		//for convolutions of size 2^l, for modulus m

	~modular_fft_rep();

	void set_size(lidia_size_t l);
		//prepares for convolutions of degree 2^l

	inline lidia_size_t number_of_primes() const
	{
		return C.number_of_primes();
	}

	inline void to_modular_fft_rep(const Fp_polynomial &a, lidia_size_t index)
	{
	  	to_modular_fft_rep(a, 0, a.degree(), index);
	}
	
	void to_modular_fft_rep(const Fp_polynomial &a, lidia_size_t lo, lidia_size_t hi, lidia_size_t index);

	void to_modular_fft_rep_SP(const Fp_polynomial &a, lidia_size_t lo, lidia_size_t hi, lidia_size_t index);
	void from_modular_fft_rep_SP(lidia_size_t lo, lidia_size_t hi, lidia_size_t index);

	void to_modular_fft_rep(const poly_mod_rep &a, lidia_size_t lo, lidia_size_t hi, lidia_size_t index);


	void from_modular_fft_rep(lidia_size_t lo, lidia_size_t hi, lidia_size_t index);
	//non-destructive !

	void get_result(Fp_polynomial &a, lidia_size_t lo, lidia_size_t hi);
	void get_result_ptr(bigint *a, lidia_size_t lo, lidia_size_t hi);
		//used in build_from_roots, enough space must be allocated


	friend void reduce(modular_fft_rep &x, const modular_fft_rep &a, lidia_size_t l);
	// reduces a 2^k point modular_fft_rep to a 2^l point modular_fft_rep
	// input may alias output

	friend void multiply(modular_fft_rep &x,
				const modular_fft_rep &a, const modular_fft_rep &b, lidia_size_t index);
	friend void add(modular_fft_rep &x,
				const modular_fft_rep &a, const modular_fft_rep &b, lidia_size_t index);
	friend void subtract(modular_fft_rep &x,
				const modular_fft_rep &a, const modular_fft_rep &b, lidia_size_t index);
	friend void add_mul(modular_fft_rep &x,
				const modular_fft_rep &a, const modular_fft_rep &b,
				const modular_fft_rep &c, const modular_fft_rep &d, lidia_size_t index);


//operations between fft_rep and modular_fft_rep
	friend void multiply(modular_fft_rep &x,
				const fft_rep &a, const modular_fft_rep &b, lidia_size_t index);
	friend void reduce(modular_fft_rep &x, const fft_rep &a, lidia_size_t l, lidia_size_t index);
	friend void reduce(fft_rep &x, const modular_fft_rep &a, lidia_size_t l, lidia_size_t index);
	friend void subtract(modular_fft_rep &x,
				const fft_rep &a, const modular_fft_rep &b, lidia_size_t index);


friend void main_test(lidia_size_t argc, char **argv);

};













/***************************************************************

					 class fft_rep

****************************************************************/
// FFT representation of polynomials

class fft_rep
{
	lidia_size_t k;                // a 2^k point representation
	lidia_size_t max_k;             // maximum space allocated
	sdigit **tbl;
	fft_data F;  //used for any FFT-operation
	crt C;
	
	void set_length(lidia_size_t l);
		//allocates space for convolutions of degree 2^l
	friend class modular_fft_rep;

public:
	fft_rep();
	fft_rep(lidia_size_t InitK, const mcp& m);
		//for convolutions of size 2^InitK and modulus m

	fft_rep(const fft_rep& R);
//	fft_rep(const modular_fft_rep& R);
		//both constructors initialize the current fft_rep
		//with the same arguments as R
		
	~fft_rep();

	void set_size(lidia_size_t NewK);
		//prepares for convolutions of degree 2^NewK

	void init(lidia_size_t l, const mcp& m);
	void init(lidia_size_t l, const fft_data& FD);


	void to_fft_rep(const Fp_polynomial& x, lidia_size_t lo, lidia_size_t hi);
	// computes an n = 2^k point convolution of x[lo..hi].

	inline void to_fft_rep(const Fp_polynomial& x)
	{
		to_fft_rep(x, 0, x.degree());
	}


	void from_fft_rep(Fp_polynomial& x, lidia_size_t lo, lidia_size_t hi);
	// converts from FFT-representation to coefficient representation
	// only the coefficients lo..hi are computed
	// NOTE: this version does not destroy the data in (*this)


	friend void multiply(fft_rep& z, const fft_rep& x, const fft_rep& y);

	friend void reduce(fft_rep& x, const fft_rep& a, lidia_size_t k);
	// reduces a 2^l point FFT-rep to a 2^k point FFT-rep
	// input may alias output


//operations between fft_rep and modular_fft_rep
	friend void multiply(modular_fft_rep &x,
				const fft_rep &a, const modular_fft_rep &b, lidia_size_t index);
	friend void reduce(modular_fft_rep &x, const fft_rep &a, lidia_size_t l, lidia_size_t index);
	friend void reduce(fft_rep &x, const modular_fft_rep &a, lidia_size_t l, lidia_size_t index);
	friend void subtract(modular_fft_rep &x, const fft_rep &a, const modular_fft_rep &b,
															lidia_size_t index);

//special purpose functions, used only in
//		void update_map(base_vector<bigint>& x, const base_vector<bigint>& a,
//					   const poly_multiplier& B, const poly_modulus& F)
	void rev_to_fft_rep(const base_vector<bigint>& x, lidia_size_t lo, lidia_size_t hi, lidia_size_t offset);
	void rev_from_fft_rep(base_vector<bigint>&, lidia_size_t lo, lidia_size_t hi);
	void add_expand(const fft_rep& a);


}; //class fft_rep






#endif

#endif
