#ifndef LIDIA_FACTORIZATION_H
#define LIDIA_FACTORIZATION_H
#if defined(HAVE_MAC_DIRS) || defined(__MWERKS__)
#include <LiDIA:comparator.h>
#include <LiDIA:sort_vector.h>
#include <LiDIA:bigmod.h>
#include <LiDIA:bigrational.h>
#else
#include <LiDIA/comparator.h>
#include <LiDIA/sort_vector.h>
#include <LiDIA/bigmod.h>
#include <LiDIA/bigrational.h>
#endif

const unsigned int dont_know = 2;
const unsigned int prime = 1;
const unsigned int not_prime = 0;
inline unsigned int state(unsigned int x) { return (x & 3); }

class ecm_primes;

class rf_single_factor
{
  friend class rational_factorization;
  friend istream & operator >> (istream &, rational_factorization &);

private:
   bigint single_base;
   int single_exponent;
   unsigned int factor_state;

public: 
    rf_single_factor() {};
   ~rf_single_factor() {};
    rf_single_factor & operator= ( const rf_single_factor & ) ;
    
   friend void swap(rf_single_factor & a, rf_single_factor & b);
   
private:  

   friend bool operator < (const rf_single_factor & a, const rf_single_factor & b);
   friend bool operator <= (const rf_single_factor & a, const rf_single_factor & b);
   friend bool operator == (const rf_single_factor & a, const rf_single_factor & b);
   
   friend istream & operator >> (istream &, rf_single_factor &);
   friend ostream & operator << (ostream &, const rf_single_factor &);   
};



class ec_curve
{
  friend class rational_factorization;
  friend class ec_point_M;
  friend class ec_point_W;
  friend void mecgen16(ec_curve &, ec_point_M &, bigint &, int);
  friend bigint trans(ec_curve &, ec_point_W &, ec_point_M &);
  friend void mecfind(ec_curve &, ec_point_M &, bigint &, unsigned int, 
		      unsigned int, ecm_primes &);
  friend void add (ec_point_W &, const ec_point_W &, const ec_point_W &, 
		   bigint &);
  friend void multiply_by_2 (ec_point_W &, const ec_point_W &, bigint &);

 private:
   bigmod a;
   bigmod b;
   
   ec_curve() { };
   ec_curve(const bigmod &l_a, const bigmod & l_b)
   {
      a.assign(l_a);
      b.assign(l_b);
   }
   ~ec_curve() { };
   
   void assign(const bigmod &l_a, const bigmod &l_b) 
   {
      a.assign(l_a);
      b.assign(l_b);
   }
   
   bigmod A() const   { return a; }
   bigmod B() const   { return b; }
};


class ec_point_M
{
  friend class rational_factorization;
  friend class ec_curve; 
  friend void mecfind(ec_curve &, ec_point_M &, bigint &, unsigned int, 
		      unsigned int, ecm_primes &);
  friend void mecgen16(ec_curve &, ec_point_M &, bigint &, int); 
  friend void add(ec_point_M &, const ec_point_M &, const ec_point_M &, const  
                  ec_point_M &);
  friend void multiply_by_2(ec_point_M &, const ec_point_M &, const bigmod &);
  friend void multiply(ec_point_M &, const ec_point_M &, const bigmod &, 
		       unsigned int, unsigned int );
  friend bigint trans(ec_curve &, ec_point_W &, ec_point_M &);

 private:
   bigmod x, z;
   
   ec_point_M() { };
   ec_point_M(const ec_point_M &mp)
   {
      x.assign(mp.x);
      z.assign(mp.z);
   }
   ~ec_point_M()  { };

   void assign(const ec_point_M &mp)
   {
      x.assign(mp.x);
      z.assign(mp.z);
   }
   
   void assign(const bigmod &l_x, const bigmod &l_y)
   {
      x.assign(l_x);
      z.assign(l_y);
   }

   bigmod X() const   {     return x; }
   bigmod Z() const   {     return z; } 
};


class ec_point_W
{
  friend class rational_factorization;
  friend class ec_curve;
  friend void cont(ec_point_W &, bigint &, unsigned int, unsigned int,  
		   ecm_primes & );
  friend void mecgen16(ec_curve &, ec_point_M &, bigint &, int); 
  friend bigint trans(ec_curve &, ec_point_W &, ec_point_M &);

 private:
   bigmod x, y;
   
   ec_curve  * curve;

   char is_0;              //Flag if the point is infinite or not

   ec_point_W()  
   {  
      curve = NULL;
      is_0 = 1;
   }

   ec_point_W(const ec_curve & we)
   {
      is_0 = 1;
      curve = (ec_curve *) &we;
   }
   
   ec_point_W(const ec_curve & we, const bigmod & l_x, const bigmod & l_y)
   {
      is_0 = 0;
      curve = (ec_curve *) &we;
      x.assign(l_x);
      y.assign(l_y);
   }

   ec_point_W(const ec_point_W &wp)
   {
      if ( !(is_0 = wp.is_0) )
      {
         x = wp.x;
         y = wp.y;
         
         curve = wp.Curve();
      }
   }
   
   ~ec_point_W()  { };

   int is_negative(const ec_point_W &wp) const
   {
      // added this to allow compiling on Mips with CC, TP
      int i;
      bigmod h = wp.y;
      
      add(h, h, y);
      
      if ( (is_0 == wp.is_0) && (is_0 || (x == wp.x) && h.is_zero()) )
	 // return 1;
         i = 1;
      else
	 // return 0;
         i = 0;
      return i;
   }
   
   ec_curve * Curve() const   {  return curve; }
   
   bigmod X() const
   { 
      if ( !is_0 )         return x;
      else
      {
         warning_handler("EC_POINT_W", "x-coordinate of infinite point");
         return (bigmod(0));
      }
   }

   bigmod Y() const
   {
      if ( !is_0 )         return y;
      else
      {
         warning_handler("EC_POINT_W", "y-coordinate of infinite point");
         return (bigmod(0));
      }
   }
   
   void assign(const bigmod &l_x, const bigmod & l_y)
   {
      x.assign(l_x);
      y.assign(l_y);
   }

   void assign_one()   { is_0 = 0; }
   
   void assign_zero()   { is_0 = 1; }

   void assign(const ec_point_W &wp)
   {
      if ( !(is_0 = wp.is_0) )
      {
         x.assign(wp.x);
         y.assign(wp.y);
         curve = wp.Curve();
      }
   }
   
   friend bool operator == (const ec_point_W &, const ec_point_W &);
   friend void add(ec_point_W &, const ec_point_W &, const ec_point_W &, 
		   bigint &);
   friend void addPQ(ec_point_W &, const ec_point_W &, const ec_point_W &, 
		     bigint &);
   friend void multiply_by_2(ec_point_W &, const ec_point_W &, bigint &);
   friend void multiply(ec_point_W &, const ec_point_W &, int, bigint &);

};


class ecm_primes
{
  friend class rational_factorization; 
  friend void mecfind(ec_curve &, ec_point_M &, bigint &, unsigned int, 
		      unsigned int, ecm_primes &);
  friend void cont(ec_point_W &, bigint &, unsigned int, unsigned int,  
		   ecm_primes & );

  // friend int compute_multiplier(bigint &N, int bis, ecm_primes &prim);
  // friend void mpqs(lidia_size_t index, ecm_primes & prim);
  // friend void mpqs_comp(lidia_size_t index);

 private:
   unsigned int *table,   // table to hold the actual primetable
                 *table2;  // table to hold the primes up to $sqrt$(max)
   unsigned int *over;     // table to hold the overhead 
                           //
   unsigned int max,       // upper limit for the primes
        p_max,             // number of odd numbers < sqrt(max)
        last_prime,        // last prime number in the actual primetable
        dim_ar,            // length of the actual primetable      
        space,             // upper limit for memory

        feld,              // pointers to hold the position in 
        feld_abs,          // the primetables
        pl; 

   unsigned int  maske35, maske7,     // patterns for sieving all 
                 maske11, maske13,    // of the primes 3, 5, 7, 11, 13, 17,
                 maske17, maske19,    //               19, 23, 29, 31
                 maske23, maske29, 
                 maske31, maske_i;

   char flag;          // flag == 0 : table contains all primes
                       // flag == 1 : we must sieve on several intervals


   void sieve_array(int);
   static const unsigned int  BIT;

   void initprimes(unsigned int, unsigned int, unsigned int);
   void killprimes();

   ecm_primes(unsigned int us, unsigned int os, unsigned int sp)
   { initprimes(us, os, sp); }

   ~ecm_primes()
   { killprimes(); }

   unsigned int getprimes();
   void resetprimes(unsigned int);

};

class rational_factorization
{

 private: 
   sort_vector < rf_single_factor > factors;
   int isign;
   int info;
   unsigned int decomp_state;
  
   void compose(); 
   void sort()
   { factors.sort(); }

   void trialdiv(lidia_size_t index, unsigned int lower_bound, 
		 unsigned int upper_bound, ecm_primes & prim);
   
   void ecm(lidia_size_t index, int jobs_avail, int job_buffer[30][5], 
	    ecm_primes & prim);
   
   void ecm(lidia_size_t index, int rest_number_of_factors, int jobs_avail, 
	    int job_buffer[30][5], ecm_primes & prim);
   
   static const unsigned int ecm_params[29][3];
   unsigned int ecm_read_max(int stell);
   int ecm_job_planing(int strat[30], int buf[30][5]);

   static const float qs_params[67][8];  
   rational_factorization & mpqs(lidia_size_t index, ecm_primes & prim);
   void qs_read_par(int stellen, double &T, int &M, int &groesse_FB, 
                    int &P_ONCE, int &POLY, int &P_TOTAL, int &smallstart);
   int create_FB(int groesse, const bigint &kN, int **FB, ecm_primes &prim);   
   bool qs_build_factors(bigint & N, bigint & kN, int index, int *FB);
   int compute_multiplier(bigint &N, int bis, ecm_primes &prim);
   static void compose( sort_vector< rf_single_factor > & v ) ; 
   static void refine2( sort_vector< rf_single_factor > & v, rf_single_factor & sf,
			const rf_single_factor   & a, const rf_single_factor & b ) ;

   double h_zeitqs(int i);
   void zeitqs(int i);

public:
   
   rational_factorization();
   rational_factorization(int n);
   rational_factorization(unsigned int n);
   rational_factorization(long n);
   rational_factorization(unsigned long n);
   rational_factorization(const bigint & n);
   rational_factorization(const bigrational & n);
   rational_factorization(const rational_factorization &); 
   ~rational_factorization();
   
   rational_factorization & operator = (const rational_factorization &);

#ifndef HEADBANGER
   void assign (long n);
   void assign (const bigint & n);
   void assign (const bigrational & n);
   void assign (const rational_factorization & f);
#endif
   

  void convert(base_vector<bigint> & basis, base_vector<int> & exponent);
  bigint base(lidia_size_t index) const;
  int exponent(lidia_size_t index) const;
  void set_exponent(lidia_size_t index, int expo);
   
   int sign() const
   { return isign; }
   
   void verbose(int a)
   { info = a; }
   
   lidia_size_t no_of_comp() const
   { return factors.size(); }
   
   bool is_prime_factor(lidia_size_t index);
   bool is_prime_factorization();
   
   /**
   ** input / output
   **/
   
   void pretty_print(ostream &);

   friend istream & operator >> (istream &, rational_factorization &);
   friend ostream & operator << (ostream &, const rational_factorization &);

   /**
   **  functions for computing with rational_factorizations
   **/

   void invert();
   inline friend void invert(rational_factorization & f, 
			     const rational_factorization & a)
   { f.assign(a); f.invert(); }

   void square();
   inline friend void square(rational_factorization & f,
			     const rational_factorization & a)
   { f.assign(a); f.square(); }

   friend void multiply (rational_factorization &, 
			 const rational_factorization &,    
			 const rational_factorization &);

   inline friend rational_factorization
   operator *(const rational_factorization & a,
	      const rational_factorization & b)
     { rational_factorization c; multiply(c, a, b); return c; }
   
   friend void divide (rational_factorization &, 
		       const rational_factorization &,    
		       const rational_factorization &);

   inline friend rational_factorization 
   operator / (const rational_factorization & a,
	       const rational_factorization & b)
     { rational_factorization c; divide(c, a, b); return c; }


  void refine();
  bool refine(const bigint &);
  bool refine_comp(lidia_size_t index, const bigint &);

  friend bool operator == (const rational_factorization &,
			  const rational_factorization &);

  inline friend bool operator != (const rational_factorization & a,
				 const rational_factorization & b)
  { return !(a == b); }

   /**
   ** factoring functions
   **/

  
   void trialdiv_comp(lidia_size_t index, unsigned int upper_bound=1000000, 
		      unsigned int lower_bound = 1);
   void trialdiv(unsigned int upper_bound = 1000000, 
		 unsigned int lower_bound = 1);
   
   void ecm_comp(lidia_size_t index, int upper_bound = 34, 
		 int lower_bound = 6, int step = 3);
   void ecm(int upper_bound = 34, int lower_bound = 6, int step = 3);
   
   rational_factorization & mpqs_comp(lidia_size_t index);

   void factor_comp(lidia_size_t index, int upper_bound = 34);
   void factor(int upper_bound = 34);

   friend rational_factorization trialdiv(const bigint & N, 
					  unsigned int upper_bound=1000000, 
					  unsigned int lower_bound = 1); 
   friend rational_factorization ecm(const bigint & N, int upper_bound = 34, 
				     int lower_bound = 6, int step = 3);
   friend rational_factorization mpqs(const bigint & N);
   friend rational_factorization factor(const bigint & N); 
  
};


#endif

