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

#ifndef LIDIA_POLY_MODULUS__H
#define LIDIA_POLY_MODULUS__H


#ifndef HEADBANGER


#if defined(HAVE_MAC_DIRS) || defined(__MWERKS__)

#include <LiDIA:Fp_polynomial.h>
#include <LiDIA:Fp_polynomial_fft.h>

#else

#include <LiDIA/Fp_polynomial.h>
#include <LiDIA/Fp_polynomial_fft.h>

#endif


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

        Modular Arithmetic with Pre-conditioning

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



// If you need to do a lot of arithmetic modulo a fixed f,
// build poly_modulus F for f.  This pre-computes information about f
// that speeds up the computation a great deal.
// f should be monic, and deg(f) > 0.

class Fp_ratfunc;

class poly_modulus
{

	friend void add      (Fp_ratfunc&,const Fp_ratfunc&,const Fp_ratfunc&);
	friend void subtract (Fp_ratfunc&,const Fp_ratfunc&,const Fp_ratfunc&);

	Fp_polynomial f;   	// modulus, a monic polynomial
	lidia_size_t deg_f;     // n = deg(f)
	bool use_FFT;		// flag indicating whether FFT should be used.
	lidia_size_t crov;	// crossover point
	
	lidia_size_t k;     	// least k s/t 2^k >= deg_f
	lidia_size_t l;     	// least l s/t 2^l >= 2deg_f-3
	fft_rep FRep; 		// 2^k point rep of f
				// H = rev((rev(f))^{-1} remainder x^{deg_f-1})
	fft_rep HRep; 		// 2^l point rep of H
	fft_data F;		// used for FRep and HRep


	void rem21(Fp_polynomial& x, const Fp_polynomial& a) const;
	// x = a % f
	// deg(a) <= 2(n-1), where n = f.degree()


public:
    poly_modulus() : deg_f(-1), use_FFT(false)
	{
		debug_handler( "poly_modulus", "poly_modulus ( void )" );
	}

	poly_modulus(const poly_modulus & P)
	{
		debug_handler( "poly_modulus", "poly_modulus ( poly_modulus& )" );
		build(P.modulus());
	}
					  
	poly_modulus(const Fp_polynomial& ff) // MM
	{
		debug_handler( "poly_modulus", "poly_modulus ( Fp_polynomial& )" );
		build(ff);
	}

	~poly_modulus()
	{
		debug_handler( "poly_modulus", "~poly_modulus ( void )" );
	}

	void build(const Fp_polynomial& ff); // MM
	
	poly_modulus & operator =(const poly_modulus & P)
	{
		build(P.modulus());
		return *this;
	}


	inline bool use_fft() const
	{
		debug_handler( "poly_modulus", "use_fft ( void )" );
		return use_FFT;
	}

	inline lidia_size_t deg() const
	{
		debug_handler( "poly_modulus", "deg ( void )" );
		return deg_f;
	}

	inline const Fp_polynomial& modulus() const
	{
		debug_handler( "poly_modulus", "modulus ( void )" );
		return f;
	}

	inline void forward_modulus(Fp_polynomial &x) const
	{
	    if (deg_f != -1)
		x.MOD = f.MOD;	// = x.set_modulus(f.modulus())
	    else
		lidia_error_handler( "poly_modulus", 
		"forward_modulus(...)::poly_modulus was not inaitalized" );
	}


// -------------------- friends --------------------


	friend void remainder(Fp_polynomial& x, const Fp_polynomial& a,
			    const poly_modulus& F);
	// x = a % f, no restrictions on deg(a);  makes repeated calls to rem21


	friend void multiply(Fp_polynomial& x, const Fp_polynomial& a,
			    const Fp_polynomial& b, const poly_modulus& F);
	// x = (a * b) % f
	// deg(a), deg(b) < f.degree()


	friend void multiply(Fp_polynomial& x, const Fp_polynomial& a,
			    const poly_multiplier& B, const poly_modulus& F);
	// x = (a * b) % f


	friend void square(Fp_polynomial& x, const Fp_polynomial& a,
			    const poly_modulus& F);
	// x = a^2 % f
	// deg(a) < f.degree()


	friend void power(Fp_polynomial& x, const Fp_polynomial& a,
			    const bigint&  e, const poly_modulus& F);
	// x = a^e % f

	friend void power_x(Fp_polynomial& x, const bigint&  e, const poly_modulus& F);
	// x = X^e % f

	friend void power_x_plus_a(Fp_polynomial& x, const bigint& a,
			    const bigint&  e, const poly_modulus& F);
	// x = (X + a)^e % f


//fractions.[ch]
	friend void remainder(Fp_polynomial& x, fft_rep& R1, const poly_modulus& F,
				modular_fft_rep& R2, Fp_polynomial& P1);
	friend void add_frac(Fp_polynomial& x, Fp_polynomial& y, 
				const Fp_polynomial& a, const Fp_polynomial& b,
				const Fp_polynomial& c, const Fp_polynomial& d,
				const poly_modulus& F);
	friend void subtract_frac(Fp_polynomial& x, Fp_polynomial& y, 
				const Fp_polynomial& a, const Fp_polynomial& b,
				const Fp_polynomial& c, const Fp_polynomial& d,
				const poly_modulus& F);
	friend bool eq_frac(const Fp_polynomial& a, const Fp_polynomial& b,
				const Fp_polynomial& c, const Fp_polynomial& d, 
				const poly_modulus& F);
//compose.[ch]
	friend void update_map(base_vector<bigint>& x, const base_vector<bigint>& a,
				const poly_multiplier& B, const poly_modulus& F);


	friend class poly_multiplier;	

};

#endif

#endif
